Don't use HttpContext.Current, especially when using async
Tags: dotnet, aspnet, csharp
Published: 2021-04-01 14:51:19

---
title: Don't use HttpContext.Current, especially when using async
published: true
date: 2021-03-25 00:00:00 UTC
tags: dotnet, aspnet, csharp
canonical_url: https://swimburger.net/blog/dotnet/don-t-use-httpcontext-current-especially-when-using-async
---

`HttpContext` holds on to all the information regarding the current HTTP request. It has a lot of properties but is most commonly used to get the Request, Response, Session, User, Cache, and more. In ASP.NET WebForms most of the same properties are conveniently provided to you on the `Page` class. The `HttpContext` is also available using the `Context` property on the `Page` class.  
The same goes for MVC controllers. When possible always use the properties made available to you through the `Page` or `Controller`.

If for some reason you don't have access to the `HttpContext` or the properties on the context, you can use static `HttpContext.Current` which will return the `HttpContext` for the current HTTP request. This can be very useful, but avoid relying on static state like this, especially when writing asynchronous code or you may run into the following issue in the future.

## Static session wrapper gone wrong

The session is often wrapped by a class to avoid magic strings being used all over your application. The idea is to have const strings at the top of your class and provide typed properties wrapping the session like this:

```csharp
public static class MyStaticSessionWrapper
{
    private const string CounterKey = "MyCounter";
    private static HttpSessionState Session => HttpContext.Current.Session;

    public static int Counter
    {
        get => (int)(Session[CounterKey] ?? 0);
        set => Session[CounterKey] = value;
    }
}

```

In this example:

- A constant string is defined that will be used as the index value to store the counter in session
- A private static `Session` property returns the `Session` property from `HttpContext.Current`
- A public static `Counter` property uses the `Session` property to set and get the counter integer value. If there's no counter in session, 0 is returned.

I've seen these types of session wrappers many times before, and find them quite useful since they centralize the retrieval, storage, and avoid magic strings.  
This also works just fine in ASP.NET WebForms and MVC when using synchronous code. At my latest client, there was a session wrapping class just like this which worked fine for many years.  
Until one day `NullReferenceException`'s were thrown left and right. Unfortunately, `NullReferenceException`'s aren't always as obvious as they could be. The exceptions were being logged, but there was no way of knowing which variable or property was null.  
It wouldn't even give a line number in the stacktrace. This application was also being load balanced and experiencing session issues in the past due to misconfigured sticky sessions.   
So initially, the assumption was that certain session properties were being initialized on one server, but then fetched on a different server leading to a `NullReferenceException`.

What was actually going wrong is that the static `HttpContext.Current` was actually null which was unexpected since this had never happened over the years it was used. But this code had never been run inside an asynchronous method before, until now.

Here is an example of a controller which has two actions incrementing the counter stored in session using the above `MyStaticSessionWrapper`:

```csharp
public class HomeController : Controller
{
    public ActionResult IncrementWithStaticSession()
    {
        MyStaticSessionWrapper.Counter++;
        return View("Index");
    }

    public async Task<ActionResult> IncrementWithStaticSessionAsync()
    {
        await Task.Run(() =>
        {
            MyStaticSessionWrapper.Counter++;
        });
        return View("Index");
    }
}

```

One of the actions is incrementing the counter synchronously, and one of the actions is running the same code inside of `Task.Run`. A simple `Task.Run` is used to simulate the issue described earlier. The real code was not as clear and simple as this sample.  
The synchronous action works as expected, but the asynchronous version throws a `NullReferenceException`  because `HttpContext.Current` is null.

## Moving away from HttpContext.Current

Fixing this is relatively easy depending on your codebase. Instead of using `HttpContext.Current`, use the HttpContext provided as a property on the `Page` or `Controller`, or even better, you can simply use the `Session` property.  
You probably do want to keep using a class that wraps the session. Instead of using a static class, use a non-static class and allow the session to be passed in as part of the constructor like this:

```csharp
public class MySessionWrapper
{
    private const string CounterKey = "MyCounter";
    private readonly HttpSessionStateBase session;

    public MySessionWrapper(HttpSessionStateBase session)
    {
        this.session = session;
    }

    public int Counter
    {
        get => (int)(session[CounterKey] ?? 0);
        set => session[CounterKey] = value;
    }
}

```

And create a new instance in your controller or have a dependency injection container take care of this for you:

```csharp
public async Task<ActionResult> IncrementAsync()
{
    await Task.Run(() =>
    {
        var mySession = new MySessionWrapper(Session);
        mySession.Counter++;
    });
    return View("Index");
}

```

The above code action achieves the same as before but does not cause `NullReferenceException`'s.

Alternatively, you could add extension methods to set and get the counter on the session like this:

```csharp
public static class MySessionExtensions
{
    private const string CounterKey = "MyCounter";

    public static int GetCounter(this HttpSessionStateBase session) 
        => (int)(session[CounterKey] ?? 0);

    public static void SetCounter(this HttpSessionStateBase session, int count) 
        => session[CounterKey] = count;
}

```

Unfortunately, C# does not have extension properties yet, so for now you'll have to use the `GetX` and `SetX` convention which may not be very appealing to C# developers.  
With these extension methods, you can increment the counter asynchronously in an action like this:

```csharp
public async Task<ActionResult> IncrementWithExtensionMethodsAsync()
{
    await Task.Run(() =>
    {
        Session.SetCounter(Session.GetCounter() + 1);
    });
    return View("Index");
}

```

Once the usage of `HttpContext.Current` inside of async code was removed, all the errors were gone.