Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HttpContext.Current is null in an asynchronous Callback

Tags:

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?

like image 421
Serge P Avatar asked Jul 03 '14 03:07

Serge P


People also ask

Why Httpcontext current is null?

Current is not null only if you access it in a thread that handles incoming requests. That's why it works "when i use this code in another class of a page".

Why is Httpcontext current null after await?

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 .

What is the difference between session and Httpcontext current session?

There is no difference. The getter for Page. Session returns the context session.


2 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)

like image 132
crokusek Avatar answered Oct 12 '22 17:10

crokusek


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

like image 44
3dd Avatar answered Oct 12 '22 18:10

3dd