.Net Core Hosted Service需要HttpContext

joh*_*y 5 3 c# dependency-injection background-process signalr asp.net-core

编辑摘要

我有一个后台服务,它需要一个DBContext,我的DbContext依赖于HttPContext,因为它使用UserClaims来过滤上下文.HttpContext在从BackgroundService请求DbContext时为null(这是设计原因,因为没有与BackgroundService关联的WebRequest).

我如何捕获和moq HTTPContext,所以我的用户声称将转移到后台服务?

我正在编写一个托管服务来排队从Signalr中的中心推送消息.我的背景.

public interface IBackgroundTaskQueue
{
    void QueueBackgroundWorkItem(Func<IServiceProvider, CancellationToken, Task> workItem);
    Task<Func<IServiceProvider, CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken);
}

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private ConcurrentQueue<Func<IServiceProvider, CancellationToken, Task>> _workItems = new ConcurrentQueue<Func<IServiceProvider, CancellationToken, Task>>();
    private SemaphoreSlim _signal = new SemaphoreSlim(0);

    public void QueueBackgroundWorkItem(Func<IServiceProvider, CancellationToken, Task> workItem)
    {
        if (workItem == null)
        {
            throw new ArgumentNullException(nameof(workItem));
        }

        this._workItems.Enqueue(workItem);
        this._signal.Release();
    }

    public async Task<Func<IServiceProvider, CancellationToken, Task>> DequeueAsync(
        CancellationToken cancellationToken)
    {
        await this._signal.WaitAsync(cancellationToken);
        this._workItems.TryDequeue(out var workItem);

        return workItem;
    }
}
Run Code Online (Sandbox Code Playgroud)

我正在排队需要我的DbContext的工作项,My Db Context根据登录用户自动过滤数据,因此它需要访问HTTPContextAccessor.

当我对需要我的DbContext的项进行排队时,我得到一个错误,HTTPContext为null,这是有道理的,因为我正在后台进程中运行.

var backgroundTask = sp.GetRequiredService<IBackgroundTaskQueue>();

backgroundTask.QueueBackgroundWorkItem((isp, ct) => {
    //When this line is executed on the background task it throws
    var context = sp.GetService<CustomDbContext>(); 
    //... Do work with context
});
Run Code Online (Sandbox Code Playgroud)

我的DBContext使用HTTPContextAccessor来过滤数据:

public CustomDbContext(DbContextOptions options, IHttpContextAccessor httpContextAccessor)
{
}
Run Code Online (Sandbox Code Playgroud)

有没有办法可以捕获每个Task的HTTPContext,或者可能是Moq one捕获UserClaims,并在BackgroundService的范围内替换它们?

如何使用依赖于我的后台服务的HTTPContext的服务.

pok*_*oke 5

我的DbContext依赖于HttpContext,因为它使用UserClaims来过滤上下文.

这听起来像是一个糟糕的设计.您的数据库上下文应该只担心提供数据库访问.我认为基于用户权限的过滤已经对数据库上下文负有太多责任,并且您应该将其移动到另一层.但即便如此,您可能应该尝试不依赖于HTTP上下文,尤其是当您计划从在HTTP上下文之外执行的某个地方使用它时.相反,请考虑明确地将用户或用户声明传递给被调用的方法.这样,您就不会引入隐藏在上下文中的用户的隐式依赖项.

至于模拟上下文,你真的不需要用像Moq这样的模拟库来模拟它.你可以创建一个DefaultHttpContext并在里面设置用户,例如:

var context = new DefaultHttpContext()
{
    User = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
    {
        new Claim(ClaimTypes.Name, "Foo"),
    })),
};
Run Code Online (Sandbox Code Playgroud)

但是能够创建HTTP上下文并不能帮助您为数据库上下文提供上下文:在创建服务提供者之后,您无法替换已注册的服务,因此您必须设置上下文HttpContextAccessor- 我强烈建议反对因为在后台服务中使用可能不安全,或者明确地创建数据库上下文 - 我也建议反对,因为这会破坏DI的目的.

因此,"正确"的方式可能会重新考虑您的体系结构,这样您就不需要依赖HTTP上下文,理想情况下甚至不需要依赖用户.因为毕竟,您的后台服务没有在HTTP上下文的范围内运行,因此它也不在单个用户的范围内运行.

通常还要记住数据库上下文是作用域依赖项,因此无论如何都不应在依赖项作用域之外解析它们.如果您需要后台服务中的数据库上下文,则应首先使用以下命令显式打开依赖项作用域IServiceScopeFactory.