简体   繁体   中英

.Net Core Hosted Service Requires HttpContext

EDIT Summary

I have a background service, which requires a DBContext, my DbContext is dependent on the HttPContext, because it uses the UserClaims to filter the context. The HttpContext which is null when requesting the DbContext from BackgroundService, (this is by design because there is no WebRequest associated with a BackgroundService).

How can I capture and moq the HTTPContext, so my user claims will carry over to the background service?

I'm writing a hosted service to queue pushing messages from my Hubs in Signalr. My Background.

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;
    }
}

I'm queue work items that require my DbContext, My Db Context automatically filters data based off the logged in user, So it requires access to the HTTPContextAccessor.

When I Queue up an Item that requires my DbContext, I get an error the the HTTPContext is null, which makes sense because, I'm running in a background process.

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
});

My DBContext uses the HTTPContextAccessor to filter data:

public CustomDbContext(DbContextOptions options, IHttpContextAccessor httpContextAccessor)
{
}

Is there a way I can capture the HTTPContext with each Task, or perhaps Moq one and capture the UserClaims, and replace them in the scope for the BackgroundService?

How can I use services dependent on HTTPContext from my background services.

My DbContext is dependent on the HttpContext, because it uses the UserClaims to filter the context.

That already sounds like a bad design. Your database context should only worry about providing database access. I would argue that filtering based on user permissions there is already too much responsibility for the database context, and that you should move that up into another layer. But even then, you should probably try not to rely on the HTTP context then, especially when you plan to use it from somewhere that executes outside of a HTTP context. Instead, consider explicitly passing in the user or user claims to the called methods. That way, you are not introducing implicit dependencies of the user that is hidden inside of the context.

As for mocking the context, you don't really need to mock it with a mocking library like Moq. You can just create a DefaultHttpContext and set the user inside, eg:

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

But being able to create the HTTP context will not help you provide the context to your database context: You cannot replace registered services after the service provider has been created, so you would have to either set the context on the HttpContextAccessor —which I would strongly advise against as this might not be safe to use inside your background service—, or create your database context explicitly—which I would also advise against since that will defeat the purposes of DI.

So the “right” way would probably to rethink your architecture so that you do not need to rely on the HTTP context, and ideally not even on the user. Because after all, your background service is not running inside the scope of a HTTP context, so it is also not running inside the scope of a single user.

Generally also remember that the database contexts are scoped dependencies, so you should not resolve them outside of a dependency scope anyway. If you need the database context in your background service, you should explicitly open a dependency scope first using the IServiceScopeFactory .

You didn't share your configuration, so you will have to forgive me, as I am going to guess here...

It's possible that you are getting a null value for the IHttpContextAccessor due to some dependency injection issues.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); // this
}

Actually, I believe that you could also achieve the exact same thing by doing

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddHttpContextAccessor(); // does pretty much the same as above
}

the source for that extension is here

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM