web-dev-qa-db-fra.com

HttpContext.Current est null dans un rappel asynchrone

J'essaie d'accéder à _HttpContext.Current_ dans un rappel de méthode, donc je peux modifier une variable Session, mais je reçois l'exception que _HttpContext.Current_ est null. La méthode de rappel est déclenchée de manière asynchrone, lorsque l'objet __anAgent_ le déclenche.

Je ne suis toujours pas sûr de la solution à cela après avoir vu similairequestions sur SO.

Une version simplifiée de mon code ressemble à ceci:

_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"]
  }
}
_

Je ne sais pas si nécessaire, mais ma classe WorkAgent ressemble à ceci:

_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);
  }
}
_

Que se passe-t-il dans le code:
Cliquer sur un bouton appelle la méthode SendData(), à l'intérieur de celle-ci __webAgent_ envoie un message à un autre serveur et attend la réponse (en attendant, l'utilisateur peut toujours interagir avec cette page et se référer à la même SessionID). Une fois reçu, il appelle la méthode BackendReceived() qui, de retour dans la page .aspx.cs, appelle la méthode Callback().

Question:
Lorsque WorkAgent déclenche la méthode Callback(), il essaie d'accéder à _HttpContext.Current_ qui est null. Pourquoi est-ce le cas lorsque, si je continue, en ignorant l'exception, je peux toujours obtenir les mêmes variables SessionID et Session en utilisant la méthode ajax retournée GetSessionVar().

Dois-je activer le paramètre aspNetCompatibilityEnabled ?
Dois-je créer une sorte de gestionnaire de module asynchrone ?
Est-ce lié à mode intégré/classique ?

21
Serge P

Voici une solution basée sur une classe qui fonctionne jusqu'à présent dans des cas simples dans MVC5 (MVC6 prend en charge un contexte basé sur 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;
        }
    }
}

Pour l'utiliser, vous pouvez vous connecter à partir de 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();
    }

Vous pouvez ensuite l'utiliser à la place de HttpContext.Current:

    HttpContextProvider.Current

Il peut y avoir des problèmes car je ne comprends pas cela actuellement réponse connexe . Commentez s'il vous plaît.

Référence: AsyncLocal (nécessite .NET 4.6)

10
crokusek

Veuillez consulter l'article suivant pour une explication sur la raison pour laquelle la variable de session est nulle et les solutions possibles

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

cité à partir de l'article;

le HttpContext actuel est en fait dans le stockage local des threads, ce qui explique pourquoi les threads enfants n'y ont pas accès

Et comme le dit un travail proposé autour de l'auteur

passez-y une référence dans votre thread enfant. Incluez une référence à HttpContext dans l'objet "state" de votre méthode de rappel, puis vous pouvez la stocker dans HttpContext.Current sur ce fil

4
3dd

Lorsque vous utilisez des threads ou une fonction async, HttpContext.Current n'est pas disponible.

Essayez d'utiliser:

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

Une fois que vous avez défini current avec une instance appropriée, le reste de votre code est indépendant, qu'il soit appelé à partir d'un thread ou directement à partir d'un WebRequest.

4
Pranav Singh