Why is HttpContext.Current null after await?
Asked Answered
C

7

105

I have the following test WebAPI code, I don't use WebAPI in production but I made this because of a discussion I had on this question: WebAPI Async question

Anyways, here's the offending WebAPI method:

public async Task<string> Get(int id)
{
    var x = HttpContext.Current;
    if (x == null)
    {
        // not thrown
        throw new ArgumentException("HttpContext.Current is null");
    }

    await Task.Run(() => { Task.Delay(500); id = 3; });

    x = HttpContext.Current;
    if (x == null)
    {
        // thrown
        throw new ArgumentException("HttpContext.Current is null");
    }

    return "value";
}

I had hereto believed that the second exception is expected because when the await completes, it will likely be on a different thread where HttpContext.Current as a thread-static variable will no longer resolve to the appropriate value. Now, based on the synchronization context, it could actually be forced to go back to the same thread after the await but I'm not doing anything fancy in my test. This is just a plain, naive use of await.

In comments in another question I was told that HttpContext.Current should resolve after an await. There's even another comment on this question indicating the same. So what's true? Should it resolve? I think no, but I want an authoritative answer on this because async and await is new enough that I can't find anything definitive.

TL;DR: Is HttpContext.Current potentially null after an await?

Crescantia answered 22/8, 2013 at 14:53 Comment(9)
Your question is unclear - you've said what you expected to happen, and the comments indicate that that is what's happening... so what's confusing you?Jolynnjon
@user2674389, this is misleading. It's AspNetSynchronizationContext that takes care of HttpContext, not await. More, the continuation callback for await may (and most likely will) occur on a different thread for Web API execution model.Affluence
edited to pose a succinct questionCrescantia
Don't use HttpContext in Web API. It isn't supported for all hosts. There are alternative ways to do what HttpContext is used for.Apotheosize
Could you elaborate on what these alternative ways might be, @DarrelMiller? That would be useful...Hyposthenia
@JoepBeusenberg Request object is accessible via the Controller object. The Request.Properties collection allows storing arbitrary data.Apotheosize
@DarrelMiller, that's not really the same, because that would require you to pass around the controller or the request object to other assemblies. That's exactly what we're trying to avoid with .Current.Hyposthenia
@JoepBeusenberg Creating separate assemblies that only work when they are called from an assembly that is executing within the context of a HTTP request of one particular web stack seems like it might make testing, maintenance and reuse a challenge.Apotheosize
@DarrelMiller Quite the contrary. I have separated business logic from the actual web project. Using dependency injection I can add a webapi-aware library on top of the business logic. But this library breaks when the business logic has done .ConfigureAwait(false) somewhere down the line. There is no request or controller explicitly handed through the business layer, since that one is not web-aware. This is useful for example for a logging module that can inject the request details when the business logic writes a generic TraceInformation.Hyposthenia
T
172

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.

Thoraco answered 22/8, 2013 at 15:6 Comment(19)
I am definitely on .NET 4.5Crescantia
I just created a new ASP.NET 4.5 WebAPI project, copy/pasted your code, and did a test. It worked perfectly for me (neither exception was thrown). Please re-check that you are running on and targeting 4.5.Thoraco
I have Target framework: .NET Framework 4.5 set. I don't know what to tell you, it's definitely null on my local machine.Crescantia
I did already mention it twice (the first time with a link), but just to emphasize: you have to target 4.5 in the web.config. ASP.NET 4.5 introduced a "quirks mode" on the server side and you have to turn that off to get async/await working correctly. See the link for all the gory details.Thoraco
the <httpRuntime targetFramework="4.5" /> is what solved it, thanks for clarifying.Crescantia
Yes, in my async on the server side talk I use the Picard WTF?! meme when I talk about the quirks mode on the server. :)Thoraco
Does not work with .NET 4.5.1? I had to downgrade to 4.5 to make the sample code in this question to work (after puting httpRuntime.targetFramework to 4.5 in Web.Config)Van
@Vince: 4.5.1 should work fine. I'm not sure if you should/could set targetFramework to 4.5.1 or 4.5, but async on 4.5.1 should work fine.Thoraco
It must be mentioned, that using ConfigureAwait(false) in action method will make HttpContext.Current = null. It's obvious but still.Hawks
What if you wrote your own managed handler? I keep coming up with HttpContext.Current=null in there even after adding these items in the web.config.Miscue
@Brain2000: I have no experience with that, but from what I understand it should work. Try to reduce your code to a minimal repro and post it as a question.Thoraco
@StephenCleary If I find it's not easy to find, I will. You clearly know your stuff in this area! If anyone can find the answer, it would be you.Miscue
@Stephen Cleary, the exception is thrown here due to different implementation in the frameworks i.e. >=4.5 and frameworks < 4.5 ? If so there should be a KB article from Microsoft regarding this ?Salzman
@Necromancer: A KB article wouldn't be out of the question. The information is a bit hard to find right now. OTOH, .NET 4.0 is considered quite old at this point.Thoraco
I have my WebAPI project set to 4.6 and some class library projects that are referenced are set to targetFramwork to 4.5 and have specified in my system.web <httpRuntime targetFramework = "4.6" /> and <compilation targetFramework="4.6". In my appSettings, I have <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />. I don't have .ConfigureAwait anywhere in my solution and yet HttpContext is null for me. Can someone help? @StephenClearyEndopeptidase
@Rahul: Please post a question with a minimal, reproducible example, and we'll be able to help you.Thoraco
@Endopeptidase the same for me! my action is Task<IActionResult> and the HttpContext.Current is null before the await function, I mean, whaat? I tried everything here... .net 4.6.1Vulnerary
@JobaDiniz: Can you post a question with a minimal, reproducible example?Thoraco
Dear @StephenCleary , I know you the best in mltithreading and asynchronous topics. I want to ask you if you find it helpfull, do some clarification in my question: https://mcmap.net/q/205734/-iis-worker-thread-vs-web-application-thread/9212040 thanks in advanceFormic
B
33

As @StephenCleary correctly pointed out, you need this in your web.config:

<httpRuntime targetFramework="4.5" />

When I was first troubleshooting this, I did a solution-wide search for the above, confirmed it was present in all my web projects and quickly dismissed it as the culprit. Eventually it occurred to me to look at those search results in full context:

<!--
  For a description of web.config changes for .NET 4.5 see http://go.microsoft.com/fwlink/?LinkId=235367.

  The following attributes can be set on the <httpRuntime> tag.
    <system.Web>
      <httpRuntime targetFramework="4.5" />
    </system.Web>
-->

Doh.

Lesson: If you upgrade a web project to 4.5, you still need to get that setting in place manually.

Bibliography answered 28/1, 2014 at 18:16 Comment(1)
Another gotcha is this is different from <compilation targetFramework"4.5" />Kenwrick
I
7

I ran into this issue recently. As Stephen pointed out not setting explicitly the target framework can generate this issue.

In my case, our Web API was migrated to version 4.6.2 but the runtime target framework was never specified in the web config, so basically this was missing inside the <system.web> tag:

If you have doubts about the framework version you are running this may help: Add the following line on any of your Web API methods and set a breakpoint to verify what type is currently loaded at runtime and verify it is not a Legacy implementation:

You should see this (AspNetSynchronizationContext):

enter image description here

Instead of LegazyAspNetSynchronizationContext (Which was what I saw before adding the target framework):

enter image description here

If you go to the source code (https://referencesource.microsoft.com/#system.web/LegacyAspNetSynchronizationContext.cs) you will see that the Legacy implementation of this interface lacks of asynchronous support.

enter image description here

I spent a lot of time trying to find the source of the issue and Stephen´s response helped a lot. Hope this answer provides some more information about the issue.

Infringe answered 23/7, 2020 at 19:54 Comment(0)
F
3

Is my test flawed, or is there some web.config element I'm missing here that would make HttpContext.Current resolve correctly after an await?

Your test is not flawed and HttpContext.Current should not be null after the await because in ASP.NET Web API when you await, this will ensure that the code that follows this await is passed the correct HttpContext that was present before the await.

Fado answered 22/8, 2013 at 14:58 Comment(7)
Are you sure about the same continuation thread for WebAPI? I dealt with the case where it was a different thread.Affluence
ASP.NET will resume on any thread pool thread, but with the correct request context.Thoraco
Yes, you are right, the thread might not be the same but HttpContext.Current will be the same as before the await. I have updated my question.Fado
HttpContext.Current is null after an await in my code, and I'm targetting .net 4.6.1.Inhospitable
Same problem here still. 4.6.1Arius
for me HttpContext.Current is null before an await functionVulnerary
Me too HttpContext.Current is gone 4.8Boots
P
1

I wanted to comment and say that the previous answers hit the nail on the head from a web.config perspective, however, there can be inherited settings from IIS that can override this functionality if you simply use <httpRuntime targetFramework="4.5" />.

What I mean: The true fix here is this setting: <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />

If you don't explicitly include this setting but rely on <httpRuntime targetFramework="4.5" /> to configure this setting - it will be overridden by any settings in IIS.

If you are debugging or logging the type of SynchronizationContext and you find out it's of type Legacy, you may want to check for a setting at the IIS or hosting Site level.

Will yield LegacyAspNetSynchronizationContext:

Web.config:

    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="aspnet:UseLegacyEncryption" value="true" />
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.8" />
    <httpRuntime targetFramework="4.5" />
  </system.web>

enter image description here

Will yield AspNetSynchronizationContext (this is what you want):

    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true"/>
    <add key="aspnet:UseLegacyEncryption" value="true" />
    <add key="aspnet:UseLegacyMachineKeyEncryption" value="true" />
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.8" />
    <httpRuntime targetFramework="4.5" />
  </system.web>

*Note there is no override setting in IIS here

Plerre answered 13/4, 2021 at 20:44 Comment(0)
H
1

I tried all the other answers here and HttpContext.Current was still null.

.net Framework 4.6.1 / MVC

I'm using HttpContext.Current to get the mapped path for an upload to App_Data

This is how I fixed the issue:

I just obtain the current HttpContext.Current in a variable and then reset it after my await calls.

var tempHttpContextCurrent = System.Web.HttpContext.Current;

var report = await reportingUtilities.GetReport(reportId, currentUserRubixId).ConfigureAwait(false);

// reset the HttpContext.Current after the await call.
System.Web.HttpContext.Current = tempHttpContextCurrent;
Hann answered 11/9, 2021 at 17:14 Comment(0)
O
0

In my case the problem was I forget await in the begining of stack, explicity in my constructor action method

NOTE: Using net core 5.0 with IHttpContextAccessor

So the problem was in code...

[HttpPost]
public async Task AnyAction()
{
    await service.MethodReturningTask(); //await was not
}
Ornamented answered 12/5, 2022 at 3:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.