Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

User.GetUserId() fails inside controller's constructor

I am getting the following error: Value cannot be null. Parameter name: principal

How can I access Identity (userId) inside the controller's constructor? I can only get it working by wrapping the failed call in a function (both highlighted below).

Is there anything I need to inject?

public class BaseController : Controller {
    protected readonly MylDbContext dbContext;
    protected readonly string userId;

    public BaseController(MylDbContext dbContext) {
        this.dbContext = dbContext;
        userId = User.GetUserId(); // fails here
    }

    public string GetUserId() {
        return User.GetUserId(); // works here
    }
}
like image 221
Toonsylvania Avatar asked Oct 16 '15 11:10

Toonsylvania


2 Answers

As @Henk mentioned, the controller constructor will be executed before the ActionContext has been set, so you won't have access to properties like Context, Request or User. You need to retrieve the userId within the context of a request.

You could use the old-fashioned approach of the action filters which are still part of the MVC6 pipeline (Which also supports async action filters via IAsyncActionFilter).

Since you want to set a property in your controller, the most simple way you could implement this is by overriding the OnActionExecuting method in your controller class. This works because you are inheriting from Controller, which already implements IActionFilter.

public override void OnActionExecuting(ActionExecutingContext context)
{
    //Get user id   
    userId = User.GetUserId();
}

Edit

If you check the DefaultControllerFactory you will see that:

  1. first the controller is created
  2. then the ActionContext is set (Via the DefaultControllerPropertyActivator which is one of the property activators):

    var controller = _controllerActivator.Create(actionContext, controllerType);
    foreach (var propertyActivator in _propertyActivators)
    {
        propertyActivator.Activate(actionContext, controller);
    }
    
like image 79
Daniel J.G. Avatar answered Oct 13 '22 14:10

Daniel J.G.


When the controller is instantiated there is no guarantee that the request information is avaiable in the HttpContext yet. There might not be a request at all. Is there reason why you need that information in the constructor?

Edit
I understand your issue. What I usually do in a scenario like this, is create a property with a backing field which only queries once per controller:

private int? _userId;
public int UserId
{
    get
    {
        if (!_userId.HasValue)
        {
            // query from db.
            _userId = 42;
        }
        return _userId.Value;
    }
}
like image 22
Henk Mollema Avatar answered Oct 13 '22 12:10

Henk Mollema