HttpContext.Current имеет значение null в асинхронном обратном вызове
Попытка доступа к HttpContext.Current
в вызове метода, поэтому я могу изменить переменную Session
, однако получаю исключение, что HttpContext.Current
- null
. Метод обратного вызова запускается асинхронно, когда объект _anAgent
запускает его.
Я все еще не уверен в решении этого вопроса после просмотра аналогичного questions в формате 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"]
}
}
Не уверен, если это необходимо, но класс WorkAgent
выглядит так:
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);
}
}
Что происходит в коде:
Нажатие кнопки вызывает метод SendData()
, внутри которого _webAgent
отправляет сообщение другому серверу и ждет ответа (в то же время пользователь может взаимодействовать с этой страницей и ссылаться на тот же SessionID
). После получения вызова он вызывает метод BackendReceived()
, который на странице .aspx.cs вызывает метод Callback()
.
Вопрос:
Когда WorkAgent
запускает метод Callback()
, он пытается получить доступ к HttpContext.Current
, который равен null
. Почему это так, когда я продолжаю, игнорируя исключение, я все же могу получить ту же переменную SessionID
и Session
, используя возвращаемый метод ajax GetSessionVar()
.
Должен ли я включать параметр aspNetCompatibilityEnabled? Должен ли я создавать какой-то обработчик асинхронного модуля? Это связано с Интегрированный/классический режим?
Ответы
Ответ 1
Здесь решение на основе класса, которое работает в простых случаях до сих пор в MVC5 (MVC6 поддерживает контекст, основанный на DI).
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;
}
}
}
Чтобы использовать его, можно подключиться к 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();
}
Затем можно использовать это вместо HttpContext.Current:
HttpContextProvider.Current
Могут возникнуть проблемы, поскольку я в настоящее время не понимаю этот связанный ответ. Прокомментируйте.
Ссылка: AsyncLocal (требуется .NET 4.6)
Ответ 2
Пожалуйста, обратитесь к следующей статье для объяснения причин, по которым переменная Session имеет значение null, и возможная работа вокруг
http://adventuresdotnet.blogspot.com/2010/10/httpcontextcurrent-and-threads-with.html
цитируется из статьи;
текущий HttpContext
фактически находится в потоковом локальном хранилище, что объясняет, почему дочерние потоки не имеют к нему доступа
И как предлагаемая работа вокруг автора говорит
передать ссылку на нее в дочернем потоке. Включите ссылку на HttpContext
в "состоянии" объекта вашего метода обратного вызова, а затем вы можете сохранить его в HttpContext.Current
в этом потоке
Ответ 3
При использовании потоков или async
функция HttpContext.Current
недоступна.
Попробуйте использовать:
HttpContext current;
if(HttpContext != null && HttpContext.Current != null)
{
current = HttpContext.Current;
}
else
{
current = this.CurrentContext;
//**OR** current = threadInstance.CurrentContext;
}
Как только вы установите current
с соответствующим экземпляром, остальная часть вашего кода будет независимой, вызвана ли она из потока или непосредственно из WebRequest
.