Запрос недоступен в этом контексте


113

Я использую интегрированный режим IIS 7 и получаю

Запрос недоступен в этом контексте

когда я пытаюсь получить к нему доступ в функции, связанной с Log4Net, которая вызывается из Application_Start. Это строка кода, которую я

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

и создается исключение для второго сравнения.

Что еще я могу проверить, кроме проверки HttpContext.Current.Request на null ??


Аналогичный вопрос размещен @ Request is not available in this context exception when runnig mvc on iis7.5

но и там нет соответствующего ответа.


2
Ребята, вы бы порекомендовали добавить блок try-catch в качестве единственного варианта, если я не воспользуюсь двумя другими решениями, как предложено в ссылке от Эндрю Хейра? например попробуйте {if (HttpContext.Current.Request.Headers ["User_info"]! = null) log4net.MDC.Set ("UserInfo", HttpContext.Current.Request.Headers ["User_info"]. ToString ()); } catch () {}
Вишал Сет

Ответы:


79

См. Интегрированный режим IIS7: запрос недоступен в этом исключении контекста в Application_Start :

Исключение «Запрос недоступен в этом контексте» - одна из наиболее распространенных ошибок, которые могут возникнуть при переводе приложений ASP.NET в интегрированный режим в IIS 7.0. Это исключение происходит в вашей реализации метода Application_Start в файле global.asax, если вы пытаетесь получить доступ к HttpContext запроса, запустившего приложение.


2
Подробное обсуждение этой ситуации здесь: stackoverflow.com/questions/1790457/…
jball

6
Спасибо. Я уже видел эту ссылку раньше. В нем говорится: «В основном, если вы получаете доступ к контексту запроса в Application_Start, у вас есть два варианта: 1) Измените код приложения, чтобы не использовать контекст запроса (рекомендуется). 2) Переместите приложение в классический режим (НЕ рекомендуется ). " У них нет других вариантов? Мой код ведения журнала записывает данные в БД, например, приложение запущено, если не по запросу, эти поля должны быть установлены в значение null, а не полностью удалять мой оператор журнала.
Vishal Seth

У меня такие же требования к журналированию, если контекст доступен, используйте его для заполнения базы данных, если не оставьте поля пустыми. (В моем случае не записывать запись в одну таблицу журналирования, но было бы полезно, если бы был хороший способ определить, доступна она или нет.)
Зарепет

2
Не понравилось, но включение проверки в try-catch было единственным вариантом, кроме серьезного рефакторинга нашего кода ведения журнала (и / или всего приложения)
Zarepheth

47
Есть ли способ узнать , находитесь ли вы в ситуации, когда запрос не будет доступен? Какое-то свойство HttpContext, которое знает об этом? Почему он генерирует исключение, а не просто возвращает Nothing, как многие другие свойства?
Джошуа Франк

50

Когда у вас есть настраиваемая логика ведения журнала, довольно неприятно быть вынужденным либо не регистрировать application_start, либо допускать возникновение исключения в регистраторе (даже если оно обработано).

Похоже, что вместо проверки на Requestдоступность вы можете проверить Handlerдоступность: когда ее нет Request, было бы странно иметь обработчик запросов. И тестирование Handlerне вызывает этого ужасного Request is not available in this contextисключения.

Таким образом, вы можете изменить свой код на:

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

Остерегайтесь, в контексте модуля http, Handlerхотя они могут не быть определены Requestи Responseопределены (я видел это в событии BeginRequest). Поэтому, если вам нужна регистрация запросов / ответов в настраиваемом модуле http, мой ответ может не подойти.


1
Более того, недостатки, уже указанные здесь, я понял, что это действительно не способ пойти для конкретных потребностей, объясненных OP в комментарии. Смотрите мой другой ответ на этой странице.
Frédéric

1
Это помогло мне, мне просто нужно было проверить объект Request, не вызывая исключения. Ty
OverMars

17

Это очень классический случай: если в конечном итоге вам придется проверять какие-либо данные, предоставленные экземпляром http, подумайте о перемещении этого кода в BeginRequestсобытие.

void Application_BeginRequest(Object source, EventArgs e)

Это подходящее место для проверки заголовков http, строки запроса и т. Д. Application_StartДля настроек, которые применяются для всего времени выполнения приложения, таких как маршрутизация, фильтры, ведение журнала и так далее.

Пожалуйста, не применяйте никаких обходных решений, таких как статический .ctor или переключение в классический режим, если нет возможности переместить код из файла Startв BeginRequest. это должно быть выполнимо для подавляющего большинства ваших случаев.


7

Поскольку во время запуска приложения в конвейере больше нет контекста запроса, я не могу представить, как можно угадать, на каком сервере / порту может прийти следующий фактический запрос. Вы должны сделать это на Begin_Session.

Вот что я использую, когда не в классическом режиме. Накладные расходы незначительны.

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

Спасибо, это заставило мой сайт снова заработать после того, как он внезапно вышел из строя. Как ни странно, я не перешел с классического ASP.NET в пуле приложений - ошибка все равно появлялась. Добавление варианта этого кода (с использованием Interlocked.Exchange (ref int, int)) решило проблему.
John Källén

1
Первую строку этого ответа (это дубликат ...) следует удалить. Это не дубликат связанного сообщения, вопрос совсем другой. Он не запрашивал доступ к имени сервера при запуске приложения. Он только хотел, чтобы его общая логика ведения журнала не генерировала исключение в специальном случае application_start.
Frédéric

6

На основе подробных требований OP, описанных в комментариях , существует более подходящее решение. OP заявляет, что он желает добавить пользовательские данные в свои журналы с помощью log4net, данные, связанные с запросами.

Вместо того чтобы оборачивать каждый вызов log4net в настраиваемый централизованный вызов журнала, который обрабатывает получение данных, связанных с запросом (при каждом вызове журнала), log4net предоставляет контекстные словари для настройки дополнительных данных для регистрации. Использование этих словарей позволяет разместить данные журнала запросов для текущего запроса в событии BeginRequest, а затем отклонить его в событии EndRequest. Эти пользовательские данные будут полезны любому входу между ними.

А то, что не происходит в контексте запроса, не будет пытаться регистрировать данные, связанные с запросом, устраняя необходимость проверки доступности запроса. Это решение соответствует принципу, который предлагал Арман МакХитарян в своем ответе .

Для того, чтобы это решение работало, вам также потребуется дополнительная настройка ваших приложений log4net, чтобы они могли регистрировать ваши пользовательские данные.

Это решение можно легко реализовать в виде настраиваемого модуля расширения журнала. Вот пример кода для этого:

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

Добавьте его на свой сайт, пример конфигурации 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>

И настройте приложения для регистрации этих дополнительных свойств, пример конфигурации:

<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 за указание на то, что вы можете использовать HttpContext.Current.Items ["IP"] вместо HttpContext.Request.UserHostAddress. В случае пустого запроса это работает для получения данных - что меня спасло :) спасибо.
Сиелу

2

Вы можете обойти проблему, не переключаясь в классический режим, и по-прежнему использовать 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
   }

По какой-то причине статический тип создается с запросом в его HTTPContext, что позволяет сохранить его и сразу же повторно использовать в событии Application_Start.


Я не знаю .. При локальном запуске кажется, что он не «видит» порт, когда я пытаюсь использовать: initialRequest.Url.GetLeftPart (UriPartial.Authority); Придется искать другой путь.
justabuzz

Ужасно хакерский, но может помочь в некоторых безнадежных случаях. (Я немного сбалансирован между голосованием «против» или «за», поэтому я просто не голосую.)
Фредерик,

1

Я смог решить эту проблему, перейдя в «классический» режим из «интегрированного» режима.


0

У меня это сработало - если вам нужно войти в Application_Start, сделайте это, прежде чем изменять контекст. Вы получите запись в журнале без источника, например:

2019-03-12 09: 35: 43,659 INFO (null) - Приложение запущено

Обычно я регистрирую как Application_Start, так и Session_Start, поэтому я увижу более подробную информацию в следующем сообщении.

2019-03-12 09: 35: 45,064 INFO ~ / Leads / Leads.aspx - Сессия началась (локально)

        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

В Visual Studio 2012, когда я опубликовал решение по ошибке с опцией «отладка», я получил это исключение. С опцией «релиз» этого не произошло. Надеюсь, поможет.


-3

Вы можете использовать следующее:

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

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

-4

сделайте это в 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 + "/");
  // ...
}

работает как шарм. this.Context.Request есть ...

this.Request намеренно генерирует исключение на основе флага


5
-1: Прочтите вопрос: вот что дает сбой (с IIS> = 7 и интегрированным режимом)
Ричард

Вот что происходит, когда пираты теряют работу и пробуют себя в программировании :) Без обид,
чувак
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.