管道模型–Asp.Net MVC篇 Http请求
我们自己写的程序,是怎样进行处理的?一个完整的HTTP请求流程:
一. 请求大致流程
拿一个实例了解整个流程的步骤:
-
用户浏览器输入地址
例如 http://www.csdn.net
-
DNS解析(域名供应商)
将输入的网址解析成IP+端口
-
请求到达服务器Server
IP可以在互联网上唯一定位一台服务器,而端口是用来确定进程的,端口还可以带有协议信息,用于穿过防火墙
-
HTTP.SYS服务接收HTTP请求
我们可以自己用IIS部署一个网站,模拟HTTP请求。顺序是部署网站----指定一个端口监听----请求到服务器----带了端口信息和协议----被HTTP.SYS监听到。HTTP.SYS是安装IIS时自动装上去的
-
IIS将请求转发给ISAPI
IIS不能处理我们写的代码,不会做业务的处理,它只能将我们的代码转发到对应的程序进行处理。
它里面有一个“处理映射程序”,这里配置的是IIS的处理方式,即请求是什么后缀名,就用哪种dll处理程序进行处理,其中*.cshtml、.aspx、.ashx都是由asp.net_isapi.dll来进行处理,如图:
因为IIS只是将请求根据后缀转发到不同的处理程序,所以我们甚至可以给java和php指定处理程序。比如将php转发给php_ISAPI,java转发给java_ISAPI,只要配置好就可以实现。
如果是js,css,html等静态文件,IIS是直接返回的。
这里有个小问题,聪明的你可能会说,像mvc这样 Home/Index,没有后缀怎么办?
IIS6和它之前都不支持mvc的,后来出现了mvc,没有后缀怎么写匹配?IIS会给没有后缀的加一个axd的后缀再处理,如图:
到了IIS7.0,就不需要这么做了。
IIS的应用程序池分为集成和经典,如图:
-
HttpWorkerRequest
在上一步,会将http请求包装成一个HttpWorkerRequest对象,通过pipeline传到HttpRuntime.ProcessRequest ()这里来,这里才是asp.net开发的入口,前面是系统帮我们做好的,我们程序员是从这里开始搞事情的!
HttpWorkerRequest的定义,如图:
-
HttpRuntime
HttpRuntime:Http运行时,ProcessRequest 是它下面的一个方法,ProcessRequest 方法需要一个HttpWorkerRequest参数,也就是上一步得到的。
二、HttpRuntime.ProcessRequest()
至于ProcessRequest是怎么处理的,就要进入“管道处理模型”了。
什么是“管道处理模型”呢?
就是一个请求进入到HttpRunTime之后,也就是从第7步开始,要做的事情,就叫做“管道处理模型”,因为前6步都是系统做好的,我们管不着,从第7步才开始运行我们写的代码。
System.Web.HttpRuntime 类下的ProcessRequest() 方法源码。
// System.Web.HttpRuntime
[AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
public static void ProcessRequest(HttpWorkerRequest wr) //传入参数是HttpWorkerRequest类型的
{
//如果传入null,抛出异常;
if (wr == null)
{
throw new ArgumentNullException("wr");
}
//如果没有使用管道模型,也抛出异常
if (HttpRuntime.UseIntegratedPipeline)
{
throw new PlatformNotSupportedException(SR.GetString("Method_Not_Supported_By_Iis_Integrated_Mode", new object[]
{
"HttpRuntime.ProcessRequest"
}));
}
//如果都OK,就访问下一个方法 ProcessRequestNoDemand(wr)
HttpRuntime.ProcessRequestNoDemand(wr);
}
传入参数是HttpWorkerRequest类型的,如果传入null,抛出异常;如果没有使用管道模型,也抛出异常,如果都OK,就访问下一个方法 ProcessRequestNoDemand(wr):
// System.Web.HttpRuntime
internal static void ProcessRequestNoDemand(HttpWorkerRequest wr)
{
RequestQueue requestQueue = HttpRuntime._theRuntime._requestQueue;
wr.UpdateInitialCounters();
if (requestQueue != null)
{
wr = requestQueue.GetRequestToExecute(wr);
}
if (wr != null)
{
HttpRuntime.CalculateWaitTimeAndUpdatePerfCounter(wr);
wr.ResetStartTime();
HttpRuntime.ProcessRequestNow(wr);
}
}
这里有个RequestQueue,因为Http请求也可以队列,如果队列不为空,就执行GetRequestToExecute(wr)方法进行处理,再看下面的ProcessRequestNow(wr)方法:
// System.Web.HttpRuntime
internal static void ProcessRequestNow(HttpWorkerRequest wr)
{
HttpRuntime._theRuntime.ProcessRequestInternal(wr);
}
它调用了ProcessRequestInternal(wr)方法。
ProcessRequestInternal(wr)方法是怎么做的呢?如果被释放了(disposingHttpRuntime),那么就发送一个503的错误,“Server Too Busy”,并用一个html页面来显示。如果没有被释放,就往下走,初始化一个HttpContext,它是Http请求上下文,如果初始化HttpContext失败了,就用html页面给用户返回一个400错误;
如果HttpContext初始化成功了,就把它拿到HttpApplicationFactory.GetApplicationInstance方法里面创建了一个HttpApplication对象。
// System.Web.HttpRuntime
private void ProcessRequestInternal(HttpWorkerRequest wr)
{
Interlocked.Increment(ref this._activeRequestCount);
if (this._disposingHttpRuntime)
{
try
{
wr.SendStatus(503, "Server Too Busy");
wr.SendKnownResponseHeader(12, "text/html; charset=utf-8");
byte[] bytes = Encoding.ASCII.GetBytes("<html><body>Server Too Busy</body></html>");
wr.SendResponseFromMemory(bytes, bytes.Length);
wr.FlushResponse(true);
wr.EndOfRequest();
}
finally
{
Interlocked.Decrement(ref this._activeRequestCount);
}
return;
}
HttpContext httpContext;
try
{
httpContext = new HttpContext(wr, false);
}
catch
{
try
{
wr.SendStatus(400, "Bad Request");
wr.SendKnownResponseHeader(12, "text/html; charset=utf-8");
byte[] bytes2 = Encoding.ASCII.GetBytes("<html><body>Bad Request</body></html>");
wr.SendResponseFromMemory(bytes2, bytes2.Length);
wr.FlushResponse(true);
wr.EndOfRequest();
return;
}
finally
{
Interlocked.Decrement(ref this._activeRequestCount);
}
}
wr.SetEndOfSendNotification(this._asyncEndOfSendCallback, httpContext);
HostingEnvironment.IncrementBusyCount();
try
{
try
{
this.EnsureFirstRequestInit(httpContext);
}
catch
{
if (!httpContext.Request.IsDebuggingRequest)
{
throw;
}
}
httpContext.Response.InitResponseWriter();
//创建了一个HttpApplication对象, 其本质是一个IHttpHandler类型对象
IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(httpContext);
if (applicationInstance == null)
{
throw new HttpException(SR.GetString("Unable_create_app_object"));
}
if (EtwTrace.IsTraceEnabled(5, 1))
{
EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, httpContext.WorkerRequest, applicationInstance.GetType().FullName, "Start");
}
if (applicationInstance is IHttpAsyncHandler)
{
IHttpAsyncHandler httpAsyncHandler = (IHttpAsyncHandler)applicationInstance;
httpContext.AsyncAppHandler = httpAsyncHandler;
httpAsyncHandler.BeginProcessRequest(httpContext, this._handlerCompletionCallback, httpContext);
}
else
{
applicationInstance.ProcessRequest(httpContext);
this.FinishRequest(httpContext.WorkerRequest, httpContext, null);
}
}
catch (Exception e)
{
httpContext.Response.InitResponseWriter();
this.FinishRequest(wr, httpContext, e);
}
}
每个请求都经过了上面的步骤,创建了一个HttpApplication对象,用这个HttpApplication对象来处理请求,HttpApplication是我们管道模型的核心。
通过上述步骤,终于执行到了我们写的代码,前面我们几乎没做什么,都是框架做的,我们也扩展不了。
三、HttpApplication
因为HttpApplication要处理各种不同的请求,每个请求也许要做相同的事情,也可能不同的事情,也可能要做的事情的顺序不同,我们要把共性的部分封装起来,所以就封装成了这么多的事件(event),这样做的好处就是,遇到不同的请求,我们可以把这些事件排列和组合起来,就能完成请求的处理。
HttpApplication对象是由Asp.net帮助我们创建的,它是asp.net中处理请求的重要对象。为了便于扩展,HttpApplication采用处理管道的方式进行处理,将处理的步骤分为多个步骤,每个步骤通过事件的形式暴露给程序员,这些事件按照固定的处理顺序依次触发,程序员通过编写事件处理方法就可以定义一个请求的扩展过程。
对于HttpApplication,到ASP.NET 4.0,提供了19个标准事件。
序号 | 事件名称 | 备注 |
---|---|---|
1 | BeginRequest | asp.net开始处理请求的第一个事件,表示处理的开始 |
2 | AuthenticateRequest | 验证请求,开始检查用户的身份,一般用来取得请求的用户信息 |
3 | PostAuthenticateRequest | 已经获取请求的用户信息,用户信息已经检查完成,检查完成后可以用 HttpContext的User属性获取到,类型为System.Security.Principal.IPrincipal |
4 | AuthorizeRequest | 开始进行用户权限检查,如果用户没有通过上面的安全检查,一般情况下直接跳至 EndRequest 事件 |
5 | PostAuthorizeRequest | 用户请求已经获得授权 |
6 | ResolveRequestCache | 获取以前处理缓存的处理结果,如果以前缓存过,那么不用再进行请求的处理工作,直接返回缓存的结果 |
7 | PostResolveRequestCache | 已经完成缓存的处理工作,缓存检查结果 |
创建被请求的页面类对象(aspx/ashx) | ||
8 | PostMapRequestHandler | 已经根据用户的请求,创建了请求的处理器对象(IHttpHandler) |
9 | AcquireRequestState | 获取请求的状态,一般用于session |
10 | PostAcquireRequestState | 已经获得了session |
11 | PreRequestHandlerExecute | 准备执行处理程序,即调用 HttpContext 中的Handler 属性的 ProcessRequest 方法 |
12 | PostRequestHandlerExecute | 已经执行了处理程序 |
13 | ReleaseRequestState | 准备释放请求的状态 |
14 | PostReleaseRequestState | 已经释放了请求的状态 |
15 | UpdateRequestCache | 更新缓存 |
16 | PostUpdateRequestCache | 已经更新完了缓存 |
17 | LogRequest | 请求的日志操作 |
18 | PostLogRequest | 已经完成请求的日志操作 |
19 | EndRequest | 本次请求处理完成 |
20 | Error | |
21 | RequestCompleted | |
22 | PreSendRequestHeaders | (.net 4.0 新增)可以根据发送的 Header 来动态设置一些参数,比如,如果通过 Content-Type 参数获知发送的内容是 text/html 网页,那么通过启用输出的压缩来提高网络的传输速度。这个操作可以通过设置一个特殊的 Header 来通知浏览器 |
23 | PreSendRequestContent | (.net 4.0 新增)如果配置了输出到客户端的压缩,那么可以在这个事件中包装输出到浏览器的流以实现输出的压缩 |
其中BeginRequest 和 EndRequest 是方便我们做扩展的,可以在这两个方法里面加上我们要的触发动作。
PostMapRequestHandler 就是把我们的请求创建一个处理器对象,我们写的MVC,Webform,都在它里面,要让它来实际执行的。
执行HtttpRuntime.ProcessRequest进入程序入口,基于HttpWorkerRequest创建HttpRequset; 继续往后是有IhttpHandler来执行—HttpApplication来执行;
如果HttpApplication中就只是编写处理业务逻辑的代码;
HttpApplication内部肯定有固定的逻辑;也有千千万万的开发者需要扩展的地方;事件:观察者模式;
可以把不变的逻辑(公共逻辑)写在HttpApplication内部,可变的逻辑就可以通过定义事件暴露出去;让别人来注册动作到事件中;
在执行的时候,按照事件的顺序来执行;从而达到框架的可扩展!
在事件中的动作—谁来注册呢? 是HttpMoudule
四、HttpMoudule
先写一段代码,用反射的方式获取所有系统自带的HttpApplication 的 Event事件
public ViewResult Events()
{
//获取当前上下文的HttpApplication实例
HttpApplication app = base.HttpContext.ApplicationInstance;
List<SysEvent> sysEventsList = new List<SysEvent>();
int i = 1;
foreach (EventInfo e in app.GetType().GetEvents())
{
sysEventsList.Add(new SysEvent()
{
Id = i++,
Name = e.Name,
TypeName = e.GetType().Name
});
}
return View(sysEventsList);
}
运行一下,25个Event事件:
先说一下,HttpModule是什么:对HttpApplication做的扩展事件(上面图上,都是HttpApplication原本有的事件),Init方法,参数是HttpApplication。
public class MyCustomModule : IHttpModule
{
public void Dispose()
{
//此处放置清除代码。
}
public void Init(HttpApplication application)
{
application.AcquireRequestState += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "AcquireRequestState "));
application.AuthenticateRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "AuthenticateRequest "));
application.AuthorizeRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "AuthorizeRequest "));
application.BeginRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "BeginRequest "));
application.Disposed += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "Disposed "));
application.EndRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "EndRequest "));
application.Error += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "Error "));
application.LogRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "LogRequest "));
application.MapRequestHandler += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "MapRequestHandler "));
application.PostAcquireRequestState += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostAcquireRequestState "));
application.PostAuthenticateRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostAuthenticateRequest "));
application.PostAuthorizeRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostAuthorizeRequest "));
application.PostLogRequest += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostLogRequest "));
application.PostMapRequestHandler += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostMapRequestHandler "));
application.PostReleaseRequestState += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostReleaseRequestState "));
application.PostRequestHandlerExecute += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostRequestHandlerExecute "));
application.PostResolveRequestCache += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostResolveRequestCache "));
application.PostUpdateRequestCache += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PostUpdateRequestCache "));
application.PreRequestHandlerExecute += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PreRequestHandlerExecute "));
application.PreSendRequestContent += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PreSendRequestContent "));
application.PreSendRequestHeaders += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "PreSendRequestHeaders "));
application.ReleaseRequestState += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "ReleaseRequestState "));
application.RequestCompleted += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "RequestCompleted "));
application.ResolveRequestCache += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "ResolveRequestCache "));
application.UpdateRequestCache += (s, e) => application.Response.Write(string.Format("<h1 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h1><hr>", DateTime.Now.ToString(), "UpdateRequestCache "));
}
}
好了,自定义的MyCustomModule写好了,但是还不能使用,要去webconfig注册, 注册后,每个页面都会执行这个Module。
运行一下,发现注册后比以前多了一些东西:
MyCustomModule被从头到尾执行了一遍,这些蓝色的文字,说明在这个module中执行了哪些事件以及执行的顺序,而以前通过反射找到HttpApplication中事件(ShowEvents方法),是在PreRequstHandlerExecute方法中执行的,执行完后,还进行了收尾操作,比如PostRequestHandlerExecute(已经执行了处理程序),ReleaseRequestState(释放请求的状态),PostReleaseRequestState(已经释放了请求的状态)等等。
好了,上面就是我们自定义的Module,现在看看框架自带的Module,打开 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\web.config
<httpModules>
<add name="OutputCache" type="System.Web.Caching.OutputCacheModule"/>
<add name="Session" type="System.Web.SessionState.SessionStateModule"/>
<add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule"/>
<add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule"/>
<add name="PassportAuthentication" type="System.Web.Security.PassportAuthenticationModule"/>
<add name="RoleManager" type="System.Web.Security.RoleManagerModule"/>
<add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule"/>
<add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule"/>
<add name="AnonymousIdentification" type="System.Web.Security.AnonymousIdentificationModule"/>
<add name="Profile" type="System.Web.Profile.ProfileModule"/>
<add name="ErrorHandlerModule" type="System.Web.Mobile.ErrorHandlerModule, System.Web.Mobile, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
<add name="ServiceModel" type="System.ServiceModel.Activation.HttpModule, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule"/>
<add name="ScriptModule-4.0" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</httpModules>
看到没?有OutputCache,Session,WindowsAuthentication等,这些都是.net4.0框架自己注册好了的Module,是全局的,运行在这台服务器上的Web程序都要用到这个config,每个页面用到了这些Module。但是这里列的一些Module我们是用不上的,比如WindowsAuthentication,FormsAuthentication,PassportAuthentication,可以干掉的,不过不要在这个全局的webconfig中干掉,可以在每个项目的webconfig中干掉,比如刚才项目的webcongif中可以加这么一些代码干掉不要的Module
有没有干掉呢?
写一个方法,获取全部的Module,包括我们扩展的MyCustomModule,还有框架自带的,看效果:
public ViewResult Modules()
{
HttpApplication app = base.HttpContext.ApplicationInstance; //获取当前上下文的HttpApplication实例
List<SysModules> sysModulesList = new List<SysModules>();
int i = 1;
foreach (string name in app.Modules.AllKeys)
{
sysModulesList.Add(new SysModules()
{
Id = i++,
Name = name,
TypeName = app.Modules[name].ToString()
});
}//1 我们自定义配置的config 2 来自于.Net框架的配置
return View(sysModulesList);
}
运行一下,发现在webconfig中remove的Module,它们都不出现了:
总结一下,HttpModule要实现IHttpModule接口,在webconfig注册,里面给HttpApplication事件去添加动作,并且HttpModule是每个页面(每次请求)都要执行的。
五、HttpModule 应用
-
权限认证:每个请求都经过Module,所以做权限认证很好
-
URL转发
-
新创建一个BaseModule
public class BaseModule : IHttpModule { /// <summary> /// Init方法仅用于给期望的事件注册方法 /// </summary> /// <param name="httpApplication"></param> public void Init(HttpApplication httpApplication) { httpApplication.BeginRequest += new EventHandler(context_BeginRequest);//Asp.net处理的第一个事件,表示处理的开始 httpApplication.EndRequest += new EventHandler(context_EndRequest);//本次请求处理完成 } // 处理BeginRequest 事件的实际代码 private void context_BeginRequest(object sender, EventArgs e) { HttpApplication application = (HttpApplication)sender; HttpContext context = application.Context; string extension = Path.GetExtension(context.Request.Url.AbsoluteUri); if (string.IsNullOrWhiteSpace(extension) && !context.Request.Url.AbsolutePath.Contains("Verify")) { context.Response.Write(string.Format("<h4 style='color:#00f'>来自BaseModule 的处理,{0}请求到达</h4><hr>", DateTime.Now.ToString())); } //处理地址重写 if (context.Request.Url.AbsolutePath.Equals("/Pipe/Some", StringComparison.OrdinalIgnoreCase)) context.RewritePath("/Pipe/Handler"); } ...... }
注意这两句,先获取到了HttpApplication,后来又获取到了HttpContext,有了HttpContext就有了全世界。
HttpApplication application = (HttpApplication)sender; HttpContext context = application.Context;
-
注册BaseModule
<httpModules> <add name="BaseModule" type="XXX.Web.Core.PipeLine.BaseModule,XXX.Web.Core"/> </httpModules>
-
访问 “/Pipe/Some”
如果访问 “/Pipe/Some”,Module会偷偷跳到"/Pipe/Handler",而且浏览器的地址栏不改变,还是"/Pipe/Some"。
MVC其实就是Module的扩展,即上面说过的UrlRoutingModule, MVC框架的诞生对webform没有影响,只是在原来框架流程上增加了一个UrlRoutingModule,它会把我们的请求做一次映射处理,指向MvcHandler,MVC路由为什么能生效啊?就是利用了UrlRoutingModule,处理方式和上面说的Url转发是一样的。
伪静态也可以这样做,先在IIS里面给后缀指定处理程序asp.net_isapi(上面有介绍),请求进到HttpApplication后,我们扩展一个Module,在里面建立一些跳转页面的规则。
-
-
反爬虫
每个请求都要经过Module,所以可以记录每个IP,如果某个IP请求太频繁,那么就不让它访问原始页面,让它访问一个需要输入验证码的页面,验证通过了才能继续访问。
但也有Module不适合的场景,比如对一些特殊请求的处理。Module是每个请求都会执行的,单独给某些请求服务的不合适。
又总结一下,管道处理模型就是请求进入System.Web(HttpRuntime.processrequest)之后,那些处理的类、方法、过程,这些就叫管道处理模型,mvc只是其中很小的一部分。
六、Module的扩展
-
新建一个GlobalModule.cs
public class GlobalModule : IHttpModule { public event EventHandler GlobalModuleEvent; /// <summary> /// Init方法仅用于给期望的事件注册方法 /// </summary> /// <param name="httpApplication"></param> public void Init(HttpApplication httpApplication) { httpApplication.BeginRequest += new EventHandler(context_BeginRequest);//Asp.net处理的第一个事件,表示处理的开始 httpApplication.EndRequest += new EventHandler(context_EndRequest);//本次请求处理完成 } // 处理BeginRequest 事件的实际代码 void context_BeginRequest(object sender, EventArgs e) { HttpApplication application = (HttpApplication)sender; HttpContext context = application.Context; string extension = Path.GetExtension(context.Request.Url.AbsoluteUri); if (string.IsNullOrWhiteSpace(extension) && !context.Request.Url.AbsolutePath.Contains("Verify")) context.Response.Write(string.Format("<h1 style='color:#00f'>来自GlobalModule 的处理,{0}请求到达</h1><hr>", DateTime.Now.ToString())); //处理地址重写 if (context.Request.Url.AbsolutePath.Equals("/Pipe/Some", StringComparison.OrdinalIgnoreCase)) context.RewritePath("/Pipe/Handler"); if (GlobalModuleEvent != null) GlobalModuleEvent.Invoke(this, e); } // 处理EndRequest 事件的实际代码 void context_EndRequest(object sender, EventArgs e) { HttpApplication application = (HttpApplication)sender; HttpContext context = application.Context; string extension = Path.GetExtension(context.Request.Url.AbsoluteUri); if (string.IsNullOrWhiteSpace(extension) && !context.Request.Url.AbsolutePath.Contains("Verify")) context.Response.Write(string.Format("<hr><h1 style='color:#f00'>来自GlobalModule的处理,{0}请求结束</h1>", DateTime.Now.ToString())); } public void Dispose() { } }
注意看这里:
public event EventHandler GlobalModuleEvent;
本来Module是用来添加事件的,比如:
httpApplication.BeginRequest += new EventHandler(context_BeginRequest); //Asp.net处理的第一个事件,表示处理的开始
这里结果又给Module配置了一个事件GlobalModuleEvent。那怎样让这个GlobalModuleEvent生效呢?=> 在global里面添加代码
-
注册GlobalModule
<httpModules> <add name="GlobalModule" type="XXX.Web.Core.PipeLine.GlobalModule,XXX.Web.Core"/> </httpModules>
-
在Global里面添加代码
/// <summary> /// HttpModule注册名称_事件名称 /// 约定的 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void GlobalModule_GlobalModuleEvent(object sender, EventArgs e) { Response.Write("<h3 style='color:#800800'>来自 Global.asax 的文字 GlobalModule_GlobalModuleEvent</h2>"); }
其中方法名 = webconfig中注册的Module名称 + ‘_’+ Module中的事件名称。这样,这个方法就会自动触发。
-
访问站点查看效果
有没有类似的?有的,大家应该见过这个,也是在Global里面。
例如Session:
protected void Session_Start(object sender, EventArgs e)
{
// 在新会话启动时运行的代码
Console.WriteLine("Session_Start 啥也不干");
logger.Info("Session_Start");
}
protected void Session_End(object sender, EventArgs e)
{
// 在会话结束时运行的代码。
// 注意: 只有在 Web.config 文件中的 sessionstate 模式设置为
// InProc(默认内存里) 时,才会引发 Session_End 事件。如果会话模式设置为 StateServer
// 或 SQLServer,则不会引发该事件。
Console.WriteLine("Session_End 啥也不干");
logger.Info("Session_End");
}
Start和End就是Session这个Module里面定义的Event。有证据吗?有的!先看看全局的webconfig,session注册过。
然后通过反编译工具,找到SessionState这个类,从它内部找到了Start和End。
这样做的好处是:我们封装了Module之后,Module里面还有要扩展的,就可以做成Event,在global里面去实现,并且也只能在global里面实现,什么时候执行这个Event呢?根据自己的需求。使用套路如图:
七、其他
Application_Error是处理异常的,filter也是处理异常的,但前提是要进入了控制器,如果是cshtml出错了,或者页面不存在,filter就管不到了,但可以被Application_Error处理,它可以捕获整个网站的异常,不管是控件级事件、页面级事件还是请求级事件,都可以get到。
/// <summary>
/// 请求出现异常,都可以处理
/// 也可以完成全局异常处理
/// filter只能处理控制器里面的异常
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void Application_Error(object sender, EventArgs e)
{
logger.Info("Application_Error");
Response.Write("出错");
Server.ClearError();
}