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?
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.