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?
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".
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 .
There is no difference. The getter for Page. Session returns the context session.
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 toHttpContext.Current
on that thread
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With