dependency-injectionasp.net-coreautofac.net-coreasp.net-authorization

Dependency Injection on AuthorizationOptions Requirement in DotNet Core


I have a .NET core project and am trying to create a custom policy using AuthorizationOptions as shown in the documentation located here:

ASP.NET.Core Authorization - Dependency Injection in requirement handlers

The examples show setting up an authorization requirement with 1 parameter - a simple int value. My custom requirement requires a string parameter as well as a DbContext object. I want to inject the DbContext into the requirement's constructor at runtime. I am using the Autofac container. I'm not sure how I can achieve this - have tried several approaches and nothing is working so far.

Here is my custom requirement:

public UserNameRequirement(string username, MyDbContext context)
{
    _userName = username;
    _dbContext = context;
}

When setting up the authorization options in Startup.cs ConfigureServices method the documentation shows you register this like so:

services.AddAuthorization(options =>
{
    options.AddPolicy(
        "UserNamePolicy",
        policy => policy.Requirements.Add(new UserNameRequirement("admin", ** want to resolve and inject my DbContext here **)));
}

I am not sure how to achieve this. I've seen this post which is a similar question but it's using ASP.NET 5 and that syntax doesn't work with .net core:

Dependency Injection on AuthorizationOptions


Solution

  • OK, I'm going to make an assumption here, and that is that you need to inject an instance of MyDbContext in UserNameRequirement to perform the business logic.

    If this is the case, then it means UserNameRequirement both holds the data - in your case the username - and does the authorization logic. An example of this in ASP.NET Core is the ClaimsAuthorizationRequirement.

    The solution to this is to separate this into two classes - on one side the requirement that just holds the data associated with the requirement, on on the other side the authorization handler. As a note, even if we'll go through it, what I'm describing is available in the official ASP.NET Core docs.

    So the requirement class could look something like:

    public class UserNameRequirement : IAuthorizationRequirement
    {
        public UserNameRequirement(string userName)
        {
            UserName = userName;
        }
    
        public string UserName { get; }
    }
    

    and the handler class would be:

    public class UserNameRequirementHandler : AuthorizationHandler<UserNameRequirement>
    {
        private readonly MyDbContext _dbContext;
    
        public UserNameRequirementHandler(MyDbContext dbContext)
        {
            _dbContext = dbContext;
        }
    
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UserNameRequirement requirement)
        {
            var userName = requirement.UserName;
    
            // Use _dbContext to perform business logic
        }
    }
    

    The next and last part is to register the handler in the container:

    services.AddSingleton<IAuthorizationHandler, UserNameRequirementHandler>();
    

    The effect of doing this is that you can now add your requirement to the policy without worrying about the DbContext:

    services.AddAuthorization(options =>
    {
        options.AddPolicy(
            "UserNamePolicy",
            policy => policy.Requirements.Add(new UserNameRequirement("admin")));
    }
    

    Internally, ASP.NET will then resolve all the handlers associated with that requirement through the container, so the instance of MyDbContext will be available to you in the handler, allowing you to perform the business logic as you see fit.

    Hopefully, my assumption is correct, and this helps you.

    Edit:

    Henry Roux made a good point in a comment below regarding the fact that if the UserNameRequirementHandler is registered as a singleton, then a single instance of MyDbContext will be used, and that could lead to issues. Make sure you register your authorization handlers with the appropriate lifecycle.