Reputation: 35843
I've read about configuring IHttpContextAccessor
as services.AddSingleton
scope, but I also read about it is working "async local", and I am also aware of the sophisticated working of async in ASP.Net, I mean for example if a controller action method is async
, and it await
for an async
call, then it may continue with an other thread, but magically some thread bound things with maintained (like HttpContext
)
My concrete use case: I have to inject a MyConverter
class to my EF Core DbContext
which uses it in OnModelCreating
. However this model is cached by the DbContext
, so any subsequent request, even it will have a brand new instance of DbContext
will use this very same model so the very same MyConverter
instance. (even it has configured services.AddTransient
). This MyConverter
has a constructor and an injected IHttpContextAccessor
, so based on the very similar reasons, it effectively will be also a singleton for all DbContext/MyConverter
usages.
Question
This particular HttpContextAccessor
instance which is created in the very first request will serve all the subsequent requests in the Web app lifecycle. Will it work correctly? Is there any (concurrency) trap here?
(Do I suppose correctly that it does not really matter if we use a single or multiple HttpContextAccessor
instances, because its implementation of getting HttpContext
will use the correct way including async local thread switch traps etc to return with the correct HttpContext
?)
Upvotes: 6
Views: 9486
Reputation: 14034
Short answer: register as services.AddHttpContextAccessor()
and then you can inject IHttpContextAccessor
wherever you want and it'll work as long as you're using it in the request's execution context. For instance you can't read HTTP request headers for code that was not initiated by a HTTP request.
You're right that the IHttpContextAccessor
should be registered as a singleton. Instead of doing it yourself, the recommendation is to use AddHttpContextAccessor()
extension method. See source code here. It internally registers an HttpContextAccessor
as a singleton.
The code for HttpContextAccessor
can be found here, which I'm also pasting below:
public class HttpContextAccessor : IHttpContextAccessor
{
private static AsyncLocal<HttpContextHolder> _httpContextCurrent = new AsyncLocal<HttpContextHolder>();
public HttpContext HttpContext
{
get
{
return _httpContextCurrent.Value?.Context;
}
set
{
var holder = _httpContextCurrent.Value;
if (holder != null)
{
// Clear current HttpContext trapped in the AsyncLocals, as its done.
holder.Context = null;
}
if (value != null)
{
// Use an object indirection to hold the HttpContext in the AsyncLocal,
// so it can be cleared in all ExecutionContexts when its cleared.
_httpContextCurrent.Value = new HttpContextHolder { Context = value };
}
}
}
private class HttpContextHolder
{
public HttpContext Context;
}
}
Since the HttpContext
getter property returns from an async local field, you always get the HttpContext
local to the execution context.
The HttpContext
field is set in HttpContextFactory.Create()
only if IHttpContextAccessor
was registered with the DI. Source.
And HttpContextFactory.Create()
is invoked from [HostingApplication](https://github.com/aspnet/AspNetCore/blob/v2.2.5/src/Hosting/Hosting/src/Internal/HostingApplication.cs)
where the context is setup.
Upvotes: 18