Żądanie nie jest dostępne w tym kontekście

113

Używam trybu zintegrowanego usług IIS 7 i otrzymuję

Żądanie nie jest dostępne w tym kontekście

kiedy próbuję uzyskać do niego dostęp w funkcji związanej z Log4Net, która jest wywoływana z Application_Start. To jest linia kodu, którą mam

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

i dla drugiego porównania jest zgłaszany wyjątek.

Co jeszcze mogę sprawdzić poza sprawdzaniem HttpContext.Current.Request dla null?


Pojawia się podobne pytanie @ Żądanie nie jest dostępne w tym kontekście wyjątek, gdy uruchamiany jest mvc na iis7.5

ale tam też nie ma odpowiedniej odpowiedzi.

Vishal Seth
źródło
2
Czy polecilibyście dodanie bloku try-catch jako jedynej opcji, jeśli nie wezmę pozostałych dwóch rozwiązań, jak sugeruje to łącze od Andrew Hare? jak try {if (HttpContext.Current.Request.Headers ["User_info"]! = null) log4net.MDC.Set ("UserInfo", HttpContext.Current.Request.Headers ["User_info"]. ToString ()); } catch () {}
Vishal Seth

Odpowiedzi:

79

Zobacz tryb zintegrowany usług IIS7: żądanie nie jest dostępne w tym kontekście wyjątek w Application_Start :

Wyjątek „Żądanie nie jest dostępne w tym kontekście” jest jednym z bardziej typowych błędów, które mogą wystąpić podczas przenoszenia aplikacji ASP.NET do trybu zintegrowanego w usługach IIS 7.0. Ten wyjątek występuje w implementacji metody Application_Start w pliku global.asax, jeśli spróbujesz uzyskać dostęp do HttpContext żądania, które uruchomiło aplikację.

Andrew Hare
źródło
2
Więcej omówienia tej sytuacji tutaj: stackoverflow.com/questions/1790457/ ...
jball
6
Dzięki. Widziałem już ten link. Mówi: „Zasadniczo, jeśli zdarzy ci się uzyskać dostęp do kontekstu żądania w Application_Start, masz dwie możliwości: 1) Zmień kod aplikacji, aby nie korzystał z kontekstu żądania (zalecane). 2) Przenieś aplikację do trybu klasycznego (NIE zalecane ). ” Czy nie mają innych opcji? Mój kod logowania zapisuje rzeczy w DB, np. Aplikacja uruchomiona, jeśli nie przez żądanie, te pola powinny być ustawione na null, zamiast całkowicie usuwać moją instrukcję log.
Vishal Seth
Mam takie same wymagania dotyczące rejestrowania, jeśli kontekst jest dostępny, użyj go do wypełnienia bazy danych, jeśli nie, pozostaw pola puste. (W moim przypadku nie zapisuj rekordu do jednej tabeli rejestrowania, ale pomogłoby to, gdyby istniał dobry sposób określenia, czy jest dostępny.)
Zarepheth
2
Nie podobało mi się to, ale zapakowanie czeku w
próbnym
47
Czy istnieje sposób, aby stwierdzić, czy znajdujesz się w sytuacji, w której żądanie nie będzie dostępne? Jakaś właściwość HttpContext, która o tym wie? Dlaczego zgłasza wyjątek zamiast po prostu zwracać Nothing, jak wiele innych właściwości?
Joshua Frank,
50

Gdy masz niestandardową logikę rejestrowania, dość denerwujące jest zmuszanie do nierejestrowania application_start lub konieczności zezwolenia na wystąpienie wyjątku w rejestratorze (nawet jeśli jest obsługiwany).

Wygląda na to, że zamiast testować Requestdostępność, możesz przetestować Handlerdostępność: jeśli nie ma Request, byłoby dziwne, gdyby nadal mieć moduł obsługi żądań. A testowanie pod kątem Handlernie powoduje tego przerażającego Request is not available in this contextwyjątku.

Możesz więc zmienić swój kod na:

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

Uważaj, w kontekście modułu http Handlermogą nie być zdefiniowane Requesti Responsesą zdefiniowane (widziałem to w zdarzeniu BeginRequest). Więc jeśli potrzebujesz logowania żądania / odpowiedzi w niestandardowym module http, moja odpowiedź może nie być odpowiednia.

Frédéric
źródło
1
Co więcej, stwierdziłem już, że wady, które zostały już tutaj wymienione, zdałem sobie sprawę, że to naprawdę nie jest sposób na zaspokojenie określonych potrzeb wyjaśnionych w PO w komentarzu. Zobacz moją inną odpowiedź na tej stronie.
Frédéric
1
To załatwiło sprawę dla mnie, po prostu musiałem sprawdzić obiekt Request bez zgłaszania wyjątku. Ty
OverMars
17

To bardzo klasyczny przypadek: jeśli w końcu będziesz musiał sprawdzić jakiekolwiek dane dostarczone przez instancję http, rozważ przeniesienie tego kodu pod BeginRequestzdarzenie.

void Application_BeginRequest(Object source, EventArgs e)

To jest właściwe miejsce do sprawdzania nagłówków http, ciągów zapytań itp., Dotyczy Application_Startustawień, które dotyczą całej aplikacji, takich jak routing, filtry, logowanie i tak dalej.

Nie stosuj żadnych obejść, takich jak statyczny .ctor lub przełączanie do trybu klasycznego, chyba że nie ma możliwości przeniesienia kodu z Startdo BeginRequest. powinno to być możliwe w większości przypadków.

Arman McHitarian
źródło
7

Ponieważ nie ma już kontekstu żądania w potoku podczas uruchamiania aplikacji, nie wyobrażam sobie, aby zgadnąć, na jakim serwerze / porcie może przyjść następne rzeczywiste żądanie. Musisz to zrobić na Begin_Session.

Oto, czego używam, gdy nie jestem w trybie klasycznym. Narzut jest znikomy.

/// <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
źródło
Dzięki, to sprawiło, że moja witryna została ponownie uruchomiona po tym, jak nagle wystąpił ten objaw. O dziwo, nie zmieniłem klasycznego ASP.NET w puli aplikacji - nadal mam błąd. Dodanie wariantu tego kodu (przy użyciu Interlocked.Exchange (ref int, int)) rozwiązało problem.
John Källén,
1
Pierwszy wiersz tej odpowiedzi (to jest duplikat ...) powinien zostać usunięty. To nie jest duplikat posta z linkiem, pytanie jest zupełnie inne. Nie prosił o dostęp do nazwy serwera podczas uruchamiania aplikacji. Chciał tylko mieć swoją wspólną logikę logowania, aby nie zgłaszać wyjątku w specjalnym przypadku application_start.
Frédéric
6

W oparciu o szczegółowe potrzeby PO wyjaśnione w komentarzach istnieje bardziej odpowiednie rozwiązanie. OP oświadcza, że ​​chce dodać niestandardowe dane w swoich dziennikach za pomocą log4net, dane związane z żądaniami.

Zamiast zawijać każde wywołanie log4net w niestandardowy scentralizowany dziennik, który obsługuje pobieranie danych związanych z żądaniem (przy każdym wywołaniu dziennika), log4net zawiera słowniki kontekstowe do konfigurowania niestandardowych dodatkowych danych do logowania. Korzystanie z tych słowników pozwala na umieszczenie danych dziennika żądań dla bieżącego żądania w zdarzeniu BeginRequest, a następnie odrzucenie go w zdarzeniu EndRequest. Każde logowanie pomiędzy będzie korzystało z tych niestandardowych danych.

A rzeczy, które nie zdarzają się w kontekście żądania, nie będą próbować rejestrować danych związanych z żądaniem, eliminując potrzebę testowania dostępności żądania. To rozwiązanie jest zgodne z zasadą, którą sugerował Arman McHitaryan w swojej odpowiedzi .

Aby to rozwiązanie działało, będziesz potrzebować dodatkowej konfiguracji w aplikatorach log4net, aby mogli rejestrować niestandardowe dane.

To rozwiązanie można łatwo wdrożyć jako niestandardowy moduł rozszerzający dziennik. Oto przykładowy kod:

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

Dodaj to do swojej witryny, przykład konfiguracji 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>

Skonfiguruj programy dołączające, aby rejestrowały te dodatkowe właściwości, przykładowa konfiguracja:

<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
źródło
+1 za wskazanie, że możesz użyć HttpContext.Current.Items ["IP"] zamiast HttpContext.Request.UserHostAddress. W przypadku pustego Żądania działa to przy pobieraniu danych - co mnie uratowało :) dzięki.
Sielu
2

Możesz obejść ten problem bez przełączania się do trybu klasycznego i nadal używać 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
   }

Z jakiegoś powodu typ statyczny jest tworzony z żądaniem w jego kontekście HTTPContext, co pozwala na jego przechowywanie i natychmiastowe ponowne użycie w zdarzeniu Application_Start

Filip
źródło
Nie wiem ... Działając lokalnie wydaje się, że nie „widzi” portu, gdy próbuję użyć: initialRequest.Url.GetLeftPart (UriPartial.Authority); Będzie musiał szukać innej drogi.
justabuzz
Strasznie hakerski, ale może pomóc w niektórych rozpaczliwych przypadkach. (Jestem trochę zbalansowany między głosowaniem w dół a głosowaniem w górę, więc po prostu nie oddam głosu.)
Frédéric
1

Udało mi się obejść / zhakować ten problem, przechodząc do trybu „Klasycznego” z trybu „zintegrowanego”.

nithinmohantk
źródło
0

U mnie to zadziałało - jeśli musisz zalogować się do Application_Start, zrób to przed zmodyfikowaniem kontekstu. Otrzymasz wpis dziennika, tylko bez źródła, taki jak:

2019-03-12 09: 35: 43,659 INFO (null) - Uruchomiono aplikację

Generalnie rejestruję zarówno Application_Start, jak i Session_Start, więc więcej szczegółów zobaczę w następnej wiadomości

2019-03-12 09: 35: 45,064 INFO ~ / Leads / Leads.aspx - Sesja rozpoczęta (lokalnie)

        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
źródło
0

W Visual Studio 2012, kiedy błędnie opublikowałem rozwiązanie z opcją „debug”, dostałem ten wyjątek. Z opcją „zwolnij” to się nigdy nie zdarzyło. Mam nadzieję, że to pomoże.

Mimi
źródło
-3

Możesz użyć następujących:

    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
źródło
-4

zrób to w 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 + "/");
  // ...
}

działa jak marzenie. this.Context.Request jest tam ...

this.Request celowo zgłasza wyjątek na podstawie flagi

Gergely Herendi
źródło
5
-1: Przeczytaj pytanie: oto, co się zawodzi (z IIS> = 7 i trybem zintegrowanym)
Richard
Tak się dzieje, gdy piraci tracą pracę i próbują programować :) Bez urazy,
stary