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
to4.5
, or - In your
appSettings
, setaspnet:UseTaskFriendlySynchronizationContext
totrue
.
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 fromHttpContext.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 connectionasyncContinue
: 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
How to Fix the Microsoft Visual Studio Error: "Package Did Not Load Correctly"
What Does Missingmanifestresourceexception Mean and How to Fix It
Transparent Images with C# Winforms
Property(With No Extra Processing) VS Public Field
How to Detect a Usb Drive Has Been Plugged In
C#: Waiting for All Threads to Complete
How to Get the Index of an Element in an Ienumerable
Make a Sphere with Equidistant Vertices
Dynamically Adding Properties to an Expandoobject
Differencebetween Directory.Enumeratefiles VS Directory.Getfiles
.Net: Determine the Type of "This" Class in Its Static Method
Access to the Path Is Denied When Using Directory.Getfiles(...)
JSON Deserialization to C# with Dynamic Keys
Solid Ffmpeg Wrapper for C#/.Net