昨天抱怨了一下HttpContext的设计,在Global.asax的Application_Start()中访问Context.Request,会引发“Request is not available in this context”异常(注:程序运行于IIS集成模式)。
有园友反驳说在Application_Start()中不应该去访问Context.Request。我对这个反驳的理解就是HttpApplication还没启动好,还没开始正式处理请求,你就猴急着访问Request干吗?知道不,这和排队一样,有个先来后到。先有HttpApplication,然后才有Request。
我后来想想,不对呀!我们发布一个ASP.NET网站时,并没有先接通电源,按一个启动按钮让HttpApplication启动起来,然后打开大门大喊:“HTTP请求们,可以进来了”。
实际情况是,发布网站之后,至少有一个请求,程序才会启动,才会有HttpApplication。明明是先有Request,才会有HttpApplication。
而且前一篇文章中,我们的需求是在Application_Start()中获取当前Request的URL,Application_Start是由Request而生,有Request的地方就有URL,这个需求合情合理啊!
在程序员的世界,我们需要反驳,我们也需要对反驳的反抗。
于是,今天把IIS应用程序池改为传统(Classic)模式,继续在Application_Start()中访问Context.Request。奇迹出现了!竟然可以正常访问Context.Request,拿到想要的Context.Request.Url.AbsoluteUri。这至少说明了,“在Application_Start()中访问Context.Request”的需求是合理的。如果不合理,那么在传统Classic模式也不应该能访问,否则就是一个自相矛盾的设计。
写代码不是辩论,找到例子证明自己的观点就行了。解决问题或者找到问题的原因才是王道。
在前一篇文章中,我们知道罪魁祸首是HttpContext的HideRequestResponse属性,再拿出来示众一下:
internal bool HideRequestResponse;
public HttpRequest Request
{
get
{
if (this.HideRequestResponse)
{
throw new HttpException(SR.GetString("Request_not_available"));
}
return this._request;
}
}
既然在IIS集成模式下才会出现这个异常,那就是在IIS集成模式下,ASP.NET Runtime将HideRequestResponse设置为了true。我们需要找到ASP.NET Runtime在哪里干了这个“勾当”。这就要借助于.NET世界的侦探新星 —— ILSpy。
在ILSpy中,选中HttpContext的HideRequestResponse属性,点击“Analyze”,看看谁调用了HideRequestResponse并修改了它的值,然后一个一个去侦查。。。侦查的过程这里就省略了。。。直接看我们的侦查结果,先看下面的ILSpy截图:
既然我们是在Application_Start方法中调用Context.Request,那我们首先就要知道Application_Start是在哪执行。实际就是上图所示的FireApplicationOnStart()方法,反编译出来的代码如下:
// System.Web.HttpApplicationFactory
private void FireApplicationOnStart(HttpContext context)
{
if (this._onStartMethod != null)
{
HttpApplication specialApplicationInstance = this.GetSpecialApplicationInstance();
specialApplicationInstance.ProcessSpecialRequest(context, this._onStartMethod,
this._onStartParamCount, this, EventArgs.Empty, null);
this.RecycleSpecialApplicationInstance(specialApplicationInstance);
}
}
_onStartMethod就是在Global.asax中定义的Application_Start()方法,从上面的代码中可以看出,_onStartMethod是实际在specialApplicationInstance.ProcessSpecialRequest中被执行的,进入ProcessSpecialRequest看个究竟,代码如下(代码已精简,...表示被省略的代码,重点看红色字体部分):
// System.Web.HttpApplication
internal void ProcessSpecialRequest(...)
{
this._context = context;
if (HttpRuntime.UseIntegratedPipeline && this._context != null)
{
this._context.HideRequestResponse = true;
}
...
using (new HttpContextWrapper(context))
{
using (new ApplicationImpersonationContext())
{
try
{
//调用Application_Start()方法
method.Invoke(...)
...
}
catch (Exception ex)
{
...
}
finally
{
...
if (HttpRuntime.UseIntegratedPipeline && this._context != null)
{
this._context.HideRequestResponse = false;
}
this._hideRequestResponse = false;
...
}
}
}
}
从上面的红色字体部分可以看出,真相就在这里,当运行于IIS集成模式时,HttpRuntime.UseIntegratedPipeline就等于true,然后HttpContext.HideRequestResponse被设置为true,然后执行Application_Start()中的代码,然后就引发“Request is not available in this context”异常。
这篇文章只是对“Request is not available in this context”问题的进一步探索,找出了问题发生的具体地方。至于为什么微软要这么设计?有没有办法在Application_Start()中获取当前请求的URL?即使不能获取,有没有办法避开这个异常?目前我们不知道答案。