O pedido não está disponível neste contexto

113

Estou executando o modo integrado IIS 7 e estou conseguindo

O pedido não está disponível neste contexto

quando tento acessá-lo em uma função relacionada ao Log4Net que é chamada de Application_Start. Esta é a linha de código que eu

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

e uma exceção está sendo lançada para uma segunda comparação.

O que mais posso verificar além de verificar HttpContext.Current.Request para null ??


Uma pergunta semelhante é postada @ Request is not available in this context exception when runnig mvc on iis7.5

mas nenhuma resposta relevante lá também.

Vishal Seth
fonte
2
Vocês recomendariam adicionar um bloco try-catch como minha única opção se eu não aceitar as outras duas soluções sugeridas no link de Andrew Hare? como try {if (HttpContext.Current.Request.Headers ["User_info"]! = null) log4net.MDC.Set ("UserInfo", HttpContext.Current.Request.Headers ["User_info"]. ToString ()); } catch () {}
Vishal Seth

Respostas:

79

Consulte Modo integrado IIS7: a solicitação não está disponível nesta exceção de contexto em Application_Start :

A exceção “A solicitação não está disponível neste contexto” é um dos erros mais comuns que você pode receber ao mover aplicativos ASP.NET para o modo Integrado no IIS 7.0. Essa exceção acontece na implementação do método Application_Start no arquivo global.asax se você tentar acessar o HttpContext da solicitação que iniciou o aplicativo.

Andrew Hare
fonte
2
Mais discussão sobre essa situação aqui: stackoverflow.com/questions/1790457/…
jball
6
Obrigado. Eu já tinha visto esse link antes. Diz: "Basicamente, se por acaso você estiver acessando o contexto da solicitação em Application_Start, você tem duas opções: 1) Altere o código do seu aplicativo para não usar o contexto da solicitação (recomendado). 2) Mova o aplicativo para o modo Clássico (NÃO recomendado ). " Não há outras opções? Meu código de registro grava coisas no banco de dados, por exemplo, aplicativo iniciado, se não por meio de uma solicitação, esses campos devem ser definidos como nulos em vez de remover completamente minha instrução de registro.
Vishal Seth
Eu tenho o mesmo tipo de requisito de log, se o contexto estiver disponível, use-o para preencher o banco de dados, se não deixar os campos nulos. (No meu caso, não escreva um registro em uma tabela de registro, mas ajudaria se houvesse uma boa maneira de determinar se está disponível ou não.)
Zarefete
2
Não gostei, mas envolver a verificação em um try-catch era a única opção além de uma grande refatoração de nosso código de registro (e / ou aplicativo inteiro)
Zarepheth
47
Existe alguma maneira de saber se você está em uma situação em que a solicitação não estará disponível? Alguma propriedade do HttpContext que sabe disso? Por que ele lança uma exceção em vez de apenas retornar Nothing, como muitas das outras propriedades?
Joshua Frank
50

Quando você tem uma lógica de registro personalizada, é um tanto chato ser forçado a não registrar o application_start ou ter que permitir que uma exceção ocorra no logger (mesmo se tratada).

Parece que, em vez de testar a Requestdisponibilidade, você pode testar a Handlerdisponibilidade: quando não houver Request, seria estranho ainda ter um manipulador de solicitações. E o teste de Handlernão levanta aquela Request is not available in this contextexceção temida .

Portanto, você pode alterar seu código para:

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

Cuidado, no contexto de um módulo http, Handlerpode não ser definido embora Requeste Responseesteja definido (eu vi isso no evento BeginRequest). Portanto, se você precisar de registro de solicitação / resposta em um módulo http personalizado, minha resposta pode não ser adequada.

Frédéric
fonte
1
Além das desvantagens já mencionadas aqui, percebi que não era realmente o caminho a percorrer para atender às necessidades específicas explicadas pelo OP em um comentário. Veja minha outra resposta nesta página.
Frédéric
1
Isso funcionou para mim, eu só precisava verificar o objeto Request sem lançar uma exceção. Ty
OverMars,
17

Este é um caso muito clássico: se você acabar tendo que verificar os dados fornecidos pela instância http, considere mover esse código no BeginRequestevento.

void Application_BeginRequest(Object source, EventArgs e)

Este é o lugar certo para verificar os cabeçalhos http, string de consulta e etc ... Application_Starté para as configurações que se aplicam a todo o tempo de execução do aplicativo, como roteamento, filtros, registro e assim por diante.

Por favor, não aplique nenhuma solução alternativa , como estático .ctor ou alternar para o modo Clássico, a menos que não haja como mover o código de Startpara BeginRequest. isso deve ser viável para a grande maioria dos seus casos.

Arman McHitarian
fonte
7

Como não há mais contexto de solicitação no pipeline durante a inicialização do aplicativo, não posso imaginar que haja alguma maneira de adivinhar em qual servidor / porta a próxima solicitação real pode vir. Você tem que fazer isso em Begin_Session.

Aqui está o que estou usando quando não estou no modo clássico. A sobrecarga é insignificante.

/// <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
fonte
Obrigado, isso colocou meu site no ar e funcionando novamente depois que ele repentinamente apresentou esse sintoma. Estranhamente, não mudei do ASP.NET clássico no pool de aplicativos - ainda recebo o erro. Adicionar uma variante desse código (usando Interlocked.Exchange (ref int, int)) resolveu o problema.
John Källén
1
A primeira linha desta resposta (esta é uma duplicata ...) deve ser removida. Esta não é uma duplicata da postagem vinculada, a questão é bem diferente. Ele não estava pedindo para acessar o nome do servidor na inicialização do aplicativo. Ele estava apenas disposto a ter sua lógica de registro comum para não lançar exceção no caso especial application_start.
Frédéric
6

Com base nas necessidades detalhadas do OP explicadas nos comentários , existe uma solução mais apropriada. O OP declara que deseja adicionar dados personalizados em seus logs com log4net, dados relacionados às solicitações.

Em vez de agrupar cada chamada log4net em uma chamada de log centralizada personalizada que lida com a recuperação de dados relacionados à solicitação (em cada chamada de log), o log4net apresenta dicionários de contexto para configurar dados adicionais personalizados a serem registrados. Usar esses dicionários permite posicionar seus dados de registro de solicitação para a solicitação atual no evento BeginRequest e, em seguida, descartá-los no evento EndRequest. Qualquer login no meio se beneficiará desses dados personalizados.

E coisas que não acontecem em um contexto de solicitação não tentarão registrar os dados relacionados à solicitação, eliminando a necessidade de testar a disponibilidade da solicitação. Essa solução corresponde ao princípio que Arman McHitaryan estava sugerindo em sua resposta .

Para que esta solução funcione, você também precisará de algumas configurações adicionais em seus anexadores log4net para que eles registrem seus dados personalizados.

Esta solução pode ser facilmente implementada como um módulo de aprimoramento de log personalizado. Aqui está um exemplo de código para ele:

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

Adicione ao seu site, amostra de configuração do 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 configurar appenders para registrar essas propriedades adicionais, configuração de amostra:

<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
fonte
1 para apontar que você pode usar HttpContext.Current.Items ["IP"] em vez de HttpContext.Request.UserHostAddress. Em caso de solicitação vazia, isso funciona para buscar dados - o que me salvou :) obrigado.
Sielu
2

Você pode contornar o problema sem alternar para o modo clássico e ainda usar 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
   }

Por algum motivo, o tipo estático é criado com uma solicitação em seu HTTPContext, permitindo armazená-lo e reutilizá-lo imediatamente no evento Application_Start

Filip
fonte
Não sei ... Executando localmente, parece que não "vê" a porta quando tento usar: initialRequest.Url.GetLeftPart (UriPartial.Authority); Terá que procurar um caminho diferente.
justabuzz
Terrivelmente hackeado, mas pode ajudar em alguns casos desesperadores. (Estou um pouco equilibrado entre votar contra ou a favor, então eu simplesmente não voto.)
Frédéric
1

Consegui contornar / hackear esse problema passando do modo "integrado" para o modo "Clássico".

nithinmohantk
fonte
0

Isso funcionou para mim - se você tiver que fazer logon Application_Start, faça-o antes de modificar o contexto. Você receberá uma entrada de registro, mas sem fonte, como:

12/03/2019 09: 35: 43,659 INFO (nulo) - Aplicativo iniciado

Eu geralmente registro o Application_Start e Session_Start, então vejo mais detalhes na próxima mensagem

12/03/2019 09: 35: 45.064 INFO ~ / Leads / Leads.aspx - Sessão iniciada (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
fonte
0

No visual studio 2012, quando publiquei a solução por engano com a opção 'debug', recebi essa exceção. Com a opção de 'liberação', isso nunca ocorreu. Espero que ajude.

Mimi
fonte
-3

Você pode usar o seguinte:

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

faça isso em 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 + "/");
  // ...
}

Funciona como um encanto. this.Context.Request está lá ...

this.Request lança exceção intencionalmente com base em um sinalizador

Gergely Herendi
fonte
5
-1: Leia a pergunta: isto é o que está falhando (com IIS> = 7 e modo integrado)
Richard
É o que acontece quando os piratas perdem o emprego e tentam programar :) Sem ofensa,
cara