.Net Core Hosted Service Requires HttpContext

Multi tool use
.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.
@Nkosi my dbContext takes in the httpcontextaccessor, How can I replace the httpcontextaccessor in that scope?
– johnny 5
Jul 1 at 17:59
Ah. I did not know about that. (assuming for auditing or something?) Can you include that little nugget. should help me see the bigger picture.
– Nkosi
Jul 1 at 18:01
I’ll make an edit right now
– johnny 5
Jul 1 at 18:02
You have tagged this question with
asp.net-core
but it's not clear to me where the request is handled in relation to your code. Could you help explain that part a bit more?– Svek
Jul 1 at 18:05
asp.net-core
2 Answers
2
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.:
DefaultHttpContext
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.
HttpContextAccessor
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
.
IServiceScopeFactory
In this example I’ve made poor architecture, but let’s assume there was an example where I require a user specific dependency from the background service. What I took away from your answer, is that I should minimize the my dependency to be a custom object instead of httpcontextaccessor. e.g ContextUserClaims, and override that implementation from the service container before requesting?
– johnny 5
Jul 1 at 20:51
If you have a user-specific context, then you apparently communicate with your background service from that user-specific context (e.g. by calling a method on the background service from a controller, or by sending some message), then that should be where you collect the user specific dependencies and pass it explicitly to the background service. E.g.
_bgService.DoSomethingForUser(new SomethingContext { UserId = User.GetUserId() })
. So your background service does not depend on it globally but some individual method or message requires that information to be passed.– poke
Jul 1 at 20:58
_bgService.DoSomethingForUser(new SomethingContext { UserId = User.GetUserId() })
Cool, I’ll mark this as correct, and I’ll post an answer later of how I resolve this specifically
– johnny 5
Jul 1 at 21:00
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.
null
IHttpContextAccessor
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
Sorry, I should have been more clear, The issue is that there is no HttpContext for a background Service, because there was no web request made, this is a new feature for .Net-Core which allows you to run tasks in the background. I'm wondering how to get around the issue, if I have dependencies which require the HTTP Context
– johnny 5
Jul 1 at 18:38
If
IHttpContextAccessor
is not registered, you will usually get an exception during object construction that the type cannot be resolved. You won’t get null
with DI for required dependencies.– poke
Jul 1 at 19:31
IHttpContextAccessor
null
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
Why not hold on to the User principle when creating the task?
– Nkosi
Jul 1 at 17:39