Search code examples
c#asp.net-coreasynchronouspolicy

How to write an asynchronous Policy handler, injecting a scoped service


I'm trying to write a custom policy for an ASP.NET Core 3.1 web application, using a custom Identity storage provider.

I've tried to wrap my head around the fact that policies in ASP.NET Core are designed to take user informations from an HttpContext object, when I read this in a MSDN Article:

once you hold a reference to the user, you can always find the username from the claims and run a query against any database or external service

I started writing my own policy (as of now a simple role requirement) injecting the UserManager into the constructor:

public class RoleHandler : AuthorizationHandler<RoleRequirement>
{
    private UserManager<AppUser> UserManager;

    public RoleHandler(UserManager<AppUser> usermanager)
    {
        UserManager = usermanager;
    }
}

Now I have a couple problems:

INJECTING A SCOPED SERVICE IN A SINGLETON

Policies are supposed to be lasting for the entire application life, so that would be a Singleton:

services.AddSingleton<IAuthorizationHandler, RoleHandler>();

but the UserManager injected in the policy server is a scoped service and that is not allowed. Solution was very easy, changing the configuration of the policy service from a singleton to a scoped service

services.AddScoped<IAuthorizationHandler, RoleHandler>();

but I don't know whether that cause any issue or not.

WRITING AN ASYNCHRONOUS POLICY HANDLER

This is my implementation of the HandleRequirementAsync method:

protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
{
    AppUser user = UserManager.FindByIdAsync(context.User.Identity.Name).Result;

    if (user != null)
    {
        bool result = UserManager.IsInRoleAsync(user, requirement.Role.ToString()).Result;
        if (result) context.Succeed(requirement);
    }

    return Task.CompletedTask;
}

I used Task.Result but it blocks the thread. I can't use await because that would make the method returning a Task<Task> instead of a Task and I can't change it. How can I solve this?


Solution

  • Don't return Task.CompletedTask.

    When you declare a method as async, it implicitly returns a Task when the first await is hit:

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
    {
        AppUser user = await UserManager.FindByIdAsync(context.User.Identity.Name);
    
        if (user != null)
        {
            bool result = await UserManager.IsInRoleAsync(user, requirement.Role.ToString());
            if (result) context.Succeed(requirement);
        }
    }
    

    Task.CompletedTask is generally used when you need to implement a Task returning method synchronously, which you are not.