在这种情况下,请求不可用


113

我正在运行IIS 7集成模式,并且

在这种情况下,请求不可用

当我尝试在从调用的Log4Net相关函数中访问它时Application_Start。这是我的代码行

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

并且正在抛出异常以进行第二次比较。

除了检查HttpContext.Current.Request是否为null之外,我还能检查什么?


在iis7.5上运行runnig mvc时,发布了类似的问题@ 请求在此上下文异常中不可用

但那里也没有相关的答案。


2
如果我不采取安德鲁·哈尔(Andrew Hare)链接中建议的其他两种解决方案,你们是否建议添加try-catch块作为我的唯一选择?像尝试{((HttpContext.Current.Request.Headers [“ User_info”]!= null)log4net.MDC.Set(“ UserInfo”,HttpContext.Current.Request.Headers [“ User_info”]。ToString()); } catch(){}
维沙尔·塞斯

Answers:


79

请参阅IIS7集成模式:在此上下文中Application_Start中的请求不可用

“将请求在这种情况下不可用”异常是将ASP.NET应用程序移动到IIS 7.0上的集成模式时可能会收到的更常见的错误之一。如果您尝试访问启动应用程序的请求的HttpContext,则在global.asax文件中Application_Start方法的实现中会发生此异常。


2
:这种情况在这里更多的讨论stackoverflow.com/questions/1790457/...
jball

6
谢谢。我以前看过那个链接。它说:“基本上,如果您恰巧在Application_Start中访问请求上下文,则有两种选择:1)将应用程序代码更改为不使用请求上下文(推荐)。2)将应用程序移至经典模式(不推荐) )。” 他们还有其他选择吗?我的日志记录代码在DB中写入内容,例如“应用程序启动”,如果不通过请求,则应将这些字段设置为null而不是完全删除我的日志语句。
维沙尔·塞斯

我有相同的日志记录要求,如果上下文可用,请使用它来填充数据库,否则请不要将字段留空。(就我而言,不要将记录写到一个日志表中,但是如果有一种确定是否可用的好方法,这将有所帮助。)
Zarepheth 2014年

2
不喜欢它,但是将检查包装在try-catch中是唯一的选择,除了对我们的日志记录代码(和/或整个应用程序)进行重大重构之外
Zarepheth 2014年

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可以不虽然被定义RequestResponse被定义(I已经看到,在BeginRequest事件)。因此,如果您需要在自定义http模块中记录请求/响应,我的答案可能不合适。


1
此外,这里已经指出了缺点,我意识到这实际上不是解决OP在评论中解释的特定需求的方法。在此页面上查看我的其他答案。
2014年

1
这帮了我大忙,我只需要检查Request对象而不会引发异常。Ty
OverMars

17

这是非常经典的情况:如果最终不得不检查http实例提供的任何数据,则可以考虑将该代码移到BeginRequest事件下。

void Application_BeginRequest(Object source, EventArgs e)

在这里检查http标题,查询字符串等的正确位置…… Application_Start是适用于应用程序整个运行时的设置,例如路由,过滤器,日志记录等。

请不要应用任何解决方法,例如static .ctor或切换到Classic模式,除非无法将代码从Start移到BeginRequest。这对您的绝大多数情况都应该可行。


7

由于在应用程序启动期间管道中不再有Request上下文,所以我无法想象有任何方法可以猜测下一个实际请求可能来自哪个服务器/端口。您必须在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))解决了该问题。
JohnKällén2012年

1
此答案的第一行(这是重复的...)应删除。这不是链接文章的重复,问题完全不同。他没有要求在应用启动时访问服务器名称。他只愿意让他的通用日志记录逻辑在application_start特殊情况下不引发异常。
弗雷德里克

6

基于注释中解释的OP详细需求,存在更合适的解决方案。OP表示他希望使用log4net在其日志中添加自定义数据,这些数据与请求有关。

log4net并没有使用上下文字典来设置要记录的自定义附加数据,而不是将每个log4net调用包装到一个自定义的集中式日志调用中,该调用处理与请求有关的数据(在每个日志调用上)。使用这些字典可以将当前请求的请求日志数据放在BeginRequest事件中,然后在EndRequest事件中将其关闭。两者之间的任何登录都将从这些自定义数据中受益。

并且在请求上下文中未发生的事情将不会尝试记录与请求相关的数据,从而消除了测试请求可用性的需求。该解决方案与Arman McHitaryan在其回答中提出的原理相符。

为了使该解决方案有效,您还需要在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+ conf示例:

<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 2015年

非常骇人听闻,但在某些绝望的情况下可能会有所帮助。(我对此表示反对还是有点反对,所以我只好投票。)
弗雷德里克


0

这对我有用-如果您必须登录Application_Start,请在修改上下文之前进行。您将获得一个日志条目,没有任何来源,例如:

2019-03-12 09:35:43,659信息(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中,当我错误地使用“ debug”选项发布解决方案时,出现了此异常。使用“发布”选项,它从未发生。希望能帮助到你。


-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且集成模式)
理查德

当海盗失去工作并尝试编程时,会发生这种情况:)没
犯错
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.