La richiesta non è disponibile in questo contesto


113

Sto eseguendo la modalità integrata IIS 7 e sto ottenendo

La richiesta non è disponibile in questo contesto

quando provo ad accedervi in ​​una funzione correlata a Log4Net chiamata da Application_Start. Questa è la riga di codice che ho

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

e viene generata un'eccezione per il secondo confronto.

Cos'altro posso controllare oltre a controllare HttpContext.Current.Request per null ??


Viene pubblicata una domanda simile @ La richiesta non è disponibile in questo contesto, eccezione quando si esegue mvc su iis7.5

ma nessuna risposta pertinente neanche lì.


2
Ragazzi, consigliereste di aggiungere un blocco try-catch come unica opzione se non prendo le altre due soluzioni come suggerito nel collegamento di Andrew Hare? come try {if (HttpContext.Current.Request.Headers ["User_info"]! = null) log4net.MDC.Set ("UserInfo", HttpContext.Current.Request.Headers ["User_info"]. ToString ()); } catch () {}
Vishal Seth

Risposte:


79

Vedere la modalità integrata IIS7: la richiesta non è disponibile in questo contesto eccezione in Application_Start :

L'eccezione "La richiesta non è disponibile in questo contesto" è uno degli errori più comuni che potresti ricevere quando sposti le applicazioni ASP.NET in modalità integrata su IIS 7.0. Questa eccezione si verifica nell'implementazione del metodo Application_Start nel file global.asax se si tenta di accedere all'HttpContext della richiesta che ha avviato l'applicazione.


2
Ulteriori discussioni su questa situazione qui: stackoverflow.com/questions/1790457/…
jball

6
Grazie. Avevo già visto quel collegamento. Dice: "Fondamentalmente, se ti capita di accedere al contesto della richiesta in Application_Start, hai due scelte: 1) Modificare il codice dell'applicazione per non utilizzare il contesto della richiesta (consigliato). 2) Spostare l'applicazione in modalità Classica (NON consigliato )." Non hanno altre opzioni? Il mio codice di registrazione scrive cose nel DB, ad es. Applicazione avviata, se non tramite una richiesta, questi campi dovrebbero essere impostati su null invece di rimuovere completamente la mia istruzione di log.
Vishal Seth

Ho lo stesso tipo di requisito di registrazione, se il contesto è disponibile usalo per popolare il database, altrimenti lascia i campi nulli. (Nel mio caso, non scrivere un record su una tabella di registrazione, ma sarebbe utile se ci fosse un buon modo per determinare se è disponibile o meno.)
Zarepheth

2
Non mi è piaciuto, ma avvolgere il controllo in un tentativo di cattura era l'unica opzione diversa da un importante refactoring del nostro codice di registrazione (e / o dell'intera app)
Zarepheth

47
C'è un modo per sapere se ti trovi in ​​una situazione in cui la richiesta non sarà disponibile? Qualche proprietà dell'HttpContext che lo sa? Perché genera un'eccezione invece di restituire semplicemente Nothing, come molte altre proprietà?
Joshua Frank,

50

Quando si dispone di una logica di registrazione personalizzata, è piuttosto fastidioso essere costretti a non registrare application_start o dover lasciare che si verifichi un'eccezione nel logger (anche se gestita).

Sembra che invece di testare la Requestdisponibilità, puoi testare la Handlerdisponibilità: quando non c'è Request, sarebbe strano avere ancora un gestore delle richieste. E il test per Handlernon solleva quella temuta Request is not available in this contexteccezione.

Quindi puoi cambiare il tuo codice in:

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

Attenzione, nel contesto di un modulo http, Handlerpotrebbero non essere definiti Requeste Responsesono definiti (l'ho visto nell'evento BeginRequest). Quindi, se hai bisogno della registrazione di richieste / risposte in un modulo http personalizzato, la mia risposta potrebbe non essere adatta.


1
Inoltre, per gli svantaggi già indicati qui, mi sono reso conto che non era davvero la strada da percorrere per le esigenze specifiche spiegate dal PO in un commento. Vedi la mia altra risposta in questa pagina.
Frédéric

1
Questo ha funzionato per me, dovevo solo controllare l'oggetto Request senza che generasse un'eccezione. Ty
OverMars

17

Questo è un caso molto classico: se finisci per dover controllare i dati forniti dall'istanza http, considera di spostare quel codice sotto l' BeginRequestevento.

void Application_BeginRequest(Object source, EventArgs e)

Questo è il posto giusto per controllare le intestazioni http, la stringa di query e così via ... Application_Startè per le impostazioni che si applicano all'intero tempo di esecuzione dell'applicazione, come routing, filtri, registrazione e così via.

Per favore, non applicare soluzioni alternative come .ctor statico o il passaggio alla modalità classica a meno che non sia possibile spostare il codice da Starta BeginRequest. dovrebbe essere fattibile per la stragrande maggioranza dei tuoi casi.


7

Poiché non esiste più un contesto di richiesta nella pipeline durante l'avvio dell'app, non riesco a immaginare che ci sia un modo per indovinare su quale server / porta potrebbe arrivare la prossima richiesta effettiva. Devi farlo su Begin_Session.

Ecco cosa sto usando quando non sono in modalità classica. Il sovraccarico è trascurabile.

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

Grazie, questo ha rimesso in funzione il mio sito dopo che all'improvviso si è verificato questo sintomo. Stranamente, non ero cambiato dal classico ASP.NET nel pool di app: ho ancora ricevuto l'errore. L'aggiunta di una variante di questo codice (utilizzando Interlocked.Exchange (ref int, int)) ha risolto il problema.
John Källén

1
La prima riga di questa risposta (questo è un duplicato ...) dovrebbe essere rimossa. Questo non è un duplicato del post collegato, la domanda è molto diversa. Non stava chiedendo di accedere al nome del server all'avvio dell'app. Voleva solo che la sua logica di registrazione comune non generasse eccezioni nel caso speciale application_start.
Frédéric

6

Sulla base delle esigenze dettagliate del PO spiegate nei commenti , esiste una soluzione più appropriata. L'OP dichiara che desidera aggiungere dati personalizzati nei suoi log con log4net, dati relativi alle richieste.

Piuttosto che racchiudere ogni chiamata log4net in una chiamata di registro centralizzata personalizzata che gestisce il recupero dei dati relativi alla richiesta (su ogni chiamata di registro), log4net dispone di dizionari di contesto per impostare dati aggiuntivi personalizzati da registrare. L'utilizzo di questi dizionari consente di posizionare i dati del registro delle richieste per la richiesta corrente all'evento BeginRequest, quindi di ignorarli all'evento EndRequest. Qualsiasi accesso in mezzo trarrà vantaggio da quei dati personalizzati.

E le cose che non si verificano in un contesto di richiesta non tenteranno di registrare i dati relativi alla richiesta, eliminando la necessità di testare la disponibilità della richiesta. Questa soluzione corrisponde al principio suggerito da Arman McHitaryan nella sua risposta .

Affinché questa soluzione funzioni, avrai bisogno anche di alcune configurazioni aggiuntive sui tuoi appender log4net in modo che possano registrare i tuoi dati personalizzati.

Questa soluzione può essere facilmente implementata come modulo di miglioramento del registro personalizzato. Ecco il codice di esempio per questo:

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

Aggiungilo al tuo sito, esempio di configurazione 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>

E imposta gli allegati per registrare quelle proprietà aggiuntive, configurazione di esempio:

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

+1 per indicare che è possibile utilizzare HttpContext.Current.Items ["IP"] invece di HttpContext.Request.UserHostAddress. In caso di richiesta vuota questo funziona per il recupero dei dati - che mi ha salvato :) grazie.
Sielu

2

Puoi aggirare il problema senza passare alla modalità classica e continuare a utilizzare 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
   }

Per qualche motivo, il tipo statico viene creato con una richiesta nel suo HTTPContext, consentendo di memorizzarlo e riutilizzarlo immediatamente nell'evento Application_Start


Non lo so .. Eseguendo localmente sembra che non "veda" la porta quando provo a usare: initialRequest.Url.GetLeftPart (UriPartial.Authority); Dovrà cercare un modo diverso.
justabuzz

Terribilmente hacker, ma può aiutare in alcuni casi disperati. (Sono un po 'equilibrato tra voto negativo o voto positivo questo, quindi semplicemente non voto.)
Frédéric

1

Sono stato in grado di aggirare / hackerare questo problema passando alla modalità "Classica" dalla modalità "integrata".


0

Questo ha funzionato per me: se devi accedere ad Application_Start, fallo prima di modificare il contesto. Riceverai una voce di registro, solo senza fonte, come:

12-03-2019 09: 35: 43,659 INFO (null) - Applicazione avviata

In genere registro sia Application_Start che Session_Start, quindi vedo maggiori dettagli nel messaggio successivo

12-03-2019 09: 35: 45,064 INFO ~ / Leads / Leads.aspx - Sessione iniziata (locale)

        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

In Visual Studio 2012, quando ho pubblicato la soluzione per errore con l'opzione "debug" ho ottenuto questa eccezione. Con l'opzione 'release' non si è mai verificato. Spero che sia d'aiuto.


-3

Puoi usare quanto segue:

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

    private void StartMySystem(object state)
    {
        Log(HttpContext.Current.Request.ToString());
    }

-4

fallo 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 + "/");
  // ...
}

funziona come un fascino. this.Context.Request is there ...

this.Request genera un'eccezione intenzionalmente basata su un flag


5
-1: Leggi la domanda: questo è ciò che non funziona (con IIS> = 7 e modalità integrata)
Richard

Questo è ciò che accade quando i pirati perdono il lavoro e si cimentano nella programmazione :) Senza offesa, Man;)
Arman McHitarian
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.