g.pickardou
g.pickardou

Reputation: 35843

Confused about using the very same HTTPContextAccessor instance through the entire ASP.NET Core 3 application?

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

Answers (1)

galdin
galdin

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

Related Questions