Search code examples
c#asp.net-corehttpcontextstatic-classes

Access to HttpContext via static class works "correctly" with different requests


I found this article while trying to solve a problem that requires some headers in non-controllers.
I am quite skeptical about the approach and the author is not responding. My major concern is about the approach of having global static HttpContext. I was thinking that it should not work with two requests. An example of this case is below (together with the approach presented in the article I mentioned):

public static class AppContext
{
    public static IHttpContextAccessor HttpContextAccessor { get; set; }
    public static void Configure(IHttpContextAccessor accessor)
    {
        HttpContextAccessor = accessor;
    }
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env,
    IHttpContextAccessor contextAccessor)
{
    AppContext.Configure(contextAccessor);
    ...
}

[Route("api/[controller]")]
[ApiController]
public class ExampleController : ControllerBase
{
    [HttpGet("{number}")]
    public IActionResult Example(int number)
    {
        if (number == 1)
        {
            Thread.Sleep(10000);
        }

        var result = AppContext.HttpContextAccessor.HttpContext.Request.GetDisplayUrl();

        return Ok(result + " " + number);
    }
}

Want to mention that author uses name AppContext for this static class and that is exactly what I'd expect (and it is indeed useless then).
However, what confuses me is the actual behavior. I am debugging the piece placing a breakpoint at the line with var result = .... I first send a request with number = 1 that will sleep for a bit and then I send the second request with a different value for number. I skip the placed breakpoint for the first request and wait for the first request (with number = 1) to stop there. Then I check what GetDisplayUrl() returns - and it returns a path with /1 (that is indeed the path for this request that has slept for 10 secs). I would expect it to end with /2 as the static field with IHttpContextAccessor of static class AppContext has been rewritten by the second request in ConfigureServices() method.
I believe I am missing something crucial and would be glad if you also provided some sources that I (and others confused) could use to fill the gap.
Could you also give me some more insights on using the approach? Does testability suffer (as I'm using the static class everywhere in the application) and in what way?


Solution

  • There's a few things going on here. Technically this will work, simply because IHttpContextAccessor is a singleton. Therefore, there's nothing technically wrong with persisting it on a static ivar. Either way, it lasts the life of the application.

    HttpContext, itself, is scoped, but that's not what's being set here. So, as long as you have access to IHttpContextAccessor, you can technically get access to HttpContext, though it may be null, depending on where you attempt to do it (i.e. outside the request pipeline).

    However, this is just such bad practice it's not even funny. Statics should largely be avoided for good code. They are not testable and they serve to hide dependencies, making your code harder to understand and more fragile.

    I've seen some people do something similar to this, but that was to make HttpContext itself appear as if it was static, with the goal only of supporting legacy code that assumed a static HttpContext. This solution does not help there, as you'd have to change legacy code either way. As such, it's completely useless.

    If you need access to HttpContext outside of places where it intrinsically exists such as controllers, pages, and views, then simply inject IHttpContextAccessor there, and use it directly. This whole AppContext thing is a joke and should die in fire.