I am getting HttpContext null sometime in the derived class. I am currently inheriting BaseController into ActionLogsController.
Just look at my code:
BaseController:
public abstract class BaseController : Controller
{
protected virtual new CustomPrincipal User
{
get { return HttpContext.User as CustomPrincipal; }
}
}
ActionLogsController:
public class ActionLogsController : BaseController
{
public ActionResult AddActionLog(ActionLogModel model)
{
var name = user.name;
}
}
What I am doing wrong? It gives null sometime. Is it just a crash or I need to do any change in my code?
Controller already has a User property.
Alright, you try create a new one, but there is not need to directly couple to HttpContext there.
Cast the base property to your custom principle.
public abstract class BaseController : Controller {
protected virtual new CustomPrincipal User {
get { return base.User as CustomPrincipal; }
}
}
It gets the user from the same place.
Controller.cs
public IPrincipal User
{
get { return HttpContext == null ? null : HttpContext.User; }
}
The risk with doing what you are doing is that you need to make sure that principle is of the matching type (in this case your custom principle), otherwise the cast will cause the property to always return null.
And that is because if the current request's user is not authenticated you may get a principle for an anonymous user, which, since it is not your CustomPrincipal will result in null being returned when User is called.
Related
This question already has an answer here:
Read ControllerBase.User in constructor
(1 answer)
Closed 4 years ago.
I'm writing an Asp.Net core application and I have the user authenticate with Google prior to gaining navigation access. When the user navigates to my "WorldBuilder" area and hits the WorldController, the BaseController.User object is null. This is the only area/controller I have set up so far.
The user is valid when the log in, as I can iterate over the Claims associated with the user. I am using the default Asp.Net core google authentication, so the user just receives a button on the login page directing them to authenticate with Google.
[Authorize]
[Area("WorldBuilder")]
public class WorldController : Controller
{
private string _userId;
private IWorldService _worldService;
private IUserRepository _userRepository;
public WorldController(IWorldService worldService, IUserRepository userRepository)
{
if (User != null) /* The user object is found to be null here. */
{
var userIdNameClaim = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
if (userIdNameClaim != null)
{
_userId = userIdNameClaim.Value;
}
}
_userRepository = userRepository;
_worldService = worldService;
}
I would expect that since the areas are part of the same application that the authenticated user information would be passed between areas, however that does not appear to be the case. The BaseController.User object is null
You should only access the User property (and others such as HttpContext) from within actions of your controller. The controller is constructed before authentication and authorization have been performed, hence the properties are null.
If you need general access to the User ID, you could create a property on the controller where you retrieve the User ID from the user object and use that property from your actions. You can even move this property to a base class and use that class as base controller to have access to the property from each controller.
For example:
public class ControllerBase : Controller
{
public string UserId
{
get
{
if (User != null) /* The user object is found to be null here. */
{
var userIdNameClaim = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
if (userIdNameClaim != null)
{
return userIdNameClaim.Value;
}
}
return null;
}
}
}
And your specific controller:
public class WorldController : ControllerBase
{
// Contructor, etc...
public IActionResult Get()
{
var userId = UserId;
// Do something with userId (or use UserId directly)
}
}
You can use HttpContextAccessor to pass the HttpContext down the line to Areas, Repositories etc
More about how to use HttpContextAccessor and set it up using Depenedency injection here
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-2.2#use-httpcontext-from-custom-components
I want access base class member in our Log Aspect Class. I have one base controller & that controller inherit by Test controller & in Test Controller i implemented AOP Aspect.
In BaseContoller i have a member _userSession. I initializing _userSession when BaseContoller's Constructor is call. And after call TestController first AOP Aspect is called. I want Access _userSession on OnEntry method of AOP.
LogAspect Class
[Serializable]
[MulticastAttributeUsage(MulticastTargets.Method)]
public class LogAspect:PostSharp.Aspects.OnMethodBoundaryAspect
{
public object UserData;
public override void OnEntry(PostSharp.Aspects.MethodExecutionArgs args)
{
LogManager.Info(string.Format("Starting - {0}-{0}",args.Instance, new StackTrace().GetFrame(1).GetMethod().Name));
// want access PCX.Portal.Controllers.BaseController._userSession member here its showing in quick watch like this
//((PCX.Portal.Controllers.BaseController)(args.Instance))._userSession
LogManager.Info(string.Format("User data - {0}", FrameworkHelper.Helper.JSONHelper.GetJSON(UserData)));
if(args.Arguments.Count>0)
{
foreach (var item in args.Arguments)
{
LogManager.Info(string.Format("arguments - {0}", FrameworkHelper.Helper.JSONHelper.GetJSON(item)));
}
}
base.OnEntry(args);
}
Base controller
public class BaseController : Controller
{
public UserSession _userSession { set; get; }
AuthenticationManager _authenticationManager = new AuthenticationManager();
public BaseController()
{
//initializing _userSession here
_userSession.userid=4 ;
_userSession.customerId=5 ;
}
}
Test Controller
[LogAspect]
public class TestController : BaseController
{
public ActionResult Index()
{
return View();
}
}
As documentation states:
MethodExecutionArgs class contains property Instance that:
Gets or sets the object instance on which the method is being executed.
as long as your method is not static you will get the object that is the this inside that method. Now you need to just cast it to BaseController as your property is public you will be able to access it.
if(args.Instance != null){
var baseController = (BaseController)args.Instance;
baseController._userSession
}
Although this is what you asked for I feel a need to remark that this approach limits your aspects usability to only instance methods of classes that inherit from BaseController. If you are able to create/retrieve form somewhere the session data in that parameterless constructor you can do it in aspect as well.
I am trying to set a session inside a abstract class which is already written. What I am trying to do is;
First I try to check the method type either is get or post. If it is GET method then set the session.
here is the code:
public abstract class BaseAbstractController : Controller
{
public BaseAbstractController()
{
if (this.HttpContext.Request.HttpMethod.ToString() == "GET")
{
this.HttpContext.Session["testsession"] = this.HttpContext.Request.Url.AbsolutePath;
}
}
}
The problem I am facing is, I get the Null exception error and it is because HTTPContext value is null.
Right now there is only one MVC controller that extends from abstract controller.
It is better not doing that in constructor, as constructor should only construct the instance if possible. You can override OnActionExecuting instead.
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
var req = filterContext.RequestContext.HttpContext.Request;
if (req.HttpMethod == "GET")
{
filterContext.RequestContext.HttpContext.Session["testsession"] = req.Url;
}
}
Your problem is that the HttpContext property within Controller that you're referencing isn't set until AFTER the controller has been instansiated - meaning it will not be available within your abstract classes' constructor.
If you need to access the HttpContext object then your best bet is to reference the static instance directly like so: System.Web.HttpContext
public abstract class BaseAbstractController : Controller
{
public BaseAbstractController()
{
if (System.Web.HttpContext.Current.Request.HttpMethod.ToString() == "GET")
{
System.Web.HttpContext.Current.Session["testsession"] = System.Web.HttpContext.Current.Request.Url.AbsolutePath;
}
}
}
Update:
To respond to your comment, if you're trying to access the HttpContext within OnActionExecuted then you should access it via the request context within the ActionExecutedContext argument like this:
protected virtual void OnActionExecuted(ActionExecutedContext filterContext)
{
var context = filterContext.HttpContext;
}
The difference is that at this point the controller has been instantiated and the base controller's HttpContext property has been set. Ultimately all the .NET MVC framework is doing is referencing the System.Web.HttpContext static instance from Controller.HttpContext.
I've written a custom principal object which contains a few additional fields (email and userid in addition to the username).
In order to access these properties I have to cast the Context.User object as my custom principal.
#Html.GetGravitarImage((User as CustomPrincipal).Email)
This custom principal is created / deserialized via the Application_AuthenticateRequest in my global.ascx. You can see this question I asked here for more information.
private void Application_AuthenticateRequest(Object source, EventArgs e)
{
var application = (HttpApplication)source;
var context = application.Context;
// Get the authentication cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = context.Request.Cookies[cookieName];
if (authCookie == null)
return;
var authTicket = FormsAuthentication.Decrypt(authCookie.Value);
context.User = CustomPrincipal.CreatePrincipalFromCookieData(authTicket.UserData);
}
However, if a user isn't authenticated, then my cast to CustomPrincipal will fail (because it won't be injected in the method above) and the result of the (User as CustomPrincipal) will return null, thus giving me a null reference exception when my method above attempts to get the email.
What would be a clean solution to this problem? I want to make accessing my custom principal easy and having to do the following seems cumbersome:
#Html.GetGravitarIcon((User is CustomPrincipal) ? (User as CustomPrincipal).Email : "Default Email")
Is this the only way to handle this situation?
I whipped something together quickly. One possible way of easily introducing a custom IPrincipal in ASP.NET MVC is the following:
1) Create your own descendant of the IPrincipal interface.
public interface IMyPrincipal : IPrincipal
{
Guid UserId { get; }
string EmailAddress { get; }
}
2) Let's assume you are using the ASP.NET Membership provider to authenticate your users. Let's quickly build an IMyPrincipal implementation which utilizes the membership API.
public class MyPrincipal : IMyPrincipal
{
private MembershipUser _user;
public MyPrincipal()
{
this._user = Membership.GetUser();
var userName = this._user != null ? this._user.UserName : String.Empty;
this.Identity = new GenericIdentity(userName);
}
public Guid UserId
{
get
{
return this._user != null ? (Guid) this._user.ProviderUserKey :
default(Guid);
}
}
public string EmailAddress
{
get
{
return this._user != null ? this._user.Email : null;
}
}
public IIdentity Identity { get; private set; }
public bool IsInRole(string role) { return false; }
}
3) Create your own base class type for your controllers. Hide the inherited User member and introduce your own IPrincipal descendant.
public class BaseController : Controller
{
protected virtual new MyPrincipal User
{
get { return HttpContext.User as MyPrincipal; }
}
}
4) Have all your controllers descend from this new BaseController type.
public class HomeController : BaseController
{
//...
}
5) Create your own controller factory to make sure your principal is introduced on the HttpContext / Thread.
public class MyControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance
(RequestContext requestContext, Type controllerType)
{
try
{
var controller = base.GetControllerInstance(requestContext, controllerType);
requestContext.HttpContext.User = Thread.CurrentPrincipal = new
MyPrincipal();
return controller;
}
catch (Exception)
{
return base.GetControllerInstance(requestContext, controllerType);
}
}
}
6) Register the controller factory in the Global.asax's Application_Start() event handler.
var controllerFactory = new MyControllerFactory();
ControllerBuilder.Current.SetControllerFactory(controllerFactory);
Voila, now you can use the new User (IMyPrincipal) anywhere in your controllers.
For example:
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
ViewBag.UserId = User.UserId;
ViewBag.UserName = User.EmailAddress;
return View();
}
You could either create a base class and override the "User" property using the "new" keyword or create an extension method like this:
public static class ControllerExtensions
{
public static CustomPrincipal CustomPrincipal(this Controller controller)
{
if(controller.User is CustomPrincipal)
{
return controller.User as CustomPrincipal;
}
return null; // maybe return an empty object instead to get around null reference...
}
}
The best way to make your IPrincipal implementation accessible in your Razor pages using ASP.NET MVC, is doing the following:
Implement the System.Security.Principal.IPrincipal interface.
Implement the System.Security.Principal.IIdentity interface.
In Global.asax define a method for: void Application_AuthenticateRequest(Object, EventArgs) that persists your both implementations of IPrincipal and IIdentity.
Create an extension method for IPrincipal to expose your implementation of IIdentity.
Finally, add the namespace for the previous extension method in your web.config file in <system.web.webPages.razor>.
At the end, you will be able to access your custom implementation of IIdentity instead of type casting. You now can access your custom implementation like this:
Hello #User.CustomIdentity().FirstName #User.CustomerIdentity().LastName!
These steps are a concise and brief description of a well detailed article written here: http://rizzo7.blogspot.com/2012/04/mvc-30-razor-custom-principal-and.html
You could create some sort of utility method or add a method to one of your services that checks if it's your custom principal. Maybe something like:
public class UserService : IUserService
{
public CustomPrincipal CurrentUser
{
get
{
CustomPrincipal user = HttpContext.Current.User as CustomPrincipal;
if (user == null)
return GuestUser; // Just some default user object
return user;
}
}
}
You could also make extension methods for the Email and UserID in the same fashion as John Kalberer's answer:
public static class CustomPrincipalExtensions
{
public static string Email(this CustomPrincipal cUser)
{
return cUser != null ? cUser.Email : "Default Email"
}
public static string UserID(this CustomPrincipal cUser)
{
return cUser != null ? cUser.UserID : "Default ID"
}
}
When not authorized, you could set the user object to a specific instance of the custom principal with default values:
if (authCookie == null)
{
context.User = CustomPrincipal.Default; // Or CreateDefault()
return;
}
I have created an abstract controller class (ApplicationController) for handling some user authentication, but HttpContext is not initialized when the code get called.
public abstract class ApplicationController : Controller
{
public ApplicationController()
{
string myuser = HttpContext.User.Identity.Name; // NullReferenceException
}
}
Yassir is correct about using protected constructors in abstract classes. But you are correct that it doesn't solve your problem--the HttpContext still ain't quite populated yet so you get null reference exceptions.
Anyhow, the solution is simple--override the Initialize method of the controller:
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
string myuser = this.User.Identity.Name;
base.Initialize(requestContext);
}
try to make your .ctor protected
public abstract class ApplicationController : Controller
{
protected ApplicationController()
{
string myuser = this.User.Identity.Name;
}
}
also make sure you are not missing this using directive:
using System.Web.Mvc;