Using HttpContext.Current in WebApi is dangerous because of async

Lukas K picture Lukas K · Jul 25, 2014 · Viewed 47.3k times · Source

My question is a bit related to this: WebApi equivalent for HttpContext.Items with Dependency Injection.

We want to inject a class using HttpContext.Current in WebApi area using Ninject.

My concern is, this could be very dangerous, as in WebApi (everything?) is async.

Please correct me if I am wrong in these points, this is what I investigated so far:

  1. HttpContext.Current gets the current context by Thread (I looked into the implementation directly).

  2. Using HttpContext.Current inside of async Task is not possible, because it can run on another Thread.

  3. WebApi uses IHttpController with method Task<HttpResponseMessage> ExecuteAsync => every request is async => you cannot use HttpContext.Current inside of action method. It could even happen, more Request are executed on the same thread by coicidence.

  4. For creating controllers with injected stuff into constructors IHttpControllerActivator is used with sync method IHttpController Create. This is, where ninject creates Controller with all its dependencies.


  • If I am correct in all of these 4 points, using of HttpContext.Current inside of an action method or any layer below is very dangerous and can have unexpected results. I saw on StackOverflow lot of accepted answers suggesting exactly this. In my opinion this can work for a while, but will fail under load.

  • But when using DI to create a Controller and its dependencies, it is Ok, because this runs on one separated thread. I could get a value from the HttpContext in the constructor and it would be safe?. I wonder if each Controller is created on single thread for every request, as this could cause problem under heavy loads, where all threads from IIS could be consumed.

Just to explain why I want to inject HttpContext stuff:

  • one solution would be to get the request in controller action method and pass the needed value all the layers as param until its used somewhere deep in the code.
  • our wanted solution: all the layers between are not affected by this, and we can use the injected request somewhere deep in code (e.g. in some ConfigurationProvider which is dependent on URL)

Please give me your opinion if I am totally wrong or my suggestions are correct, as this theme seems to be very complicated.

Answer

Stephen Cleary picture Stephen Cleary · Jul 26, 2014

HttpContext.Current gets the current context by Thread (I looked into the implementation directly).

It would be more correct to say that HttpContext is applied to a thread; or a thread "enters" the HttpContext.

Using HttpContext.Current inside of async Task is not possible, because it can run on another Thread.

Not at all; the default behavior of async/await will resume on an arbitrary thread, but that thread will enter the request context before resuming your async method.


The key to this is the SynchronizationContext. I have an MSDN article on the subject if you're not familiar with it. A SynchronizationContext defines a "context" for a platform, with the common ones being UI contexts (WPF, WinPhone, WinForms, etc), the thread pool context, and the ASP.NET request context.

The ASP.NET request context manages HttpContext.Current as well as a few other things such as culture and security. The UI contexts are all tightly associated with a single thread (the UI thread), but the ASP.NET request context is not tied to a specific thread. It will, however, only allow one thread in the request context at a time.

The other part of the solution is how async and await work. I have an async intro on my blog that describes their behavior. In summary, await by default will capture the current context (which is SynchronizationContext.Current unless it is null), and use that context to resume the async method. So, await is automatically capturing the ASP.NET SynchronizationContext and will resume the async method within that request context (thus preserving culture, security, and HttpContext.Current).

If you await ConfigureAwait(false), then you're explicitly telling await to not capture the context.

Note that ASP.NET did have to change its SynchronizationContext to work cleanly with async/await. You have to ensure that the application is compiled against .NET 4.5 and also explicitly targets 4.5 in its web.config; this is the default for new ASP.NET 4.5 projects but must be explicitly set if you upgraded an existing project from ASP.NET 4.0 or earlier.

You can ensure these settings are correct by executing your application against .NET 4.5 and observing SynchronizationContext.Current. If it is AspNetSynchronizationContext, then you're good; if it's LegacyAspNetSynchronizationContext, then the settings are wrong.

As long as the settings are correct (and you are using the ASP.NET 4.5 AspNetSynchronizationContext), then you can safely use HttpContext.Current after an await without worrying about it.