Why Is Httpcontext.Current Null After Await

Why is HttpContext.Current null after await?

Please ensure you are writing an ASP.NET 4.5 application, and targeting 4.5. async and await have undefined behavior on ASP.NET unless you are running on 4.5 and are using the new "task-friendly" synchronization context.

In particular, this means you must either:

  • Set httpRuntime.targetFramework to 4.5, or
  • In your appSettings, set aspnet:UseTaskFriendlySynchronizationContext to true.

More information is available here.

HttpContext is null after await in in ASP.NET 4.8

ConfigureAwait(false) means "don't resume on the captured context". By specifying ConfigureAwait(false), your code is telling the runtime that it doesn't need the ASP.NET request context. But your code does need the ASP.NET request context, since it depends on HttpContext.Current. So using ConfigureAwait(false) here is wrong.

If I don't use ConfigureAwait(false) the code will not run.

This is likely because your code is blocking further up the call stack, causing a deadlock. The ideal solution is to remove the blocking - i.e., use async all the way. Using async all the way is preferable to blocking with ConfigureAwait(false).

However, there are a handful of scenarios where this isn't possible. For example, ASP.NET 4.8 doesn't have proper asynchronous support for MVC action filters or child actions. If you're doing something like this, then you have a couple of options:

  • Make it synchronous all the way instead of async all the way.
  • Keep the blocking-over-async-with-ConfigureAwait(false) antipattern but copy out everything your code needs from HttpContext.Current first and pass that data as explicit parameters so the code no longer has a dependency on the ASP.NET request context.

Why is System.Web.HttpContext.Current null after awaiting an async method in a controller action?

OK, I found this answer on a similar question. Apparently this behaviour was caused by the "quirks mode" of ASP.NET. :-(

Adding the following to my web.config fixed the problem by ensuring that the code after the await was executed on the same thread, and so HttpContext.Current was non-null.

<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true"/>

HttpContext.Current null in async after await calls

Remove your .ConfigureAwait(false) calls. They are what causing you trouble. When you call .ConfigureAwait(false), it tells C# that you don't care which thread to use for the completion of async call. You end up in another thread that has doesn't have HttpContext.Current context, since it's a thread pool thread, not ASP.NET thread.

HTTPContext.Current in controller is null after multiple awaits

In blogs msdn there is a full research regarding this issue and a mapping of the problem, I.E to the root of the trouble:

The HttpContext object stores all the request-relevant data, including
the pointers to the native IIS request object, the ASP.NET pipeline
instance, the Request and Response properties, the Session (and many
others). Without all of this information, we can safely tell our code
is unaware of the request context it is executing into. Designing
entirely stateless web applications is not easy and implementing them
is indeed a challenging task. Moreover, web applications are rich of
third party libraries, which are generally black boxes where
unspecified code runs.

It is a very interesting thing to ponder about, how such a crucial and fundamental object in the program, HttpContext is lost during the request execution.


The soultion provided:

It consists of TaskScheduler, the ExecutionContext and the SynchronizationContext:

  • The TaskScheduler is exactly what you would expect. A scheduler for tasks! (I would be a great teacher!) Depending on the type of .NET

    application used, a specific task scheduler might be better than

    others. ASP.NET uses the ThreadPoolTaskScheduler by default, which is
    optimized for throughput and parallel background processing.
  • The ExecutionContext (EC) is again somehow similar to what the name suggests. You can look at it as a substitute of the TLS (thread local
    storage) for multithreaded parallel execution. In extreme synthesis,

    it is the object used to persist all the environmental context needed
    for the code to run and it guarantees that a method can be

    interrupted and resumed on different threads without harm (both from

    a logical and security perspective). The key aspect to understand is

    the EC needs to "flow" (essentially, be copied over from a thread to

    another) whenever a code interrupt/resume occurs.
  • The SynchronizationContext (SC) is instead somewhat more difficult to grasp. It is related and in some ways similar to the EC, albeit
    enforcing a higher layer of abstraction. Indeed it can persist

    environmental state, but it has dedicated implementations for

    queueing/dequeuing work items in specific environments. Thanks to the
    SC, a developer can write code without bothering about how the

    runtime handles the async/await patterns.

If you consider the code from the blog's example:

   await DoStuff(doSleep, configAwait)
.ConfigureAwait(configAwait);

await Task.Factory.StartNew(
async () => await DoStuff(doSleep, configAwait)
.ConfigureAwait(configAwait),
System.Threading.CancellationToken.None,
asyncContinue ? TaskCreationOptions.RunContinuationsAsynchronously : TaskCreationOptions.None,
tsFromSyncContext ? TaskScheduler.FromCurrentSynchronizationContext() : TaskScheduler.Current)
.Unwrap().ConfigureAwait(configAwait);

The explenation:

  • configAwait: controls the ConfigureAwait behavior when awaiting tasks (read on for additional considerations)
  • tsFromSyncContext: controls the TaskScheduler option passed to the StartNew method. If true, the TaskScheduler is built from the current
    SynchronizationContext, otherwise the Current TaskScheduler is used.
  • doSleep: if True, DoStuff awaits on a Thread.Sleep. If False, it awaits on a HttpClient.GetAsync operation Useful if you want to test

    it without internet connection
  • asyncContinue: controls the TaskCreationOptions passed to the StartNew method. If true, the continuations are run asynchronously.

    Useful if you plan to test continuation tasks too and to assess the

    behavior of task inlining in case of nested awaiting operations

    (doesn't affect LegacyASPNETSynchronizationContext)

Dive into the article and see if it matches your issue, I believe you will find useful info inside.


There is another solution here, using nested container, you can check it as well.

Why HttpContext.Current is not null in async/await with ConfigureAwait

Though it is much more complex than that, you can picture await as a kind of ContinueWith. So if you write for instance:

DoSomeStuff();
await WaitAsync()
DoMoreStuff();

It is rewritten to:

DoSomeStuff();
WaitAsync().ContinueWith(_ => DoMoreStuff());

.ConfigureAwait sets the context in which the continuation will execute. With ConfigureAwait(true) (the default), the continuation will execute in the same context as the caller. With ConfigureAwait(false), the continuation will execute in the default invariant context, on the threadpool.
With our previous simplification, let's imagine ConfigureAwait(true) will be rewritten to ContinueWithSameContext and ConfigureAwait(false) to ContinueWithThreadPool.

Now what happens if we have nested methods? For instance, your code:

public async Task WaitAsync()
{
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);

var httpContext = System.Web.HttpContext.Current; // null, OK
}

public async Task<ActionResult> Index()
{
var class1 = new MyClass();
await class1.WaitAsync();

var httpContext = System.Web.HttpContext.Current; // not null, WHY???

return View("Index");
}

This is rewritten too:

public Task WaitAsync()
{
return Task.Delay(TimeSpan.FromSeconds(1))
.ContinueWithThreadPool(_ =>
{
var httpContext = System.Web.HttpContext.Current; // null, OK
});
}

public Task<ActionResult> Index()
{
var class1 = new MyClass();
return class1.WaitAsync().ContinueWithSameContext(_ =>
{
var httpContext = System.Web.HttpContext.Current; // not null, WHY???

return View("Index");
}
}

Rewritten this way, you see that the continuation of WaitAsync will run on the same context as Task<ActionResult> Index(), explaining why the HttpContext isn't null.

HttpContext.Current is null after await (only in unit tests )

HttpContext.Current is considered a pretty terrible property to work with; it doesn't behave itself outside of its ASP.NET home. The best way to fix your code is to stop looking at this property and find a way to isolate it from the code you are testing. For example, you could create an interface which represents your current session's data and expose that interface to the component you are testing, with an implementation that requires an HTTP context.


The root problem is to do with how the HttpContext.Current works. This property is "magical" in the ASP.NET framework, in that it is unique for a request-response operation, but jumps between threads as the execution requires it to - it is selectively shared between threads.

When you use HttpContext.Current outside of the ASP.NET processing pipeline, the magic goes away. When you switch threads like you are here with the asynchronous programming style, the property is null after continuing.

If you absolutely cannot change your code to remove the hard dependency on HttpContext.Current, you can cheat this test by leveraging your local context: all the variables in local scope when you declare a continuation are made available for the context of the continuation.

// Bring the current value into local scope.
var context = System.Web.HttpContext.Current;

var httpSessionStateBefore = context.Session;
var person = await Db.Persons.FirstOrDefaultAsync();
var httpSessionStateAfter = context.Session;

To be clear, this will only work for your current scenario. If you introduce an await ahead of this in another scope, the code will suddenly break again; this is the quick-and-dirty answer that I encourage you to ignore and pursue a more robust solution.



Related Topics



Leave a reply



Submit