Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.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.

like image 245
johnny 5 Avatar asked Jul 01 '18 17:07

johnny 5


People also ask

How do I get HttpContext in .NET Core?

In ASP.NET Core, if we need to access the HttpContext in service, we can do so with the help of IHttpContextAccessor interface and its default implementation of HttpContextAccessor. It's only necessary to add this dependency if we want to access HttpContext in service.

What is HttpContext in .NET Core?

HttpContext encapsulates all information about an individual HTTP request and response. An HttpContext instance is initialized when an HTTP request is received. The HttpContext instance is accessible by middleware and app frameworks such as Web API controllers, Razor Pages, SignalR, gRPC, and more.

What is HttpContext in asp net c#?

The HttpContext encapsulates all the HTTP-specific information about a single HTTP request. When an HTTP request arrives at the server, the server processes the request and builds an HttpContext object. This object represents the request which your application code can use to create the response.


1 Answers

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, e.g.:

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.

like image 54
poke Avatar answered Sep 21 '22 08:09

poke