Search code examples
c#asp.net-coreautofac

How to get an IHttpContextAccessor instance (or equivalent) in a background task?


In my ASP.Net Core 3.1 webapi, I'm registering the IHttpContextAccessor as a singleton and injecting it into all my controllers. I have an interface that also gets injected into all my controllers and my services (which in turn connect to the db). The implementation is:

public class PrincipalProvider : IPrincipalProvider
{
    private readonly UserPrincipal principal;

    public PrincipalProvider(IHttpContextAccessor accessor)
    {
        accessor.HttpContext.Items.TryGetValue("principal", out object principal);
        this.principal = principal as UserPrincipal;
    }

    public UserPrincipal GetPrincipal()
    {
        return principal;
    }
}

The ctor of a service looks like:

    public MyService(
        IPrincipalProvider provider,
        ILogger<MyService> logger, 
        IUnitOfWork unitOfWork) : base(provider, logger, unitOfWork) 
    { }

All the above works as expected as long as I'm within the request context.

I have a controller action that starts a background task using the new IHostedService implementation with a background queue, and it gets started like this:

backgroundQueue.QueueBackgroundWorkItem(async (scope, hubContext, ct) =>
{
    await hubContext.Clients.Client(provider.GetPrincipal().ConnectionId).Notify();
    var myService = scope.Resolve<IMyService>();
}

where scope is ILifetimeScope and hubConext is IHubContext<MyHub, IMyHub>. The provider variable is the IPrincipalProvider that was injected into the controller ctor.

The problem is that when I try to resolve IMyService within the task, it creates an instance of IPrincipalProvider and that in turn requires IHttpContextAccessor which doesn't exist anymore.

What is the solution in this case? Do I need to have a second ctor on the service with a different IPrincipalProvider which gets the context from somewhere else? And if that's the case, from where?


Solution

  • The nicest solution would be to have 2 implementations of IPrincipalProvider, the one that use the httpContextAccessor and another one that use something else. Unfortunately it is not always easy to have the other implementation.

    When you create the child lifetimeScope you can add registration to this child lifetime scope. You can register a StaticPrincipalProvider here.

     private async Task BackgroundProcessing(...) {
        ... 
        try {
            using(ILifetimeScope queueScope = this._rootScope.BeginLifetimeScope(builder => {
                builder.RegisterInstance(new StaticPrincipalProvider(principal))
                       .As<IPrincipalProvider>();
            })){
                await workItem(queueScope, stoppingToken);
            }
        }
        ...
     }
    

    All you have to do now is to find a way to get the corresponding principal when you dequeue the task. To do this you can change the implementation of BackgroundTaskQueue to use a ConcurrentQueue<WorkItem> instead of ConcurrentQueue<Func<ILifetimeScope, CancellationToken, Task>> where WorkItem is

    public class WorkItem {
        public Func<ILifetimeScope, CancellationToken, Task> Work { get; private set; }
        public IPrincipal Principal { get; private set; }
        // or
        public Action<ContainerBuilder> builderAccessor { get; private set; }
    }
    

    and because BackgroundTaskQueue is instanciated with a request scope you will have access to the current principal.