如何在后台任务中获取 IHttpContextAccessor 实例(或等效实例)?

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

在我的 ASP.Net Core 3.1 webapi 中,我将 IHttpContextAccessor 注册为单例并将其注入到我所有的控制器中。我有一个接口,它也被注入到我所有的控制器和我的服务中(它们又连接到数据库)。执行是:

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;
    }
}

服务的构造函数如下:

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

只要我在请求上下文中,上述所有操作都会按预期工作。

我有一个控制器操作,它使用带有后台队列的新 IHostedService 实现来启动后台任务,它是这样启动的:

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

其中 scopeILifetimeScopehubConextIHubContext<MyHub, IMyHub>provider 变量是注入控制器 ctor 的 IPrincipalProvider

问题是,当我尝试在任务中解决 IMyService 时,它会创建一个 IPrincipalProvider 的实例,而该实例又需要 IHttpContextAccessor,但它不再存在。

这种情况的解决方法是什么?我是否需要在服务上有第二个具有不同 IPrincipalProvider 的 ctor,它从其他地方获取上下文?如果是这样,从哪里来?

最好的解决方案是有 2 个 IPrincipalProvider 的实现,一个使用 httpContextAccessor,另一个使用其他东西。不幸的是,拥有其他实现并不总是那么容易。

当您创建子 lifetimeScope 时,您可以向该子生命周期范围添加注册。您可以在这里注册一个StaticPrincipalProvider

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

你现在要做的就是想办法在任务出队的时候拿到对应的principal。为此,您可以更改 BackgroundTaskQueue 的实现以使用 ConcurrentQueue<WorkItem> 而不是 ConcurrentQueue<Func<ILifetimeScope, CancellationToken, Task>>,其中 WorkItem

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; }
}

并且因为 BackgroundTaskQueue 是用请求范围实例化的,您将有权访问当前主体。