web-dev-qa-db-fra.com

La demande n'est pas disponible dans ce contexte

J'utilise IIS 7 en mode intégré et j'obtiens

La demande n'est pas disponible dans ce contexte

quand j'essaie d'y accéder dans une fonction liée à Log4Net appelée à partir de Application_Start. Ceci est la ligne de code que j'ai

if (HttpContext.Current != null && HttpContext.Current.Request != null)

et une exception est levée pour la deuxième comparaison.

Que puis-je vérifier d'autre que de vérifier HttpContext.Current.Request pour null ??


Une question similaire est postée @ la requête n'est pas disponible dans cette exception de contexte lorsque runnig mvc sur iis7.5

mais pas de réponse pertinente là non plus.

106
Vishal Seth

Veuillez consulter mode intégré IIS7: la requête n'est pas disponible dans cette exception de contexte dans Application_Start :

L'exception "La demande n'est pas disponible dans ce contexte" est l'une des erreurs les plus courantes que vous pourriez recevoir lors du déplacement d'applications ASP.NET en mode intégré sur IIS 7.0. Cette exception se produit dans votre implémentation. de la méthode Application_Start dans le fichier global.asax si vous essayez d'accéder au HttpContext de la demande qui a démarré l'application.

75
Andrew Hare

Lorsque vous avez une logique de journalisation personnalisée, il est plutôt agaçant d’être obligé de ne pas enregistrer application_start ou de laisser une exception se produire dans l’enregistreur (même si elle est gérée).

Il semble que plutôt que de tester la disponibilité de Request, vous pouvez tester la disponibilité de Handler: quand il n'y a pas de Request, il serait étrange d'avoir encore un gestionnaire de requêtes. Et tester Handler ne soulève pas cette redoutable Request is not available in this context exception.

Donc, vous pouvez changer votre code pour:

var currContext = HttpContext.Current;
if (currContext != null && currContext.Handler != null)

Attention, dans le contexte d'un module http, Handler ne peut pas être défini bien que Request et Response soient définis (je l'ai vu dans l'événement BeginRequest). Donc, si vous avez besoin de consigner demande/réponse dans un module http personnalisé, ma réponse risque de ne pas convenir.

41
Frédéric

C'est un cas très classique: si vous devez vérifier les données fournies par l'instance http, envisagez de déplacer ce code sous l'événement BeginRequest.

void Application_BeginRequest(Object source, EventArgs e)

C’est le bon endroit pour vérifier les en-têtes http, la chaîne de requête et etc ... Application_Start concerne les paramètres qui s'appliquent à l'ensemble du temps d'exécution de l'application, tels que le routage, les filtres, la journalisation, etc.

S'il vous plaît, n'appliquez aucune solution de contournement tels que .ctor statique ou le passage au mode classique à moins qu'il ne soit pas possible de déplacer le code de Start vers BeginRequest. cela devrait être faisable pour la grande majorité de vos cas.

16
Arman McHitarian

Comme il n'y a plus de contexte de requête dans le pipeline au démarrage de l'application, je ne vois pas comment il est possible de deviner quel serveur/port la prochaine requête pourrait entrer. Vous devez le faire sur Begin_Session.

Voici ce que j'utilise quand je ne suis pas en mode classique. Les frais généraux sont négligeables.

/// <summary>
/// Class is called only on the first request
/// </summary>
private class AppStart
{
    static bool _init = false;
    private static Object _lock = new Object();

    /// <summary>
    /// Does nothing after first request
    /// </summary>
    /// <param name="context"></param>
    public static void Start(HttpContext context)
    {
        if (_init)
        {
            return;
        }
        //create class level lock in case multiple sessions start simultaneously
        lock (_lock)
        {
            if (!_init)
            {
                string server = context.Request.ServerVariables["SERVER_NAME"];
                string port = context.Request.ServerVariables["SERVER_PORT"];
                HttpRuntime.Cache.Insert("basePath", "http://" + server + ":" + port + "/");
                _init = true;
            }
        }
    }
}

protected void Session_Start(object sender, EventArgs e)
{
    //initializes Cache on first request
    AppStart.Start(HttpContext.Current);
}
7
Laramie

Sur la base des besoins détaillés du PO expliqués dans les commentaires, une solution plus appropriée existe. L'OP déclare qu'il souhaite ajouter des données personnalisées dans ses journaux avec log4net, des données liées aux demandes.

Plutôt que d'encapsuler chaque appel log4net dans un appel de journal centralisé personnalisé qui gère la récupération des données liées aux requêtes (sur chaque appel de journal), log4net propose des dictionnaires de contexte pour la configuration de données supplémentaires personnalisées à consigner. L'utilisation de ces dictionnaires permet de positionner les données de votre journal de requêtes pour la requête en cours à l'événement BeginRequest, puis de les ignorer lors de l'événement EndRequest. Toute connexion entre les deux bénéficiera de ces données personnalisées.

Et les choses qui ne se produisent pas dans un contexte de requête ne tenteront pas de consigner les données relatives aux requêtes, ce qui vous évitera de tester la disponibilité des requêtes. Cette solution correspond au principe suggéré par Arman McHitaryan dans son réponse .

Pour que cette solution fonctionne, vous aurez également besoin d'une configuration supplémentaire sur vos ajouts log4net afin qu'ils puissent enregistrer vos données personnalisées.

Cette solution peut être facilement implémentée en tant que module d'amélioration du journal personnalisé. Voici un exemple de code pour cela:

using System;
using System.Web;
using log4net;
using log4net.Core;

namespace YourNameSpace
{
    public class LogHttpModule : IHttpModule
    {
        public void Dispose()
        {
            // nothing to free
        }

        private const string _ipKey = "IP";
        private const string _urlKey = "URL";
        private const string _refererKey = "Referer";
        private const string _userAgentKey = "UserAgent";
        private const string _userNameKey = "userName";

        public void Init(HttpApplication context)
        {
            context.BeginRequest += WebAppli_BeginRequest;
            context.PostAuthenticateRequest += WebAppli_PostAuthenticateRequest;
            // All custom properties must be initialized, otherwise log4net will not get
            // them from HttpContext.
            InitValueProviders(_ipKey, _urlKey, _refererKey, _userAgentKey,
                _userNameKey);
        }

        private void InitValueProviders(params string[] valueKeys)
        {
            if (valueKeys == null)
                return;
            foreach(var key in valueKeys)
            {
                GlobalContext.Properties[key] = new HttpContextValueProvider(key);
            }
        }

        private void WebAppli_BeginRequest(object sender, EventArgs e)
        {
            var currContext = HttpContext.Current;
            currContext.Items[_ipKey] = currContext.Request.UserHostAddress;
            currContext.Items[_urlKey] = currContext.Request.Url.AbsoluteUri;
            currContext.Items[_refererKey] = currContext.Request.UrlReferrer != null ? 
                currContext.Request.UrlReferrer.AbsoluteUri : null;
            currContext.Items[_userAgentKey] = currContext.Request.UserAgent;
        }

        private void WebAppli_PostAuthenticateRequest(object sender, EventArgs e)
        {
            var currContext = HttpContext.Current;
            // log4net doc states that %identity is "extremely slow":
            // http://logging.Apache.org/log4net/release/sdk/log4net.Layout.PatternLayout.html
            // So here is some custom retrieval logic for it, so bad, especialy since I
            // tend to think this is a missed copy/paste in that documentation.
            // Indeed, we can find by inspection in default properties fetch by log4net a
            // log4net:Identity property with the data, but it looks undocumented...
            currContext.Items[_userNameKey] = currContext.User.Identity.Name;
        }
    }

    // General idea coming from 
    // http://piers7.blogspot.fr/2005/12/log4net-context-problems-with-aspnet.html
    // We can not use log4net ThreadContext or LogicalThreadContext with asp.net, since
    // asp.net may switch thread while serving a request, and reset the call context
    // in the process.
    public class HttpContextValueProvider : IFixingRequired
    {
        private string _contextKey;
        public HttpContextValueProvider(string contextKey)
        {
            _contextKey = contextKey;
        }

        public override string ToString()
        {
            var currContext = HttpContext.Current;
            if (currContext == null)
                return null;
            var value = currContext.Items[_contextKey];
            if (value == null)
                return null;
            return value.ToString();
        }

        object IFixingRequired.GetFixedObject()
        {
            return ToString();
        }
    }
}

Ajoutez-le à votre site, IIS 7+ sample conf:

<system.webServer>
  <!-- other stuff removed ... -->
  <modules>
    <!-- other stuff removed ... -->
    <add name="LogEnhancer" type="YourNameSpace.LogHttpModule, YourAssemblyName" preCondition="managedHandler" />
    <!-- other stuff removed ... -->
  </modules>
  <!-- other stuff removed ... -->
</system.webServer>

Et configurez les appenders pour consigner ces propriétés supplémentaires, exemple de configuration:

<log4net>
  <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
    <!-- other stuff removed ... -->
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date [%thread] %-5level %logger - %message - %property%newline%exception" />
    </layout>
  </appender>
  <appender name="SqlAppender" type="log4net.Appender.AdoNetAppender">
    <!-- other stuff removed ... -->
    <commandText value="INSERT INTO YourLogTable ([Date],[Thread],[Level],[Logger],[UserName],[Message],[Exception],[Ip],[Url],[Referer],[UserAgent]) VALUES (@log_date, @thread, @log_level, @logger, @userName, @message, @exception, @Ip, @Url, @Referer, @UserAgent)" />
    <!-- other parameters removed ... -->
    <parameter>
      <parameterName value="@userName" />
      <dbType value="String" />
      <size value="255" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%property{userName}" />
      </layout>
    </parameter>
    <parameter>
      <parameterName value="@Ip"/>
      <dbType value="String" />
      <size value="255" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%property{Ip}" />
      </layout>
    </parameter>
    <parameter>
      <parameterName value="@Url"/>
      <dbType value="String" />
      <size value="255" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%property{Url}" />
      </layout>
    </parameter>
    <parameter>
      <parameterName value="@Referer"/>
      <dbType value="String" />
      <size value="255" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%property{Referer}" />
      </layout>
    </parameter>
    <parameter>
      <parameterName value="@UserAgent"/>
      <dbType value="String" />
      <size value="255" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%property{UserAgent}" />
      </layout>
    </parameter>
  </appender>
  <!-- other stuff removed ... -->
</log4net>
6
Frédéric

Vous pouvez contourner le problème sans passer en mode classique et continuer à utiliser Application_Start

public class Global : HttpApplication
{
   private static HttpRequest initialRequest;

   static Global()
   {
      initialRequest = HttpContext.Current.Request;       
   }

   void Application_Start(object sender, EventArgs e)
   {
      //access the initial request here
   }

Pour une raison quelconque, le type statique est créé avec une requête dans son HTTPContext, ce qui vous permet de le stocker et de le réutiliser immédiatement dans l'événement Application_Start.

2
Filip

J'ai pu résoudre ce problème en passant du mode "intégré" au mode "classique".

1
NiTRiX-Reloaded

Dans Visual Studio 2012, lorsque j'ai publié la solution par erreur avec l'option 'débogage', j'ai eu cette exception. Avec l'option 'release', cela ne s'est jamais produit. J'espère que ça aide.

0
Mimi

Cela a fonctionné pour moi - si vous devez vous connecter à Application_Start, faites-le avant de modifier le contexte. Vous obtiendrez une entrée de journal, sans aucune source, comme:

2019-03-12 09: 35: 43,659 INFO (null) - Application lancée

En règle générale, je connecte à la fois Application_Start et Session_Start. Je vois donc plus de détails dans le message suivant.

2019-03-12 09: 35: 45,064 INFO ~/Leads/Leads.aspx - Session lancée (au niveau local)

        protected void Application_Start(object sender, EventArgs e)
        {
            log4net.Config.XmlConfigurator.Configure();
            log.Info("Application Started");
            GlobalContext.Properties["page"] = new GetCurrentPage();
        }

        protected void Session_Start(object sender, EventArgs e)
        {
            Globals._Environment = WebAppConfig.getEnvironment(Request.Url.AbsoluteUri, Properties.Settings.Default.LocalOverride);
            log.Info(string.Format("Session Started ({0})", Globals._Environment));
        }


0
Jim Ezzell