【问题标题】:How to get an IHttpContextAccessor instance (or equivalent) in a background task?如何在后台任务中获取 IHttpContextAccessor 实例(或等效实例)?
【发布时间】:2020-04-02 01:47:36
【问题描述】:

在我的 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;
    }
}

服务的 ctor 看起来像:

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

其中scope 是ILifetimeScope 而hubConext 是IHubContext&lt;MyHub, IMyHub&gt;。 provider 变量是注入控制器 ctor 的 IPrincipalProvider。

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

在这种情况下,解决方案是什么?我是否需要在服务上使用另一个 IPrincipalProvider 从其他地方获取上下文的第二个 ctor?如果是这样的话,从哪里来?

【问题讨论】:

  • HttpContext 在请求开始时创建,在响应结束时销毁。后台任务独立于 HttpContext 运行,因此在其中获取 HttpContext 是不可行的。
  • 不要在后台服务中使用 IHttpContextAccessor ;)
  • 是的,我知道。那有什么选择呢?我需要一个替代方案,使我能够使用 Resolve 将主要提供者(或 ir 的变体)注入服务的 ctor)
  • 您无法在后台运行的函数之外解析 IMyService 吗?那么当 HttpContextAccessor 存在并且 UserPrinciple 保存在实例中时,IMyService 就解决了吗?你只写 var myService = theResolvedService?
  • @Darm 我尝试通过将ILifetimeScope 注入到ctor 中,然后创建一个子作用域,并将任务包装在其中。问题仍然存在,因为 DbContext 也被处理掉了。

标签: c# asp.net-core autofac


【解决方案1】:

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

当您创建子生命周期范围时,您可以向该子生命周期范围添加注册。您可以在此处注册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&lt;WorkItem&gt; 而不是ConcurrentQueue&lt;Func&lt;ILifetimeScope, CancellationToken, Task&gt;&gt;,其中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 被实例化为请求范围,您将可以访问当前主体。

【讨论】:

  • 工作项是如何用 queueScope 实例化的?
  • 我已经在 Github 上添加了当前代码:github.com/cryo75/BackgroundQueue
  • 因为它是一个 data 对象而不是服务,您可以使用 C# new 关键字对其进行实例化。有很多地方可以创建它,例如在 BackgroundTaskQueue 的 QueueBackgroundWorkItem 方法中。
  • 我仍然不明白 queueScope 是如何被传递的。唯一的方法是将Work 属性更改为Func&lt;ILifetimeScope, CancellationToken, Task&gt; Work { get; }
  • @Ivan-MarkDebono 抱歉,复制/粘贴错误。 func 应该仍然有生命周期
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-09
  • 1970-01-01
  • 1970-01-01
  • 2010-10-12
相关资源
最近更新 更多