响应完成后在后台任务中使用 HttpContext

Flo*_*gex 5 c# asp.net-core asp.net-core-hosted-services

用户可以通过向 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; }
}
Run Code Online (Sandbox Code Playgroud)

控制器将新作业排入任务队列。

[HttpGet]
public IActionResult EnqueueJob()
{
    var job = new Job("Hello World");
    this.taskQueue.QueueBackgroundWorkItem(job);
    return Accepted();
}
Run Code Online (Sandbox Code Playgroud)
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;
    }
}
Run Code Online (Sandbox Code Playgroud)

IHostedService创建一个新JobRunner的它会转移每个作业。我正在使用IServiceScopeFactoryhere 来提供依赖注入。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);
            }

        }
    }
}
Run Code Online (Sandbox Code Playgroud)
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}");
    }
}
Run Code Online (Sandbox Code Playgroud)
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
}
Run Code Online (Sandbox Code Playgroud)

现在,当发送请求时, aNullReferenceException被抛出,JobRunner.Run()因为它httpContextAccessor.HttpContext是空的。

我试过的

我还没有一个好主意如何解决这个问题。我知道可以从 复制必要的信息HttpContext,但不知道如何将它们提供给依赖注入服务。

我想也许我可以创建一个IServiceProvider使用旧服务的新服务,但替换 的实现IHttpContextAccesor,但似乎不可能。


HttpContext尽管响应已完成,我如何在后台任务中使用?