如何在后台任务中获取 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>();
}
其中 scope
是 ILifetimeScope
,hubConext
是 IHubContext<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
是用请求范围实例化的,您将有权访问当前主体。
在我的 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>();
}
其中 scope
是 ILifetimeScope
,hubConext
是 IHubContext<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
是用请求范围实例化的,您将有权访问当前主体。