Why is User (as in User.Identity.Name) null in my abstract base controller?
Asked Answered
O

10

27

I was asking a related question but messed the title up and no-one would understand it. Since I am able now to ask the question more precisely, I decided to reformulate it in a new question and close the old one. Sorry for that.

So what I want to do is passing data (my custom user's nickname as stored in the db) to the LoginUserControl. This login gets rendered from the master page via Html.RenderPartial(), so what I really need to do is making sure that, say ViewData["UserNickname"] is present on every call. But I don't want to populate ViewData["UserNickname"] in each and every action of every controller, so I decided to use this approach and create an abstract base controller which will do the work for me, like so:

public abstract class ApplicationController : Controller
    {
        private IUserRepository _repUser;

        public ApplicationController()
        {
            _repUser = RepositoryFactory.getUserRepository();
            var loggedInUser = _repUser.FindById(User.Identity.Name); //Problem!
            ViewData["LoggedInUser"] = loggedInUser;
        }
    }

This way, whatever my deriving Controller does, the user information will already be present.

So far, so good. Now for the problem:

I can't call User.Identity.Name because User is already null. This is not the case in all of my deriving controllers, so this is specific for the abstract base controller.

I am setting the User.Identity.Name via FormsAuthentication at another place in the code, but I think this can't be the problem - afaik User.Identity.Name can be null, but not User itself.

It looks to me like the HttpContext is not available (since also null ;-) and that I am missing a simple yet important point here. Can anyone give me some hints? I would really appreciate it.

Osric answered 10/1, 2009 at 7:27 Comment(0)
X
14

My guess would be that the Controller's base constructor is not filling in the User, but that it is only known later when the ControllerContext is set for the Controller. You should check this in the documentation about the lifecycle of an MVC application, (the one here will probably do, although it might be a bit out of date since it's for the preview version), or just check the source code of MVC.

from the code that I have of MVC (also a preview version, but that should be fine): (In Controller)

 public IPrincipal User {
            get {
                return HttpContext == null ? null : HttpContext.User;
            }
        }

...

public HttpContextBase HttpContext {
        get {
            return ControllerContext == null ? null : ControllerContext.HttpContext;
        }
    }

I don't see en an implementation of a default constructor in the code. That would prove that the ControllerContext is null at the time of construction.

So you should execute your code somewhere else.

Xerox answered 10/1, 2009 at 9:34 Comment(1)
Strange, because User is null but System.Web.HttpContext.Current.User is not. MVC 1.0Wept
O
26

The answer to this problem is actually quite simple. I can't execute the code from within the constructor for reasons pointed out by Raimond, but I can do it outside the constructor.

So what I did was overriding onActionExecuting() in the base controller class (I created a custom Attribute for it, but just overriding the method should also work) and then do my user lookup from there.

Now it works as expected and I have no repeated code.

Osric answered 19/1, 2009 at 11:42 Comment(0)
E
23

The User property is not assigned until after the Controller has been instantiated, but you can gain early access from your constructor with:

System.Web.HttpContext.Current.User
Emerald answered 26/11, 2016 at 19:14 Comment(0)
X
14

My guess would be that the Controller's base constructor is not filling in the User, but that it is only known later when the ControllerContext is set for the Controller. You should check this in the documentation about the lifecycle of an MVC application, (the one here will probably do, although it might be a bit out of date since it's for the preview version), or just check the source code of MVC.

from the code that I have of MVC (also a preview version, but that should be fine): (In Controller)

 public IPrincipal User {
            get {
                return HttpContext == null ? null : HttpContext.User;
            }
        }

...

public HttpContextBase HttpContext {
        get {
            return ControllerContext == null ? null : ControllerContext.HttpContext;
        }
    }

I don't see en an implementation of a default constructor in the code. That would prove that the ControllerContext is null at the time of construction.

So you should execute your code somewhere else.

Xerox answered 10/1, 2009 at 9:34 Comment(1)
Strange, because User is null but System.Web.HttpContext.Current.User is not. MVC 1.0Wept
M
5

Can you grab this using something like:

HttpContext currentContext = HttpContext.Current;
string userName = currentContext.User.Identity.Name;

Or is the HttpContext always empty??

Could you set the httpContext through the constructor of the abstract class? and use it this way?

Marozik answered 10/1, 2009 at 9:1 Comment(0)
O
4

Thanks Raimond. I was too tired to see the obvious. @Keeney: Yes the context is always null. Raimond pointed out why. Thanks anyway, I didn't see why too :-)

My current working solution (albeit not what I wanted) is a Attribute that I use to decorate all my controller actions. Here is the implementation:

public class MasterPageDataAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            base.OnActionExecuting(filterContext);
            IUserRepository _repUser = RepositoryFactory.getUserRepository();
            IPrincipal siteUser = filterContext.Controller.ControllerContext.HttpContext.User;
            User loggedInUser = null;

            if (siteUser == null || siteUser.Identity.Name == null)
            {
                //do nothing
            }
            else
            {
                loggedInUser = _repUser.findUserById(siteUser.Identity.Name);
            }
            filterContext.Controller.ViewData["LoggedInUser"] = loggedInUser ?? new User { Nickname = "Guest" };
        }
    }

I will be looking into how to get that code executed in a way that follows the DRY principle, since using attributes for that definitely means repeating oneself. Maybe some sort of interceptor (interesting idea) or hook might help.

Cheers for that.

Osric answered 10/1, 2009 at 15:54 Comment(1)
I would recommend overwriting the Initialize method of your custom Controller class and moving your code from the constructor to there.Gonzalez
M
1

I am doing this in a basecontroller implementation and it works as expected.

public abstract class BaseController : Controller
{
    public bool LoggedOn
    {
        get { return User.Identity.IsAuthenticated; }
    }
}

This always returns true or false for me so User != null

Metamer answered 11/1, 2009 at 22:23 Comment(2)
This is because that User object is filled in by the time the LoggedOn Property is called.Gonzalez
Because a property's getter is not a controller constructorAnachronistic
B
0

to Masterfu: I did something similiar with your help, wish that can help latter visitors. In my case, i need to create reposiotry of controllers for different users, yet in the constructor of controllers, (principal)User is not ready. So i created a attribute for controllers:

[CreateRepositoryByUser]
public class MFCController : Controller
{
    protected MFCRepository _repository
    {
        get { return ViewData["repository"] as MFCRepository; }
    }
...

the _repository, indeed, is not a private variable of controller, but somethign create by the attribute:

public class CreateRepositoryByUser : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        CreateRepository(filterContext);
    }

    public static void CreateRepository(ActionExecutingContext filterContext)
    {
        if (filterContext.Controller.ViewData["repository"] == null)
        {
            filterContext.Controller.ViewData["repository"] =
                MFCRepository.CreateMFCRepository(filterContext.Controller.ControllerContext.HttpContext.User);
        }
    }
}

I put codes of creating the repository in a seperate method, in case of that other attributes may want to use (principal)User before this attribute being triggered.

Biolysis answered 17/3, 2013 at 2:55 Comment(0)
A
0

Calling from a constructor is too soon in the MVC pipeline.

Moving code to OnAuthorization, you get authorized user in a parameter. Worked for me!

From your example I would do something like this:

public abstract class ApplicationController : Controller {
    private IUserRepository _repUser;

    protected override void OnAuthorization(AuthorizationContext filterContext)
    {
        _repUser = RepositoryFactory.getUserRepository();
        var loggedInUser = _repUser.FindById(filterContext.HttpContext.User.Identity.Name); //Problem!
        ViewData["LoggedInUser"] = loggedInUser;
    }


}
Await answered 16/2, 2016 at 15:8 Comment(0)
R
0

Inject IPrincipal if you need User in the constructor.

 // startup.cs
 // Inject IPrincipal
 services.AddTransient<IPrincipal>(provider => provider.GetService<IHttpContextAccessor>().HttpContext.User);

Then add as IPrincipal in your constructor. Note that it is guaranteed to be ClaimsPrincipal with ASPNET - because that's what HttpContext.User is.

Similar question

Rotunda answered 28/1, 2019 at 23:23 Comment(0)
O
-3
Select Project -> press F4 -> anonymous login -> false  | windows authentication - > True
Otis answered 14/9, 2020 at 7:52 Comment(1)
This enables Windows Authentication, which definitely isn't the solution for OP's problemWhoops

© 2022 - 2024 — McMap. All rights reserved.