I'm following this ASP.NET MVC tutorial from Microsoft:
My code is slightly different, where I'm trying to access HttpContext.Request.IsAuthenticated in the controller's constructor.
namespace SCE.Controllers.Application
{
public abstract class ApplicationController : Controller
{
public ApplicationController()
{
bool usuario = HttpContext.Request.IsAuthenticated;
}
}
}
The problem is that HttpContext is always null.
Is there a solution to this?
instead of putting your HttpContext.Request.IsAuthenticated in Controller level you should put it in Controller Base class that will be inherited in all of your controller with an override method of OnActionExecuting() method.
In your Controller base you should have
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext ctx) {
base.OnActionExecuting(ctx);
ViewData["IsAuthenticated"] = HttpContext.Request.IsAuthenticated;
}
}
and all your Controller should inherit the BaseController class
public class ApplicationController : BaseController
now you should get the ViewData["IsAuthenticated"] in your Master page.
Edit
With the link you have given, and relating to what you have done, your ApplicationController is a Page Controller, not a Base Controller. In the example, ApplicationController is a Base Controller that is inherited by the HomeController but what you have done is you are placing the Action method inside your base controller which is the ApplicationController so your Action Index method will not be invoked when you call any page (Index page) that is not from the ApplicationController.
I would suggest you use:
System.Web.HttpContext.Current.Request
Just remember System.Web.HttpContext.Current is threadstatic, but if you don't use additional thread the solution works.
The Controller is instantiated significantly prior to the point where the Index action is invoked, and at the moment of construction HttpContext is indeed unavailable. What's wrong with referencing it in your controller method Index?
The solution of this problem is to create an override method of Initialize by passing RequestContext object.
public class ChartsController : Controller
{
bool isAuthed = false;
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
if (requestContext.HttpContext.User.Identity.IsAuthenticated)
{
isAuthed =true;
}
}
}
With the answer I am posting here, you cannot access IsAuthenticated, but you can access some stuffs related to HttpContextRequest (see in image),
I needed session value in constructor.
You can use IHttpContextAccessor as below:
public ABCController(IHttpContextAccessor httpContextAccessor)
{
//do you stuff with httpContextAccessor,
// This gives session value
string abc = httpContextAccessor.HttpContext.Session.GetString("Abc");
}
and in startup.cs, you need to configure,
services.AddHttpContextAccessor();
It is possible to get the HttpContext using IHttpContextAccessor injected into class constructor. Before doing so, you will need first to register the corresponding service to the service container in Startup.cs class or Program.cs such as below.
services.AddHttpContextAccessor(); // Startup.cs
builder.Services.AddHttpContextAccessor(); // Program.cs
Right after that, you can inject the IHttpContextAccessor interface in whererever method or class constructor.
private bool isAuthenticated { get; set; }
public ConstructorName(IHttpContextAccessor accessor)
{
var context = accessor.HttpContext;
isAuthenticated = context.User.Identity.IsAuthenticated;
}
Related
I am required to record user action and I don't want to have code in every method is every controller so would it make sense to somehow do this in the basecontroller ? OR is there a better way?
public class BaseController : Controller
{
protected ILogger logger;
public BaseController(ILogger<BaseController> logger)
{
this.logger = logger;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
//How do I get the current controller?
//How do I get the current method being called?
//How can I pass in additional parameters?
//How can I get the user?
logger.LogWarning("Loaded BaseController");
base.OnActionExecuting(context);
}
}
There are many ways to do that.
First: You could create your own base controller and implement OnActionExecution as you did. See the sample bellow to get information from ActionExecutingContext.
If you go this way, every controller that inhirits from this base controller will get the implementation of the logger because you are overriding OnActionExecuting (that applies to all actions of your controller).
public override void OnActionExecuting(ActionExecutingContext context)
{
//How do I get the current controller?
string controllerName = context.ActionDescriptor.ControllerDescriptor.ControllerName
//How do I get the current method being called?
string actionName = context.ActionDescriptor.ActionName;
//How can I pass in additional parameters?
foreach (var parameter in context.ActionParameters)
{
var parameterKey = parameter.Key;
var parameterValue = parameter.Value;
}
//How can I get the user?
var user = this.User; // IPrinciple instance, explore this object
logger.LogWarning("Loaded BaseController");
base.OnActionExecuting(context);
}
Second: On the other hand, you can use ActionFilters which is a class that inhirits from ActionFilter class and do the same implementation on this classe overriding the OnActionExecuting. Then you can decorate your controllers with this attribute to make the logger. Given it is an attribute, you have to define the name fo the class with a sufix Attribute and use without it. For sample:
public class LoggerAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
// same code above
}
}
[Logger]
public class CustomerController : Controller
{
// actions code...
}
Third: Use the same action filter class and instead of applying on all classes you want, you define it as a global action filter and it will be applied to all controllers. You have to define it on GlobalFilter and if you are using the default template of asp.net mvc, you can define it on the FilterConfig.cs, for sample:
filters.Add(new LoggerAttribute());
For getting controller & action names you can use ActionDescriptor of ActionExecutingContext
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var descriptor = filterContext.ActionDescriptor;
var actionName = descriptor.ActionName;
var controllerName = descriptor.ControllerDescriptor.ControllerName;
......
base.OnActionExecuting(filterContext);
}
Regarding User information: controller initialisation will occur before authorisation takes place. So all of yours controllers will be created before any OnAuthorization takes place.
Approach to deal with these situations is to use Action Filters. The Authorize Attribute is fired early than controller initialisation occur.
Have a look this articles:
How to get controller and action name in OnActionExecuting?
How do I get the action name from a base controller?
Getting User Identity on my base Controller constructor
I am trying to make sure that my MVC application only uses one DbContext per Request in order to reduce number of times a Db connection is open and so there are no concurrency issues.
This means i will need to use the same context in my Global Action Filters as well as my Controllers.
I have tried something like this
public class LayoutFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
MembershipUser loggedInUser = Membership.GetUser();
MyUnitOfWork uow = new MyUnitOfWork();
ViewBag.FullName = uow.UserService.GetUser().FullName
filterContext.ActionParameters["unitOfWork"] = uow;
}
}
However the context is disposed when i try to read it from the controller as shown below
public ActionResult Logout(MyUnitOfWork uow)
{
ViewBag.Something = uow.ExampleService.GetMyObject();
return RedirectToAction("LogIn");
}
I get the same issue with the context being disposed when i try to share the same unitOfWork object by casting a property of a base controller class
public class BaseController : Controller
{
public RequestboxUnitOfWork unitOfWork;
}
public class LayoutFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
MembershipUser loggedInUser = Membership.GetUser();
BaseController baseController = (BaseController)filterContext.Controller;
ViewBag.FullName = baseController.unitOfWork.UserService.GetUser().FullName
filterContext.ActionParameters["unitOfWork"] = uow;
}
}
The context is disposed when i try to access it in the controller and i have read in a few places that you should not use base controller class so i am unsure of what i can do.
What are the recommended ways to share a entity context between ActionFilters and Controllers
Create the DBContext as part of the Controller setup, and have it available via an internal property on the controller.
public class MyController : Controller
{
private MyUnitOfWork unitOfWork;
internal MyUnitOfWork UnitOfWork
{
get { return unitOfWork; }
}
}
You will then be able to access the context in the filter attribute like this:
MyController controller = (MyController)filterContext.Controller
MyUnitOfWork uow = controller.UnitOfWork;
There's no need to pass the unit of work back to the action method in the controller, because the controller already has the object, and it can be accessed via the same internal property.
I need to setup a policy in base controller that applies to all controller instance, like below:
public class BaseController : Controller
{
private IPolicy Policy;
public BaseController()
{
this.Policy= new Policy(HttpContext);
}
}
Within the Policy class, I need to do something like:
this.httpContextBase.User.
Questions: (Update)
What is the better way to design the BaseController in terms of using HttpContext and Unit test.
What is the correct way to unit test HttpContext?
Absolutely no way. You are using the HttpContext inside the constructor of a controller when this context is still not initialized. Not only that this code cannot be tested but when you run the application it will also crash with NRE. You should never use any HttpContext related stuff in a constructor of a controller.
One possibility is to refactor your code and perform this inside the Initialize method:
public class BaseController : Controller
{
private IPolicy Policy;
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
this.Policy = new Policy(HttpContext);
}
}
This being said, that's not the approach I would recommend. I would recommend you using dependency injection instead of service location which is considered by many as an anti-pattern.
So:
public abstract class BaseController : Controller
{
protected IPolicy Policy { get; private set; }
protected BaseController(IPolicy policy)
{
Policy = policy;
}
}
Now, all that's left is to configure your favourite Dependency Injection framework to inject the correct instance into the constructor. For example with Ninject.Mvc3 this is achieved with a single line of code:
kernel.Bind<IPolicy>().To<Policy>();
Now you can feel more than free to mock this IPolicy in your unit test without even caring about any HttpContext.
For example let's suppose that you have the following controller that you want to unit test:
public class FooController : BaseController
{
public FooController(IPolicy policy): base(policy)
{ }
[Authorize]
public ActionResult Index()
{
Policy.DoSomething();
return View();
}
}
Now, all that you need to do is pick up your favorite mock framework (Rhino Mocks in my case) and do the mocking:
[TestMethod]
public void Index_Action_Should_DoSomething_With_The_Policy()
{
// arrange
var policyStub = MockRepository.GenerateStub<IPolicy>();
var sut = new FooController(policyStub);
// act
var actual = sut.Index();
// assert
Assert.IsInstanceOfType(actual, typeof(ViewResult));
policyStub.AssertWasCalled(x => x.DoSomething());
}
I would like to know how am I able to redirect the request inside the controller constructor if I need to do it?.
For example:
Inside the constructor I need to initialize an object with an dynamic value, in some cases I don't want to do it and in that case I want to redirect to some other place.
At the same way the rest of the constructor will not be executed neither the "original following action".
How can I do it?
Thank you
EDIT #1
Initially I used:
public override void OnActionExecuting(ActionExecutingContext filterContext)
There I could redirect to some other controller/action/url, but later in time, I needed to change my controller, where I initialize an variable in his constructor and have some code that really needs to redirect the request :P
I need this also because the OnActionExecuting executes AFTER the controller constructor.
And in my logic, the redirect needs to be done there.
Performing redirects inside the controller constructor is not a good practice because the context might not be initialized. The standard practice is to write a custom action attribute and override the OnActionExecuting method and perform the redirect inside. Example:
public class RedirectingActionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
if (someConditionIsMet)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new
{
controller = "someOther",
action = "someAction"
}));
}
}
}
and then decorate the controller which you would like to redirect with this attribute. Be extremely careful not to decorate the controller you are redirecting to with this attribute or you are going to run into an endless loop.
So you could:
[RedirectingAction]
public class HomeController : Controller
{
public ActionResult Index()
{
// This action is never going to execute if the
// redirecting condition is met
return View();
}
}
public class SomeOtherController : Controller
{
public ActionResult SomeAction()
{
return View();
}
}
I currently have a abstract controller class that I all my controllers inherit from.
I want to be able to use the current user (IPrinciple) object in my master page.
I read that I could use the contructor of my abstract base controller class, that is I could do something like
public BaseController()
{
ViewData["UserName"] = this.User.Identity.Name;
}
I could then access ViewData["UserName"] etc from my master page.
My problem is that this.User is null at this point.
Does anybody know of a different approach?
Thanks in advance.
You could write an ActionFilter and in the OnActionExecuted event put the user inside ViewData:
public class UserActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
filterContext.Controller.ViewData["UserName"] = filterContext.HttpContext.User.Identity.Name;
}
}
And then decorate your base controller with this attribute:
[UserActionFilter]
public abstract class BaseController: Controller
{ }