Anfrage ist in diesem Zusammenhang nicht verfügbar

113

Ich verwende den integrierten IIS 7-Modus und erhalte

Anfrage ist in diesem Zusammenhang nicht verfügbar

wenn ich versuche, in einer Log4Net-bezogenen Funktion darauf zuzugreifen, von der aus aufgerufen wird Application_Start. Dies ist die Codezeile, die ich habe

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

und eine Ausnahme wird für den zweiten Vergleich ausgelöst.

Was kann ich außer HttpContext.Current.Request auf null überprüfen?


Eine ähnliche Frage wird gestellt. @ Anfrage ist in diesem Kontext keine Ausnahme verfügbar, wenn mvc auf iis7.5 ausgeführt wird

aber auch dort keine relevante Antwort.

Vishal Seth
quelle
2
Würden Sie empfehlen, einen Try-Catch-Block als meine einzige Option hinzuzufügen, wenn ich nicht die beiden anderen Lösungen nehme, wie im Link von Andrew Hare vorgeschlagen? wie try {if (HttpContext.Current.Request.Headers ["User_info"]! = null) log4net.MDC.Set ("UserInfo", HttpContext.Current.Request.Headers ["User_info"]. ToString ()); } catch () {}
Vishal Seth

Antworten:

79

Bitte beachten Sie IIS7 integrierten Modus: Request ist in diesem Zusammenhang Ausnahme in Application_Start nicht verfügbar :

Die Ausnahme "Anforderung ist in diesem Kontext nicht verfügbar" ist einer der häufigsten Fehler, die beim Verschieben von ASP.NET-Anwendungen in den integrierten Modus unter IIS 7.0 auftreten können. Diese Ausnahme tritt bei der Implementierung der Application_Start-Methode in der Datei global.asax auf, wenn Sie versuchen, auf den HttpContext der Anforderung zuzugreifen, mit der die Anwendung gestartet wurde.

Andrew Hare
quelle
2
Weitere Diskussion dieser Situation hier: stackoverflow.com/questions/1790457/…
jball
6
Vielen Dank. Ich hatte diesen Link schon einmal gesehen. Darin heißt es: "Wenn Sie zufällig in Application_Start auf den Anforderungskontext zugreifen, haben Sie zwei Möglichkeiten: 1) Ändern Sie Ihren Anwendungscode so, dass der Anforderungskontext nicht verwendet wird (empfohlen). 2) Verschieben Sie die Anwendung in den klassischen Modus (NICHT empfohlen) ). " Gibt es keine anderen Möglichkeiten? Mein Protokollierungscode schreibt Daten in die Datenbank, z. B. Anwendung gestartet, wenn nicht über eine Anforderung, sollten diese Felder auf null gesetzt werden, anstatt meine Protokollanweisung vollständig zu entfernen.
Vishal Seth
Ich habe die gleiche Art von Protokollierungsanforderung. Wenn der Kontext verfügbar ist, füllen Sie die Datenbank damit, wenn nicht, lassen Sie die Felder null. (In meinem Fall schreiben Sie keinen Datensatz in eine Protokollierungstabelle, aber es wäre hilfreich, wenn es eine gute Möglichkeit gäbe, festzustellen, ob verfügbar ist oder nicht.)
Zarepheth
2
Hat mir nicht gefallen, aber das Einpacken des Schecks in einen Try-Catch war die einzige Option,
abgesehen von einer umfassenden Überarbeitung
47
Gibt es eine Möglichkeit festzustellen, ob Sie sich in einer Situation befinden, in der die Anfrage nicht verfügbar ist? Eine Eigenschaft des HttpContext, die davon weiß? Warum wird eine Ausnahme ausgelöst, anstatt wie bei vielen anderen Eigenschaften nur Nothing zurückzugeben?
Joshua Frank
50

Wenn Sie über eine benutzerdefinierte Protokollierungslogik verfügen, ist es ziemlich ärgerlich, gezwungen zu sein, application_start entweder nicht zu protokollieren oder eine Ausnahme im Logger auftreten zu lassen (auch wenn dies behandelt wird).

Es scheint, dass Sie nicht die RequestVerfügbarkeit testen , sondern die Verfügbarkeit testen können Handler: Wenn dies nicht Requestder Fall ist , wäre es seltsam, immer noch einen Anforderungshandler zu haben. Und das Testen auf Handlerlöst diese gefürchtete Request is not available in this contextAusnahme nicht aus.

Sie können Ihren Code also ändern in:

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

Beachten Sie, dass im Kontext eines http-Moduls Handlermöglicherweise nicht definiert Requestund Responsedefiniert sind (ich habe dies im BeginRequest-Ereignis gesehen). Wenn Sie also eine Anforderungs- / Antwortprotokollierung in einem benutzerdefinierten http-Modul benötigen, ist meine Antwort möglicherweise nicht geeignet.

Frédéric
quelle
1
Aufgrund der hier bereits erwähnten Nachteile wurde mir klar, dass es wirklich nicht der richtige Weg war, die spezifischen Bedürfnisse zu erfüllen, die vom OP in einem Kommentar erläutert wurden. Siehe meine andere Antwort auf dieser Seite.
Frédéric
1
Dies hat den Trick für mich getan, ich musste nur das Request-Objekt überprüfen, ohne dass es eine Ausnahme auslöste. Ty
OverMars
17

Dies ist ein sehr klassischer Fall: Wenn Sie am Ende nach Daten suchen müssen, die von der http-Instanz bereitgestellt werden, sollten Sie diesen Code unter das BeginRequestEreignis verschieben.

void Application_BeginRequest(Object source, EventArgs e)

Dies ist der richtige Ort, um nach http-Headern, Abfragezeichenfolgen usw. zu suchen. Hier finden Sie Application_Startdie Einstellungen, die für die gesamte Laufzeit der Anwendung gelten, z. B. Routing, Filter, Protokollierung usw.

Wenden Sie keine Problemumgehungen wie statischen .ctor an oder wechseln Sie in den klassischen Modus, es sei denn, Sie können den Code nicht von Startnach verschieben BeginRequest. Das sollte für die überwiegende Mehrheit Ihrer Fälle machbar sein.

Arman McHitarian
quelle
7

Da während des App-Starts kein Anforderungskontext mehr in der Pipeline ist, kann ich mir nicht vorstellen, auf welchen Server / Port die nächste tatsächliche Anforderung eingehen könnte. Sie müssen es also auf Begin_Session tun.

Folgendes verwende ich, wenn ich mich nicht im klassischen Modus befinde. Der Overhead ist vernachlässigbar.

/// <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
quelle
Vielen Dank, dies hat meine Website wieder zum Laufen gebracht, nachdem plötzlich dieses Symptom aufgetreten ist. Seltsamerweise hatte ich mich im App-Pool nicht von klassischem ASP.NET geändert - ich habe immer noch den Fehler erhalten. Das Hinzufügen einer Variante dieses Codes (mit Interlocked.Exchange (ref int, int)) löste das Problem.
John Källén
1
Die erste Zeile dieser Antwort (dies ist ein Duplikat ...) sollte entfernt werden. Dies ist kein Duplikat des verlinkten Beitrags, die Frage ist ganz anders. Er hat beim Start der App nicht nach dem Zugriff auf den Servernamen gefragt. Er war nur bereit, seine gemeinsame Protokollierungslogik zu haben, um im Sonderfall application_start keine Ausnahme auszulösen.
Frédéric
6

Basierend auf den detaillierten Anforderungen des OP, die in den Kommentaren erläutert werden gibt es eine geeignetere Lösung. Das OP gibt an, dass er benutzerdefinierte Daten mit log4net in seine Protokolle aufnehmen möchte, Daten, die sich auf Anforderungen beziehen.

Anstatt jeden log4net-Aufruf in einen benutzerdefinierten zentralisierten Protokollaufruf zu verpacken, der das Abrufen anforderungsbezogener Daten (bei jedem Protokollaufruf) übernimmt, bietet log4net Kontextwörterbücher zum Einrichten benutzerdefinierter zusätzlicher Daten für die Protokollierung. Durch die Verwendung dieser Wörterbücher können Sie Ihre Anforderungsprotokolldaten für die aktuelle Anforderung beim BeginRequest-Ereignis positionieren und beim EndRequest-Ereignis schließen. Jede Anmeldung dazwischen profitiert von diesen benutzerdefinierten Daten.

Und Dinge, die in einem Anforderungskontext nicht passieren, versuchen nicht, anforderungsbezogene Daten zu protokollieren, sodass die Verfügbarkeit von Anforderungen nicht mehr getestet werden muss. Diese Lösung entspricht dem Prinzip, das Arman McHitaryan in seiner Antwort vorgeschlagen hat .

Damit diese Lösung funktioniert, benötigen Sie außerdem einige zusätzliche Konfigurationen für Ihre log4net-Appender, damit diese Ihre benutzerdefinierten Daten protokollieren können.

Diese Lösung kann einfach als benutzerdefiniertes Protokollverbesserungsmodul implementiert werden. Hier ist ein Beispielcode dafür:

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

Fügen Sie es Ihrer Site hinzu, IIS 7+ conf Beispiel:

<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>

Richten Sie Appender ein, um diese zusätzlichen Eigenschaften zu protokollieren. Beispielkonfiguration:

<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
quelle
+1 für den Hinweis, dass Sie HttpContext.Current.Items ["IP"] anstelle von HttpContext.Request.UserHostAddress verwenden können. Im Falle einer leeren Anfrage funktioniert dies zum Abrufen von Daten - was mich gerettet hat :) danke.
Sielu
2

Sie können das Problem umgehen, ohne in den klassischen Modus zu wechseln, und trotzdem Application_Start verwenden

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
   }

Aus irgendeinem Grund wird der statische Typ mit einer Anforderung in seinem HTTPContext erstellt, sodass Sie ihn speichern und sofort im Application_Start-Ereignis wiederverwenden können

Filip
quelle
Ich weiß nicht .. Lokal laufen scheint es, dass es den Port nicht "sieht", wenn ich versuche zu verwenden: initialRequest.Url.GetLeftPart (UriPartial.Authority); Muss nach einem anderen Weg suchen.
Justabuzz
Schrecklich hackisch, kann aber in einigen verzweifelten Fällen helfen. (Ich bin ein bisschen ausgeglichen zwischen Down-Voting oder Up-Voting, also gebe ich einfach keine Stimme ab.)
Frédéric
1

Ich konnte dieses Problem umgehen / hacken, indem ich vom "integrierten" Modus in den "klassischen" Modus wechselte.

Nithinmohantk
quelle
0

Dies hat bei mir funktioniert. Wenn Sie sich bei Application_Start anmelden müssen, tun Sie dies, bevor Sie den Kontext ändern. Sie erhalten einen Protokolleintrag, nur ohne Quelle, wie:

2019-03-12 09: 35: 43,659 INFO (null) - Anwendung gestartet

Im Allgemeinen protokolliere ich sowohl Application_Start als auch Session_Start, sodass ich in der nächsten Nachricht mehr Details sehe

2019-03-12 09: 35: 45,064 INFO ~ / Leads / Leads.aspx - Sitzung gestartet (lokal)

        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
quelle
0

In Visual Studio 2012 habe ich diese Ausnahme erhalten, als ich die Lösung fälschlicherweise mit der Option 'Debug' veröffentlicht habe. Mit der Option 'Release' ist es nie aufgetreten. Ich hoffe es hilft.

Mimi
quelle
-3

Sie können Folgendes verwenden:

    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
quelle
-4

Tun Sie dies in 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 + "/");
  // ...
}

klappt wunderbar. this.Context.Request ist da ...

this.Request löst absichtlich eine Ausnahme aus, die auf einem Flag basiert

Gergely Herendi
quelle
5
-1: Lesen Sie die Frage: Dies ist, was fehlschlägt (mit IIS> = 7 und integriertem Modus)
Richard
Dies ist, was passiert, wenn Piraten ihren Job verlieren und sich in der Programmierung versuchen :) Nichts für ungut, Mann;)
Arman McHitarian