HttpContext.Current is null in an asynchronous Callback

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?

Answers


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)


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


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.


Need Your Help

How to set the style of line comment in phpstorm

php ide phpstorm configure

How to change the default style of line comment in PHPStorm to at indentation level rather than at first column?

Joining two consecutive lines using awk or sed

unix sed awk

How would I join two lines using awk or sed?