21

Trying to access the HttpContext.Current in a method call back so can I modify a Session variable, however I receive the exception that HttpContext.Current is null. The callback method is fired asynchronously, when the _anAgent object triggers it.

I'm still unsure of the solution to this after viewing similar questions on SO.

A simplified version of my code looks like so:

public partial class Index : System.Web.UI.Page

  protected void Page_Load()
  {
    // aCallback is an Action<string>, triggered when a callback is received
    _anAgent = new WorkAgent(...,
                             aCallback: Callback);
    ...
    HttpContext.Current.Session["str_var"] = _someStrVariable;
  }

  protected void SendData() // Called on button click
  {
    ...
    var some_str_variable = HttpContext.Current.Session["str_var"];

    // The agent sends a message to another server and waits for a call back
    // which triggers a method, asynchronously.
    _anAgent.DispatchMessage(some_str_variable, some_string_event)
  }

  // This method is triggered by the _webAgent
  protected void Callback(string aStr)
  {
    // ** This culprit throws the null exception **
    HttpContext.Current.Session["str_var"] = aStr;
  }

  [WebMethod(EnableSession = true)]
  public static string GetSessionVar()
  {
    return HttpContext.Current.Session["str_var"]
  }
}

Not sure if necessary but my WorkAgent class looks like so:

public class WorkAgent
{
  public Action<string> OnCallbackReceived { get; private set; }

  public WorkAgent(...,
                   Action<string> aCallback = null)
  {
    ...
    OnCallbackReceived = aCallback;
  }

  ...

  // This method is triggered when a response is received from another server
  public BackendReceived(...)
  {
    ...
    OnCallbackReceived(some_string);
  }
}

What happens in the code:
Clicking a button calls the SendData() method, inside this the _webAgent dispatches a message to another server and waits for reply (in the mean time the user can still interact with this page and refer to the same SessionID). Once received it calls the BackendReceived() method which, back in the .aspx.cs page calls the Callback() method.

Question:
When the WorkAgent triggers the Callback() method it tries to access HttpContext.Current which is null. Why is that the case when if I continue on, ignoring the exception, I can still obtain the same SessionID and the Session variable using the ajax returned GetSessionVar() method.

Should I be enabling the aspNetCompatibilityEnabled setting?
Should I be creating some sort of asynchronous module handler?
Is this related to Integrated/Classic mode?

Community
  • 1
  • 1
Serge P
  • 1,449
  • 5
  • 22
  • 39
  • Why complicate this by using callbacks, a better solution might be to use ajax from the client side, that way the user can still interact with the website. And the call to the other system can just be a normal method call – 3dd Jul 03 '14 at 04:24
  • Ajax *is* used from the client side for most part, just didn't include it in the above code (it updates `HttpContext Session` variables and SQL database). The only method that is not an ajax call is `SendData()`. This sends data to *some other* server. I'm just confused why the `HttpContect.Current` becomes **null** on the callback. – Serge P Jul 03 '14 at 04:54
  • Please see my answer for an explanation on why this is happening – 3dd Jul 03 '14 at 05:13

3 Answers3

11

Here's a class-based solution that is working for simple cases so far in MVC5 (MVC6 supports a DI-based context).

using System.Threading;
using System.Web;

namespace SomeNamespace.Server.ServerCommon.Utility
{
    /// <summary>
    /// Preserve HttpContext.Current across async/await calls.  
    /// Usage: Set it at beginning of request and clear at end of request.
    /// </summary>
    static public class HttpContextProvider
    {
        /// <summary>
        /// Property to help ensure a non-null HttpContext.Current.
        /// Accessing the property will also set the original HttpContext.Current if it was null.
        /// </summary>
        static public HttpContext Current => HttpContext.Current ?? (HttpContext.Current = __httpContextAsyncLocal?.Value);

        /// <summary>
        /// MVC5 does not preserve HttpContext across async/await calls.  This can be used as a fallback when it is null.
        /// It is initialzed/cleared within BeginRequest()/EndRequest()
        /// MVC6 may have resolved this issue since constructor DI can pass in an HttpContextAccessor.
        /// </summary>
        static private AsyncLocal<HttpContext> __httpContextAsyncLocal = new AsyncLocal<HttpContext>();

        /// <summary>
        /// Make the current HttpContext.Current available across async/await boundaries.
        /// </summary>
        static public void OnBeginRequest()
        {
            __httpContextAsyncLocal.Value = HttpContext.Current;
        }

        /// <summary>
        /// Stops referencing the current httpcontext
        /// </summary>
        static public void OnEndRequest()
        {
            __httpContextAsyncLocal.Value = null;
        }
    }
}

To use it can hook in from Global.asax.cs:

    public MvcApplication() // constructor
    {            
        PreRequestHandlerExecute += new EventHandler(OnPreRequestHandlerExecute);
        EndRequest += new EventHandler(OnEndRequest);
    } 

    protected void OnPreRequestHandlerExecute(object sender, EventArgs e)
    {
        HttpContextProvider.OnBeginRequest();   // preserves HttpContext.Current for use across async/await boundaries.            
    }

    protected void OnEndRequest(object sender, EventArgs e)
    {
        HttpContextProvider.OnEndRequest();
    }

Then can use this in place of HttpContext.Current:

    HttpContextProvider.Current

There may be issues as I currently do not understand this related answer. Please comment.

Reference: AsyncLocal (requires .NET 4.6)

Community
  • 1
  • 1
crokusek
  • 4,655
  • 1
  • 39
  • 56
  • Would this solution mix contexts between requests if two happened at once because of the static variable? – Simon The Cat Aug 12 '19 at 15:48
  • 1
    @SimonTheCat, The [AsyncLocal](https://docs.microsoft.com/en-us/dotnet/api/system.threading.asynclocal-1) reference page includes an example using a similar static that is returning multiple values. The [AsyncLocal Source code](https://referencesource.microsoft.com/#mscorlib/system/threading/asynclocal.cs) indicates that it is actually some type of wrapper around [ExecutionContext](https://docs.microsoft.com/en-us/dotnet/api/system.threading.executioncontext) which is a thread specific context. – crokusek Aug 12 '19 at 18:59
4

Please see the following article for an explanation on why the Session variable is null, and possible work arounds

http://adventuresdotnet.blogspot.com/2010/10/httpcontextcurrent-and-threads-with.html

quoted from the from the article;

the current HttpContext is actually in thread-local storage, which explains why child threads don’t have access to it

And as a proposed work around the author says

pass a reference to it in your child thread. Include a reference to HttpContext in the “state” object of your callback method, and then you can store it to HttpContext.Current on that thread

Zameer Ansari
  • 23,672
  • 19
  • 120
  • 191
3dd
  • 2,460
  • 11
  • 20
4

When using threads or an async function, HttpContext.Current is not available.

Try using:

HttpContext current;
if(HttpContext != null && HttpContext.Current != null)
{
  current = HttpContext.Current;
}
else
{
    current = this.CurrentContext; 
    //**OR** current = threadInstance.CurrentContext; 
}

Once you set current with a proper instance, the rest of your code is independent, whether called from a thread or directly from a WebRequest.

Michael
  • 5,910
  • 4
  • 52
  • 74
Pranav Singh
  • 13,233
  • 23
  • 66
  • 87