Accessing HttpContext outside of framework components in ASP.NET Core

Β· 995 words Β· 5 minutes to read

When developing web applications with ASP.NET, it is common to end up in situations where you require access to HttpContext. This wouldn’t be anything special, but outside of the context of framework level APIs such as controllers, middleware and so on (which would always give you a way to fetch the current HttpContext), it can be tricky.

While generally speaking, HttpContext could be passed around as a regular dependency to the logical components that require it, that solution is often impractical.

Let’s have a look at how you can get a hold of HttpContext in ASP.NET Core.

HttpContextAccessor πŸ”—

ASP.NET Core provides a convenience interface, IHttpContextAccessor (and it’s default implementation, HttpContextAccessor) in order to simplify accessing HttpContext. It must be registered at application startup inside the IServicesCollection and once it’s there, the framework will make sure that you can inject it anywhere you need, and use it to access the current instance of HttpContext.

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

HttpContextAccessor under the hood πŸ”—

So how does it work? Consider piece of code you can find in any ASP.NET Core template:

    public class Program

    {

        public static void Main(string[] args)

        {

            var host = new WebHostBuilder()

                .UseKestrel()

                .UseContentRoot(Directory.GetCurrentDirectory())

                .UseStartup<Startup>()

                .Build();



            host.Run();

        }

    }

It provides the launch point of your application. This is going to start the server and boostrap all the necessary services, including building up the request processing pipeline from your Startup class.

Internally, in the process of that bootstrapping, that code will wire in the relevant server (Kestrel) and will create an instance of HostingApplication and pass into it an implementation of IHttpContextFactory (more on that later).

HostingApplication is an implementation of IHttpApplication which exposes three methods:

    public interface IHttpApplication<TContext>

    {

        /// <summary>

        /// Create a TContext given a collection of HTTP features.

        /// </summary>

        /// 

        /// <returns>The created TContext.</returns>

        TContext CreateContext(IFeatureCollection contextFeatures);



        /// <summary>

        /// Asynchronously processes an TContext.

        /// </summary>

        /// 

        Task ProcessRequestAsync(TContext context);



        /// <summary>

        /// Dispose a given TContext.

        /// </summary>

        /// 

        /// 

        void DisposeContext(TContext context, Exception exception);

    }

The server that we are using (say, Kestrel), on each incoming request, will use the above interface to call CreateContext and later on ProcessRequestAsync.

The former method is where IHttpContextFactory will be used to initialize HttpContext instance, and that instance will live throughout the lifetime of the HTTP request. The default implementation of IHttpContextFactory will look into the DI container, and check if IHttpContextAccessor is there. If it is, then it will “share” its HttpContext instance with the accessor.

The HttpContextAccessor will then store the HttpContext using System.Runtime.Remoting.Messaging.CallContext on desktop CLR and using System.Threading.AsyncLocal when built against .NET Standard.

If the accessor is not registered in the DI, then of course the context will not be saved anywhere. This is really important - and I have seen some questions already about that. If you just manually create an instance of HttpContextAccessor (which some people try), it will have no relationship to the HttpContextFactory or HttpContext, and the context will always be null. The accessor is merely a shortcut with a getter and setter, while all the logic of associating the HttpContext with the accessor instance is in HttpContextFactory.

And that’s basically how it works.

Injecting HttpContextAccessor πŸ”—

With all that set up, we could inject IHttpContextAccessor wherever we require access to the current instance of HttpContext. This of course means that your own components that rely on it, should be registered in/resolved from the IoC container too.

public class MyService

{

    private readonly IHttpContextAccessor _accessor;



    public MyService(IHttpContextAccessor accessor) 

    {

        _accessor = accessor;

    }



    public void DoWork()

    {

        var context = _accessor.HttpContext;

        // continue with context instance

    }

}

Mimicking HttpContext.Current πŸ”—

One of the most infamous relicts of System.Web that is missing in ASP.NET Core is the static access to the current HttpContext.

I bet there is not a single ASP.NET developer, that, over the years, has not seen tons of programs, logic and extensions developed based on the magic and omnipresence of HttpContext.Current.

Now, trying to build your code around HttpContext.Current is really not a good idea, but I guess if you are migrating an enterprise type of app, with a lot of HttpContext.Current sprinkled around the business logic it may provide some temporary relief in terms of porting the application. Plus, in the past I’ve already blogged about things I don’t necessarily consider to be good ideas.

Our modern day HttpContext.Current would rely on resolving the context from IHttpContextAccessor and could look like this:

namespace System.Web

{

    public static class HttpContext

    {

        private static IHttpContextAccessor _contextAccessor;



        public static Microsoft.AspNetCore.Http.HttpContext Current => _contextAccessor.HttpContext;



        internal static void Configure(IHttpContextAccessor contextAccessor)

        {

            _contextAccessor = contextAccessor;

        }

    }

}

Notice, how we even placed it in System.Web namespace so that any potential migration you have is a bit easier.

We just need to add the code that will call into Configure as early as we can in the processing pipeline and pass in the IHttpContextAccessor. This can be achieved with two extension methods:

    public static class StaticHttpContextExtensions

    {

        public static void AddHttpContextAccessor(this IServiceCollection services)

        {

            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

        }



        public static IApplicationBuilder UseStaticHttpContext(this IApplicationBuilder app)

        {

            var httpContextAccessor = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();

            System.Web.HttpContext.Configure(httpContextAccessor);

            return app;

        }

    }

The first one would be called from within ConfigureServices in your Startup and simply register the accessor in the DI. We have already established that this is necessary for the default IHttpContextFactory to share its instance of HttpContext correctly.

The second would be called from within Configure in your Startup, and it will make sure that our custom HttpContext.Current gets fed its IHttpContextAccessor so that it can work properly too.

And that’s it. Here is my Startup class which sets up the table for the static HttpContext.Current.

    public class Startup

    {

        public void ConfigureServices(IServiceCollection services)

        {

            services.AddHttpContextAccessor();

        }



        public void Configure(IApplicationBuilder app)

        {

            app.UseStaticHttpContext();

            app.UseMvc();

        }

    }

And this is the rewritten example from above.

using System.Web;



public class MyService

{

    public void DoWork()

    {

        var context = HttpContext.Current;

        // continue with context instance

    }

}

However, please think twice before going down that road.

About


Hi! I'm Filip W., a cloud architect from ZΓΌrich πŸ‡¨πŸ‡­. I like Toronto Maple Leafs πŸ‡¨πŸ‡¦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁒󠁳󠁣󠁴󠁿.

You can find me on Github, on Mastodon and on Bluesky.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP