5

用户可以通过向 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尽管响应已经完成,如何在后台任务中使用?

4

0 回答 0