La demande n'est pas disponible dans ce contexte

113

J'exécute le mode intégré IIS 7 et j'obtiens

La demande n'est pas disponible dans ce contexte

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

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

et une exception est lancé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 demande n'est pas disponible dans cette exception de contexte lors de l'exécution de mvc sur iis7.5

mais pas de réponse pertinente non plus.

Vishal Seth
la source
2
Recommanderiez-vous d'ajouter un bloc try-catch comme seule option si je ne prends pas les deux autres solutions comme suggéré dans le lien d'Andrew Hare? comme try {if (HttpContext.Current.Request.Headers ["User_info"]! = null) log4net.MDC.Set ("UserInfo", HttpContext.Current.Request.Headers ["User_info"]. ToString ()); } catch () {}
Vishal Seth

Réponses:

79

Veuillez consulter IIS7 Integrated mode: Request is not available in this context exception in Application_Start :

L'exception «La demande n'est pas disponible dans ce contexte» est l'une des erreurs les plus courantes que vous pouvez recevoir lors du déplacement des 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.

Andrew Hare
la source
2
Plus de discussion sur cette situation ici: stackoverflow.com/questions/1790457/…
jball
6
Merci. J'avais déjà vu ce lien. Il dit: "Fondamentalement, si vous accédez au contexte de demande dans Application_Start, vous avez deux choix: 1) Modifiez le code de votre application pour ne pas utiliser le contexte de demande (recommandé). 2) Déplacez l'application en mode classique (NON recommandé) ). " N'y a-t-il pas d'autres options? Mon code de journalisation écrit des éléments dans la base de données, par exemple Application démarrée, sinon via une requête, ces champs doivent être définis sur null plutôt que de supprimer complètement mon instruction de journal.
Vishal Seth
J'ai le même type d'exigence de journalisation, si le contexte est disponible, utilisez-le pour remplir la base de données, sinon laissez les champs nuls. (Dans mon cas, n'écrivez pas un enregistrement dans une table de journalisation, mais cela aiderait s'il y avait un bon moyen de déterminer s'il est disponible ou non.)
Zarepheth
2
Je n'ai pas aimé, mais envelopper le chèque dans un essai était la seule option autre qu'une refactorisation majeure de notre code de journalisation (et / ou de l'application entière)
Zarepheth
47
Existe-t-il un moyen de savoir si vous êtes dans une situation où la demande ne sera pas disponible? Une propriété du HttpContext qui sait cela? Pourquoi lance-t-il une exception au lieu de simplement renvoyer Nothing, comme la plupart des autres propriétés?
Joshua Frank
50

Lorsque vous avez une logique de journalisation personnalisée, il est plutôt ennuyeux d'être forcé de ne pas journaliser application_start ou de laisser une exception se produire dans le journal (même si elle est gérée).

Il semble qu'au lieu de tester la Requestdisponibilité, vous pouvez tester la Handlerdisponibilité: quand il n'y en a pas Request, il serait étrange d'avoir encore un gestionnaire de requêtes. Et tester pour Handlerne soulève pas cette Request is not available in this contextexception redoutée .

Vous pouvez donc changer votre code en:

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

Attention, dans le contexte d'un module http, Handlerpeuvent ne pas être définis Requestet Responsesont définis (j'ai vu cela dans l'événement BeginRequest). Donc, si vous avez besoin d'une journalisation des demandes / réponses dans un module http personnalisé, ma réponse peut ne pas convenir.

Frédéric
la source
1
De plus les inconvénients déjà énoncés ici, je me suis rendu compte que ce n'était vraiment pas la voie à suivre pour les besoins spécifiques expliqués par le PO dans un commentaire. Voir mon autre réponse sur cette page.
Frédéric
1
Cela a fait l'affaire pour moi, j'avais juste besoin de vérifier l'objet Request sans qu'il lève une exception. Ty
OverMars
17

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

void Application_BeginRequest(Object source, EventArgs e)

Ceci est le bon endroit pour vérifier http têtes, chaîne de requête et etc ... Application_Startest pour les paramètres applicables pour l'ensemble des applications temps d'exécution, tels que le routage, les filtres, l' exploitation forestière et ainsi de suite.

Veuillez ne pas appliquer de solutions de contournement telles que le .ctor statique ou le passage au mode classique à moins qu'il n'y ait aucun moyen de déplacer le code du Startvers BeginRequest. cela devrait être faisable pour la grande majorité de vos cas.

Arman McHitarian
la source
7

Puisqu'il n'y a plus de contexte de demande dans le pipeline lors du démarrage de l'application, je ne peux plus imaginer qu'il y ait un moyen de deviner sur quel serveur / port la prochaine demande réelle pourrait arriver. Vous devez le faire sur Begin_Session.

Voici ce que j'utilise lorsque je ne suis pas en mode classique. La surcharge est négligeable.

/// <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);
}
Laramie
la source
Merci, cela a remis mon site en marche après qu'il soit soudainement tombé avec ce symptôme. Curieusement, je n'avais pas changé d'ASP.NET classique dans le pool d'applications - j'ai toujours l'erreur. L'ajout d'une variante de ce code (en utilisant Interlocked.Exchange (ref int, int)) a résolu le problème.
John Källén
1
La première ligne de cette réponse (c'est un doublon ...) doit être supprimée. Ce n'est pas une copie de l'article lié, la question est assez différente. Il ne demandait pas d'accéder au nom du serveur dans le démarrage de l'application. Il était seulement prêt à avoir sa logique de journalisation commune pour ne pas lever d'exception dans le cas spécial application_start.
Frédéric
6

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 logs avec log4net, 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 (à chaque appel de journal), log4net propose des dictionnaires de contexte pour configurer des données supplémentaires personnalisées à journaliser. L'utilisation de ces dictionnaires permet de positionner vos données de journal de requêtes pour la requête en cours à l'événement BeginRequest, puis de les rejeter à 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 demande n'essaieront pas de consigner les données liées à la demande, éliminant ainsi le besoin de tester la disponibilité de la demande. Cette solution correspond au principe proposé par Arman McHitaryan dans sa réponse .

Pour que cette solution fonctionne, vous aurez également besoin d'une configuration supplémentaire sur vos appenders 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 de 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, exemple de configuration IIS 7+:

<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>
Frédéric
la source
+1 pour avoir signalé que vous pouvez utiliser HttpContext.Current.Items ["IP"] au lieu de HttpContext.Request.UserHostAddress. En cas de demande vide, cela fonctionne pour récupérer des données - ce qui m'a sauvé :) merci.
Sielu
2

Vous pouvez contourner le problème sans passer en mode classique et toujours 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, vous permettant de le stocker et de le réutiliser immédiatement dans l'événement Application_Start

Filip
la source
Je ne sais pas .. En cours d'exécution localement, il semble qu'il ne "voit" pas le port lorsque j'essaye d'utiliser: initialRequest.Url.GetLeftPart (UriPartial.Authority); Devra chercher une manière différente.
justabuzz
Terriblement hackish, mais peut aider dans certains cas désespérés. (Je suis un peu équilibré entre voter à la baisse ou à la hausse, donc je ne vote pas.)
Frédéric
1

J'ai pu contourner / pirater ce problème en passant en mode "Classique" à partir du mode "intégré".

nithinmohantk
la source
0

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, juste sans source, comme:

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

Je consigne généralement à la fois Application_Start et Session_Start, donc je vois plus de détails dans le message suivant

2019-03-12 09: 35: 45,064 INFO ~ / Leads / Leads.aspx - Session démarrée (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));
        }

Jim Ezzell
la source
0

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

Mimi
la source
-3

Vous pouvez utiliser les éléments suivants:

    protected void Application_Start(object sender, EventArgs e)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(StartMySystem));
    }

    private void StartMySystem(object state)
    {
        Log(HttpContext.Current.Request.ToString());
    }
Maxim Zabuti
la source
-4

faites ceci dans global.asax.cs:

protected void Application_Start()
{
  //string ServerSoftware = Context.Request.ServerVariables["SERVER_SOFTWARE"];
  string server = Context.Request.ServerVariables["SERVER_NAME"];
  string port = Context.Request.ServerVariables["SERVER_PORT"];
  HttpRuntime.Cache.Insert("basePath", "http://" + server + ":" + port + "/");
  // ...
}

fonctionne comme un charme. this.Context.Request est là ...

this.Request lève une exception intentionnellement basée sur un indicateur

Gergely Herendi
la source
5
-1: Lisez la question: c'est ce qui échoue (avec IIS> = 7 et mode intégré)
Richard
C'est ce qui se passe lorsque des pirates perdent leur travail et s'essaient à la programmation :) Pas d'offense,
mec