La solicitud no está disponible en este contexto

113

Estoy ejecutando el modo integrado de IIS 7 y obtengo

La solicitud no está disponible en este contexto

cuando intento acceder a él en una función relacionada con Log4Net que se llama desde Application_Start. Esta es la línea de código que tengo

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

y se lanza una excepción para una segunda comparación.

¿Qué más puedo verificar además de verificar HttpContext.Current.Request para nulo?


Se publica una pregunta similar @ La solicitud no está disponible en esta excepción de contexto cuando se ejecuta mvc en iis7.5

pero tampoco hay una respuesta relevante.

Vishal Seth
fuente
2
¿Recomendarían agregar un bloque try-catch como mi única opción si no tomo las otras dos soluciones como se sugiere en el enlace 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

Respuestas:

79

Consulte el modo integrado IIS7: la solicitud no está disponible en este contexto, la excepción en Application_Start :

La excepción "La solicitud no está disponible en este contexto" es uno de los errores más comunes que puede recibir al mover aplicaciones ASP.NET al modo Integrado en IIS 7.0. Esta excepción ocurre en su implementación del método Application_Start en el archivo global.asax si intenta acceder al HttpContext de la solicitud que inició la aplicación.

Andrew Hare
fuente
2
Más discusión sobre esta situación aquí: stackoverflow.com/questions/1790457/…
jball
6
Gracias. Había visto ese enlace antes. Dice: "Básicamente, si accede al contexto de la solicitud en Application_Start, tiene dos opciones: 1) Cambiar el código de su aplicación para no usar el contexto de la solicitud (recomendado). 2) Mover la aplicación al modo Clásico (NO recomendado) ). " ¿No hay otras opciones? Mi código de registro escribe cosas en la base de datos, por ejemplo, la aplicación se inició, si no a través de una solicitud, esos campos deben establecerse en nulo en lugar de eliminar completamente mi declaración de registro.
Vishal Seth
Tengo el mismo tipo de requisito de registro, si el contexto está disponible, úselo para completar la base de datos, si no, deje los campos como nulos. (En mi caso, no escriba un registro en una tabla de registro, pero ayudaría si hubiera una buena manera de determinar si está disponible o no).
Zarepheth
2
No me gustó, pero envolver el cheque en un try-catch era la única opción además de una refactorización importante de nuestro código de registro (y / o aplicación completa)
Zarepheth
47
¿Hay alguna forma de saber si se encuentra en una situación en la que la solicitud no estará disponible? ¿Alguna propiedad de HttpContext que sabe sobre esto? ¿Por qué lanza una excepción en lugar de simplemente devolver Nothing, como muchas de las otras propiedades?
Joshua Frank
50

Cuando tiene una lógica de registro personalizada, es bastante molesto verse obligado a no registrar application_start o tener que permitir que ocurra una excepción en el registrador (incluso si se maneja).

Parece que en lugar de probar la Requestdisponibilidad, puede probar la Handlerdisponibilidad: cuando no hay Request, sería extraño tener todavía un controlador de solicitudes. Y probar Handlerno genera esa temida Request is not available in this contextexcepción.

Entonces puede cambiar su código a:

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

Tenga cuidado, en el contexto de un módulo http, Handlersin embargo , puede no estar definido Requesty Responseestá definido (lo he visto en el evento BeginRequest). Entonces, si necesita un registro de solicitud / respuesta en un módulo http personalizado, mi respuesta puede no ser adecuada.

Frédéric
fuente
1
Además, me di cuenta de los inconvenientes ya establecidos aquí, me di cuenta de que realmente no era el camino a seguir para las necesidades específicas explicadas por el OP en un comentario. Vea mi otra respuesta en esta página.
Frédéric
1
Esto funcionó para mí, solo necesitaba verificar el objeto Request sin que arrojara una excepción. Ty
OverMars
17

Este es un caso muy clásico: si termina teniendo que verificar los datos proporcionados por la instancia http, considere mover ese código debajo del BeginRequestevento.

void Application_BeginRequest(Object source, EventArgs e)

Este es el lugar adecuado para verificar los encabezados http, la cadena de consulta, etc. Application_StartEs para las configuraciones que se aplican a la aplicación durante todo el tiempo de ejecución, como enrutamiento, filtros, registro, etc.

Por favor, no aplique ninguna solución alternativa , como .ctor estático o cambiar al modo clásico, a menos que no haya forma de mover el código de Starta BeginRequest. eso debería ser factible para la gran mayoría de sus casos.

Arman McHitarian
fuente
7

Dado que ya no hay contexto de solicitud en la canalización durante el inicio de la aplicación, no puedo imaginar que haya alguna forma de adivinar en qué servidor / puerto podría ingresar la próxima solicitud real. Tienes que hacerlo en Begin_Session.

Esto es lo que estoy usando cuando no estoy en el modo clásico. La sobrecarga es 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
fuente
Gracias, esto hizo que mi sitio volviera a funcionar después de que de repente apareciera este síntoma. Por extraño que parezca, no había cambiado de ASP.NET clásico en el grupo de aplicaciones; todavía recibí el error. Agregar una variante de este código (usando Interlocked.Exchange (ref int, int)) resolvió el problema.
John Källén
1
La primera línea de esta respuesta (esto es un duplicado ...) debe eliminarse. Este no es un duplicado de la publicación vinculada, la pregunta es bastante diferente. No estaba solicitando acceder al nombre del servidor en el inicio de la aplicación. Solo estaba dispuesto a tener su lógica de registro común para no lanzar una excepción en el caso especial application_start.
Frédéric
6

Según las necesidades detalladas de OP explicadas en los comentarios , existe una solución más adecuada. El OP afirma que desea agregar datos personalizados en sus registros con log4net, datos relacionados con las solicitudes.

En lugar de envolver cada llamada de log4net en una llamada de registro centralizada personalizada que maneja la recuperación de datos relacionados con la solicitud (en cada llamada de registro), log4net presenta diccionarios de contexto para configurar datos adicionales personalizados para registrar. El uso de esos diccionarios permite colocar los datos de registro de su solicitud para la solicitud actual en el evento BeginRequest, luego descartarlos en el evento EndRequest. Cualquier inicio de sesión intermedio se beneficiará de esos datos personalizados.

Y las cosas que no suceden en un contexto de solicitud no intentarán registrar los datos relacionados con la solicitud, eliminando la necesidad de probar la disponibilidad de la solicitud. Esta solución coincide con el principio que sugirió Arman McHitaryan en su respuesta .

Para que esta solución funcione, también necesitará alguna configuración adicional en sus agregadores log4net para que registren sus datos personalizados.

Esta solución se puede implementar fácilmente como un módulo de mejora de registros personalizado. Aquí hay un código de muestra para ello:

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

Agréguelo a su sitio, muestra de configuración de 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>

Y configure anexos para registrar esas propiedades adicionales, configuración de muestra:

<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
fuente
+1 por señalar que puede usar HttpContext.Current.Items ["IP"] en lugar de HttpContext.Request.UserHostAddress. En caso de solicitud vacía, esto funciona para obtener datos, lo que me salvó :) gracias.
Sielu
2

Puede solucionar el problema sin cambiar al modo clásico y seguir utilizando 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 alguna razón, el tipo estático se crea con una solicitud en su HTTPContext, lo que le permite almacenarlo y reutilizarlo inmediatamente en el evento Application_Start

Filip
fuente
No sé ... Al ejecutar localmente, parece que no "ve" el puerto cuando trato de usar: initialRequest.Url.GetLeftPart (UriPartial.Authority); Tendrá que buscar un camino diferente.
justabuzz
Terriblemente hackear, pero puede ayudar en algunos casos desesperados. (Estoy un poco equilibrado entre votar en contra o en favor de esto, así que simplemente no emitiré voto).
Frédéric
1

Pude solucionar este problema / hackearlo moviéndome al modo "Clásico" desde el modo "integrado".

nithinmohantk
fuente
0

Esto funcionó para mí: si tiene que iniciar sesión en Application_Start, hágalo antes de modificar el contexto. Obtendrá una entrada de registro, simplemente sin fuente, como:

2019-03-12 09: 35: 43,659 INFO (nulo) - Aplicación iniciada

Generalmente registro tanto Application_Start como Session_Start, por lo que veo más detalles en el siguiente mensaje

2019-03-12 09: 35: 45,064 INFO ~ / Leads / Leads.aspx - Sesión 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
fuente
0

En Visual Studio 2012, cuando publiqué la solución por error con la opción 'depurar' obtuve esta excepción. Con la opción de 'liberación' nunca ocurrió. Espero eso ayude.

Mimi
fuente
-3

Puede utilizar lo siguiente:

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

haz esto en 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 de maravilla. this.Context.Request está ahí ...

this.Request lanza una excepción intencionalmente basada en una bandera

Gergely Herendi
fuente
5
-1: Lea la pregunta: esto es lo que está fallando (con IIS> = 7 y modo integrado)
Richard
Esto es lo que sucede cuando los piratas pierden su trabajo y se prueban a sí mismos en la programación :) No te
ofendas