Запит недоступний у цьому контексті


113

Я запускаю IIS 7 інтегрований режим, і я отримую

Запит недоступний у цьому контексті

коли я намагаюся отримати доступ до нього в функції, пов'язаної з Log4Net, з якої викликається Application_Start. Це рядок коду, який я маю

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

і виняток викидається для другого порівняння.

Що ще я можу перевірити, окрім перевірки HttpContext.Current.Request for null ??


Аналогічне запитання розміщено @ Запит недоступний у цьому контексті за винятком, коли runnig mvc на iis7.5

але відповідної відповіді також немає.


2
Ви б порекомендували додати блок пробного лову як єдиний варіант, якщо я не прийму два інші рішення, як пропонується у посиланні від Ендрю Зайця? як-от спробуйте {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) Перемістіть програму в класичний режим (НЕ рекомендується ) ". Немає інших варіантів? Мій код реєстрації записує речі в БД, наприклад, Запуск програми, якщо не через запит, то ці поля слід встановити на нуль, а не повністю видалити мій протокол журналу.
Вішал Сет

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

2
Це не сподобалось, але обгортання чека в
пробному

47
Чи є спосіб сказати, чи потрапляєте ви в ситуацію, коли запит буде недоступним? Якась властивість HttpContext, яка знає про це? Чому він кидає виняток замість того, щоб просто повернути Ніщо, як і багато інших властивостей?
Джошуа Франк

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
Крім того, про недоліки, про які вже говорилося тут, я зрозумів, що це дійсно не шлях до конкретних потреб, пояснених ОП у коментарі. Дивіться іншу мою відповідь на цій сторінці.
Фредерік

1
Це зробило для мене хитрість, мені просто потрібно було перевірити об’єкт Запити, не викидаючи виняток. 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)) вирішило проблему.
Джон Келлен

1
Перший рядок цієї відповіді (це дублікат ...) слід видалити. Це не дублікат пов’язаної публікації, питання зовсім інше. Він не просив отримати доступ до імені сервера під час запуску програми. Він хотів лише мати свою загальну логіку ведення журналу, щоб не кидати виключення у спеціальному випадку application_start.
Фредерік

6

На основі детальних потреб ОР, пояснених у коментарях , існує більш відповідне рішення. ОП заявляє, що хоче додати користувацькі дані у свої журнали з 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


Я не знаю .. Запуск локально, здається, що він не "бачить" порт, коли я намагаюся використовувати: InitiRequest.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

У візуальній студії 2012 року, коли я помилково опублікував рішення з опцією 'debug', я отримав цей виняток. З опцією 'release' цього ніколи не виникало. Сподіваюся, це допомагає.


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