用户可以通过向 ASP.NET Core 控制器发送请求来触发长时间运行的作业。当前,控制器执行作业,然后发送 200 OK 响应。问题是客户端必须等待相当长的时间才能得到响应。
这就是为什么我目前正在尝试在后台任务中处理该作业。我正在使用IBackgroundTaskQueue
存储所有作业的位置,以及IHostedService
每当有新作业入队时处理作业的位置。它类似于Microsoft 文档中的代码。
但该作业确实需要访问数据库,因此用户必须使用 Active Directory 进行身份验证。因此,我需要访问HttpContext.User
后台任务中的属性。不幸的是,在HttpContext
发送响应时以及在作业处理开始之前,它就被处理掉了。
示范
public class Job
{
public Job(string message)
{
Message = message;
}
public string Message { get; }
}
控制器将新作业排入任务队列。
[HttpGet]
public IActionResult EnqueueJob()
{
var job = new Job("Hello World");
this.taskQueue.QueueBackgroundWorkItem(job);
return Accepted();
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private ConcurrentQueue<Job> jobs = new ConcurrentQueue<Job>();
private SemaphoreSlim signal = new SemaphoreSlim(0);
public void QueueBackgroundWorkItem(Job job)
{
jobs.Enqueue(job);
signal.Release();
}
public async Task<Job> DequeueAsync(CancellationToken cancellationToken)
{
await signal.WaitAsync(cancellationToken);
jobs.TryDequeue(out var job);
return job;
}
}
为它出列的每个作业IHostedService
创建一个新的。JobRunner
我在IServiceScopeFactory
这里使用一个可用的依赖注入。JobRunner
在实际代码中也有更多的依赖关系。
public class JobRunnerService : BackgroundService
{
private readonly IServiceScopeFactory serviceScopeFactory;
private readonly IBackgroundTaskQueue taskQueue;
public JobRunnerService(IServiceScopeFactory serviceScopeFactory, IBackgroundTaskQueue taskQueue)
{
this.serviceScopeFactory = serviceScopeFactory;
this.taskQueue = taskQueue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (stoppingToken.IsCancellationRequested == false)
{
var job = await taskQueue.DequeueAsync(stoppingToken);
using (var scope = serviceScopeFactory.CreateScope())
{
var serviceProvider = scope.ServiceProvider;
var runner = serviceProvider.GetRequiredService<JobRunner>();
runner.Run(job);
}
}
}
}
public class JobRunner
{
private readonly ILogger<JobRunner> logger;
private readonly IIdentityProvider identityProvider;
public JobRunner(ILogger<JobRunner> logger, IIdentityProvider identityProvider)
{
this.logger = logger;
this.identityProvider= identityProvider;
}
public void Run(Job job)
{
var principal = identityProvider.GetUserName();
logger.LogInformation($"{principal} started a new job. Message: {job.Message}");
}
}
public class IdentityProvider : IIdentityProvider
{
private readonly IHttpContextAccessor httpContextAccessor;
public IdentityProvider(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public string GetUserName()
=> httpContextAccessor.HttpContext.User.Identity.Name; // throws NullReferenceException
}
现在,在发送请求时,由于为空,所以NullReferenceException
会抛出a。JobRunner.Run()
httpContextAccessor.HttpContext
我试过的
我还没有一个好主意如何解决这个问题。我知道可以从 复制必要的信息HttpContext
,但不知道如何使它们可用于依赖注入服务。
我想也许我可以创建一个IServiceProvider
使用旧服务的新服务,但替换 的实现IHttpContextAccesor
,但这似乎是不可能的。
HttpContext
尽管响应已经完成,如何在后台任务中使用?