前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎。这两天算是理解了一小部分,这里先记录下来,也给须要的园友一个参考,奈何博主技术有限,若有理解不妥之处,还但愿你们斧正,博主感激涕零!html
本文原创地址:http://www.cnblogs.com/landeanfen/p/5989092.htmlgit
MVC源码学习系列文章目录:github
最近园子里Asp.Net Core火了一阵,无论微软的开源动做有多么迟缓,仍是但愿微软可以给力一次。做为Core的主要Web框架——MVC,虽然已经开源,可是读起来着实费劲,而且感受不少核心部件都找不到。因而只能经过Reflector去反编译MVC5的组件以及参考博客园Fish Li等大神的文章去学习下MVC5的原理。web
10月26日更新:感谢园友Adming在评论中提醒,原来Asp.net Core Mvc和Asp.net Mvc 5的原理已经彻底不一样,难怪在Core Mvc的源码里面已经找不到MvcHandler、UrlRoutingModule等核心部件了呢,此系列文章就先学习下MVC5的原理,等之后有空了再来研究Core MVC吧。编程
Asp.Net Core MVC的开源地址:https://github.com/aspnet/Mvc设计模式
Asp.net MVC的开源地址:http://aspnetwebstack.codeplex.com/SourceControl/latest浏览器
以前的文章有介绍MVC的路由机制,其实路由机制算是MVC的原理的核心之一。在此咱们仍是要不厌其烦再来谈谈整个过程,由于这是理解MVC原理不可逾越的鸿沟。当咱们收到一个URL的请求时,服务端收到请求,主要经历如下几个步骤:缓存
附上一张大体的流程图:mvc
纵观整个过程,看上去很复杂,各类对象缠绕,看得人晕晕的。其实若是你静下心来仔细研读MVC的源码你会发现其实并无想像中的那般复杂,请有点耐心听博主慢慢道来。app
一、整个过程有两个核心的组件,文中博主用红色标记了出来:UrlRoutingModule和MvcHandler,上文提到的各个过程都和两个组件有紧密的联系。而这两个组件分别继承至IHttpModule和IHttpHandler接口,熟悉Asp.net管线事件的朋友应该会记得这两个接口,在管道事件里面这两个接口扮演着重要角色。要理解MVC的上述原理,必需要先理解这两类接口的原理以及使用。
二、UrlRoutingModule的做用能够理解为经过一系列的与路由相关的组件去解析当前请求的Controller与Action名称,其实简单点理解,好比咱们请求http://localhost:8080/Home/Index这个url的时候,UrlRoutingModule拦截到这个请求,而后经过一系列的方式获得这里的“Home”和“Index”,这样理解有没有简单一点呢。
三、MvcHandler的做用就更加直接,上述经过拦截组件获得了请求的Controller和Action的名称,MvcHandler组件将当前请求的Controller名称反射获得对应的控制器对象,而后执行对应的Action方法。好比仍是上述http://localhost:8080/Home/Index这个请求,经过字符串“Home”反射成为Home这个类型的控制器对象,而后调用这个对象的Index()方法。
四、综上,联合这两个组件来理解,UrlRoutingMudule组件的主要做用是解析当前的Controller与Action名称,MvcHandler的做用是将获得的Controller名称激活,获得具体的Controller对象,而后执行对应的Action方法。
因此,要理解MVC的原理,必需要了解这两个组件的基本原理以及做用。下面就根据这两个组件分别展开说明,相信理解了下面的内容,你对mvc的原理会有一个新的认识。
上文说过MvcHandler是继承至IHttpHandler接口的!为何这里大标题会用HttpHandler而不是MvcHandler呢?由于博主以为,HttpHandler实在是过重要了,首先得理解了HttpHandler这么一个大的东西,而后再来看具体的MvcHandler才有意义。
总而言之,HttpHandler只是一个逻辑称谓,它并不具体存在。而IHttpHandler和MvcHandler是.net framework里面具体存在的接口和实现类,是前者的表现形式。
作过Webform开发的园友应该记得,在asp.net的页面生命周期里面,一共有24个管线事件,完整的管线事件可参考MSDN文档:
在处理该请求时将由 HttpApplication 类执行如下事件。 但愿扩展 HttpApplication 类的开发人员尤为须要注意这些事件。 1. 对请求进行验证,将检查浏览器发送的信息,并肯定其是否包含潜在恶意标记。 有关更多信息,请参见 ValidateRequest 和脚本侵入概述。 2. 若是已在 Web.config 文件的 UrlMappingsSection 节中配置了任何 URL,则执行 URL 映射。 3. 引起 BeginRequest 事件。 4. 引起 AuthenticateRequest 事件。 5. 引起 PostAuthenticateRequest 事件。 6. 引起 AuthorizeRequest 事件。 7. 引起 PostAuthorizeRequest 事件。 8. 引起 ResolveRequestCache 事件。 9. 引起 PostResolveRequestCache 事件。 10. 根据所请求资源的文件扩展名(在应用程序的配置文件中映射),选择实现 IHttpHandler 的类,对请求进行处理。 若是该请求针对从 Page 类派生的对象(页),而且须要对该页进行编译,则 ASP.NET 会在建立该页的实例以前对其进行编译。 11. 引起 PostMapRequestHandler 事件。 12. 引起 AcquireRequestState 事件。 13. 引起 PostAcquireRequestState 事件。 14. 引起 PreRequestHandlerExecute 事件。 15. 为该请求调用合适的 IHttpHandler 类的 ProcessRequest 方法(或异步版 IHttpAsyncHandler.BeginProcessRequest)。 例如,若是该请求针对某页,则当前的页实例将处理该请求。 16. 引起 PostRequestHandlerExecute 事件。 17. 引起 ReleaseRequestState 事件。 18. 引起 PostReleaseRequestState 事件。 19. 若是定义了 Filter 属性,则执行响应筛选。 20. 引起 UpdateRequestCache 事件。 21. 引起 PostUpdateRequestCache 事件。 22. 引起 EndRequest 事件。 23. 引起 PreSendRequestHeaders 事件。 24. 引起 PreSendRequestContent 事件。
这里不可能把每一个管线事件将清楚,可是在整个管线事件中,有两个重要的角色就是HttpHandler和HttpModule。在这些事件中,第10个事件【根据所请求资源的文件扩展名(在应用程序的配置文件中映射),选择实现 IHttpHandler 的类,对请求进行处理】 是HttpHandler建立的地方。关于WebForm里面HttpHandler建立的详细过程,这里就不展开说了,若是有兴趣能够参考http://www.cnblogs.com/fish-li/archive/2012/01/29/2331477.html。
首先仍是来看看IHttpHandler的定义
public interface IHttpHandler { // 定义一个处理当前http请求的方法 void ProcessRequest(HttpContext context); // 指示当前实例是否能够再次使用 bool IsReusable { get; } }
接口的定义很简单,ProcessRequest()方法里面传一个当前请求的上下文对象去处理当前的http请求。
为了处理异步请求,Framework里面还定义了一个异步的IHttpHandler接口:
public interface IHttpAsyncHandler : IHttpHandler { // Methods IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData); void EndProcessRequest(IAsyncResult result); }
接口的两个方法应该也不难理解。
咱们已经说了,HttpHandler的主要做用是处理http请求,原来在作webform的时候应该都写事后缀ashx的通常处理程序吧,这个通常处理程序就是经过实现IHttpHandler接口去实现的。咱们是否曾经也写过相似这样的代码,新建一个TestHttpHandler.ashx文件,代码以下:
public class TestHttpHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; var username = context.Request.QueryString["username"]; var password = context.Request.QueryString["password"]; if (username == "admin" && password == "admin") { context.Response.Write("用户admin登陆成功"); } else { context.Response.Write("用户名或者密码错误"); } } public bool IsReusable { get { return false; } } }
而后运行,经过http://localhost:16792/TestHttpHandler.ashx?username=admin&password=admin去访问通常处理程序,便可获得正确的结果。
固然,除了这个,还有咱们最多见的aspx页面。
public partial class TestPage : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } }
将Page类转到定义:
发现原来Page类也是继承至IHttpHandler,这就是为何咱们能够经过地址http://localhost:16792/TestPage.aspx来访问这个页面的缘由。固然,子类中的ProcessRequest()方法并无显示的声明出来,由于在Page类里面已经有一个virtue的虚方法,若是须要,你也能够在TestPage这个类里面显示声明:
public partial class TestPage : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } public void ProcessRequest(HttpContext context) { context.Response.Write("你好"); } }
而后你会发现这个时候请求会进到ProcessRequest()方法,而不会进到Page_Load()里面了,至于缘由,这和Page类里面的封装有关系。固然这不是本文的重点,本文要说明的是全部实现了IHttpHandler接口的类型均可以在ProcessRequest()方法里面处理当前http请求。
固然,除了ashx和aspx之外,还有一类http的服务接口处理文件asmx也和IHttpHandler有着不可分割的联系,能够说,在asp.net里面,只要是处理Http请求的地方,IHttpHandler几乎“无处不在”。
固然,除了上述asp.net自带的HttpHandler以外,咱们也能够自定义HttpHandler处理特定的请求。好比咱们新建一个TestMyHandler.cs页面:
public class TestMyHandler:IHttpHandler { public bool IsReusable { get { return false; } } public void ProcessRequest(HttpContext context) { context.Response.Write("从asex页面进来"); //throw new NotImplementedException(); } }
固然,要使用这个自定义的Handler须要在web.config里面加上配置。(PS:这部分是博主后来加上的,因此直接用正确的配置)
<system.webServer> <handlers> <add name="asex" verb="*" path="*.asex" type="MyTestMVC.TestMyHandler, MyTestMVC" preCondition="integratedMode" /> </handlers> </system.webServer>
这个配置的意思是全部的url以asex结尾的请求都交给TestMyHandler这个类去处理。获得效果:
上文介绍了那么多IHttpHandler的用法,都是在WebForm里面的一些实现,咱们知道了全部实现了IHttpHandler的类均可以处理Http请求。一样在MVC里面,也定义了一个实现IHttpHandler接口的类型——MvcHandler,用于处理当前的http请求。经过反编译工具能够看到:
public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState { // 省略若干字段// 全部方法 static MvcHandler(); public MvcHandler(RequestContext requestContext); protected internal virtual void AddVersionHeader(HttpContextBase httpContext); protected virtual IAsyncResult BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, object state); protected internal virtual IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state); protected internal virtual void EndProcessRequest(IAsyncResult asyncResult); private static string GetMvcVersionString(); protected virtual void ProcessRequest(HttpContext httpContext); protected internal virtual void ProcessRequest(HttpContextBase httpContext); private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory); private void RemoveOptionalRoutingParameters(); IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData); void IHttpAsyncHandler.EndProcessRequest(IAsyncResult result); void IHttpHandler.ProcessRequest(HttpContext httpContext); // 省略若干属性 }
MvcHandler实现了IHttpHandler、 IHttpAsyncHandler两个接口,异步请求这里先不作介绍。重点仍是来看看ProcessRequest()方法
将HttpContext转换为HttpContextBase对象,继续转到定义。
这里声明了一个IController和IControllerFactory对象,经过this.ProcessRequestInit()方法建立具体的Controller实例。咱们将ProcessRequestInit()方法转到定义
咱们将代码复制出来,写入相应的注释:
private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) { //1.获得当前的上下文 HttpContext current = HttpContext.Current; if (current != null && ValidationUtility.IsValidationEnabled(current) == true) ValidationUtility.EnableDynamicValidation(current); this.AddVersionHeader(httpContext); this.RemoveOptionalRoutingParameters(); //2.从路由对象RouteData中获取当前请求的Controller名称 string requiredString = this.RequestContext.RouteData.GetRequiredString("controller"); //3.获得Controller工厂对象 factory = this.ControllerBuilder.GetControllerFactory(); //4.根据当前RequestContext对象,从Controller工厂建立具体的Controller对象 controller = factory.CreateController(this.RequestContext, requiredString); if (controller == null) throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_FactoryReturnedNull, new object[] { factory.GetType(), requiredString })); }
经过上文的注释很好理解整个控制器的实例化过程。本打算看下Controller工厂如何建立以及控制器如何实例化的,奈何这部分反编译不了。咱们暂且理解为反射吧,这些实现细节并不影响咱们理解整个过程。
建立控制器成功以后,就是执行Action方法了,这个过程在上面反编译的第二张图片的 controller.Execute(this.RequestContext); 方法获得体现。因此,除去细节,理解MvcHandler的ProcessRequest()方法并非太难。
除了HttpHandler以外,Asp.net里面还有另一个重要的角色——HttpModule。和HttpHandler相似,HttpModule指全部实现了IHttpModule接口的一类类型的统称。至于HttpModule、IHttpModule、UrlRoutingModule各个名称的含义和上述HttpHandler相同,在此不作重复说明。
经过上文,咱们知道HttpHandler的做用很是明确:处理Http请求,生成相应结果。那么,HttpModule又是干什么的呢?
HttpHandler的做用是处理某一类别的请求,好比ashx、aspx、asmx等,在某些状况下,各种请求可能都须要进行某些相同的处理(好比请求拦截、身份认证、检查功能等),不可能在每一个类别的HttpHandler里面都去实现这些相同的代码,这个时候怎么办呢?处理某一类通用请求,提升代码的复用率。是否是想到了咱们的面向切面编程(AOP),没错,HttpModule就是负责作这个事,HttpModule经过事件订阅的方式,将某类HttpHandler都须要的功能抽取出来,这些功能能够编译成类库供各个模块调用。这种采用事件(观察者)的设计模式使得系统设计上更加灵活。
先来看看IHttpModule的定义
public interface IHttpModule { //初始化 void Init(HttpApplication context); //释放 void Dispose(); }
接口定义很简单,一个初始化组件的方法,一个释放对象的方法。
咱们来写一个测试的例子具体看看HttpModule如何注册事件,咱们新建一个IHttpModule的实现类:
namespace MyTestMVC { public class TestMyModule:IHttpModule { public void Dispose() { //throw new NotImplementedException(); } public void Init(HttpApplication app) { //事件注册 app.BeginRequest += app_BeginRequest; app.EndRequest += app_EndRequest; } void app_EndRequest(object sender, EventArgs e) { var app = (HttpApplication)sender; app.Context.Response.Write("请求结束"); } void app_BeginRequest(object sender, EventArgs e) { var app = (HttpApplication)sender; app.Context.Response.Write("请求开始"); } } }
在Init方法里面,经过HttpApplication对象来注册请求的事件。这样,每次发起一次http请求的时候都进到这两个方法。
固然,这些注册就能执行了吗?想得美,系统哪里知道你这个自定义HttpModule的存在,因此必需要在Web.config里面声明一下。
<system.web> <httpModules> <add name="TestMyModule" type="MyTestMVC.TestMyModule, MyTestMVC" /> </httpModules> </system.web>
出现结果:
查阅资料后发现,原来IIS经典模式下必需要这样配置:
<system.webServer> <modules> <add name="TestMyModule" type="MyTestMVC.TestMyModule, MyTestMVC" preCondition="integratedMode" /> </modules> </system.webServer>
没办法,用微软的东西就要遵照别人的游戏规则。改为这样以后获得结果:
文中的“你好”来自这里:
既然HttpModule是事件注册机制的,那么若是须要在同一个事件里面去实现不一样的功能,也就是说同一个事件注册屡次是否可行呢?咱们来试一把:
假如TestMyModule.cs这个自定义Module的做用是功能检查:
public class TestMyModule:IHttpModule { public void Dispose() { //throw new NotImplementedException(); } public void Init(HttpApplication app) { //事件注册 app.BeginRequest += app_BeginRequest; app.EndRequest += app_EndRequest; } void app_EndRequest(object sender, EventArgs e) { var app = (HttpApplication)sender; app.Context.Response.Write("功能检查结束"); } void app_BeginRequest(object sender, EventArgs e) { var app = (HttpApplication)sender; app.Context.Response.Write("功能检查开始"); //功能检查的处理逻辑... } }
而后新建一个TestMyModule2.cs这个自定义Module,去实现请求拦截的功能:
public class TestMyModule2:IHttpModule { public void Dispose() { //throw new NotImplementedException(); } public void Init(HttpApplication app) { //事件注册 app.BeginRequest += app_BeginRequest; app.EndRequest += app_EndRequest; } void app_EndRequest(object sender, EventArgs e) { var app = (HttpApplication)sender; app.Context.Response.Write("请求拦截结束"); } void app_BeginRequest(object sender, EventArgs e) { var app = (HttpApplication)sender; app.Context.Response.Write("请求拦截开始"); //请求拦截的处理逻辑.... } }
最后在Web.config里面配置两个Module:
<system.webServer> <modules> <add name="TestMyModule" type="MyTestMVC.TestMyModule, MyTestMVC" preCondition="integratedMode" /> <add name="TestMyModule2" type="MyTestMVC.TestMyModule2, MyTestMVC" preCondition="integratedMode" /> </modules> </system.webServer>
获得结果:
这说明同一个事件能够注册屡次,便可以在同一个事件里面作不一样的事。
经过上文的HttpModule的应用,咱们看到在Init方法里面能够拿到当前应用的HttpApplication对象,拿到这个貌似就能够拿到当前请求上下文里面的Request、Response了,是否是就能够处理当前的http请求了,从这点上来讲,HttpModule也能处理http请求,或者说具备处理http请求的能力。既然HttpHandler和HttpModule均可以处理http请求,那在使用的时候如何区分呢?上文说过,HttpModule的做用相似AOP,是针对某些通用功能(请求拦截、身份认证、检查功能)的,而HttpHandler经常使用来处理某一类(ashx、aspx、asmx)http请求,二者的侧重点不一样,至于具体在实际中如何使用,你能够自行考量。
好了,上面介绍那么多HttpModule的使用,都是在为了解Mvc里面的UrlRoutingModule作铺垫。上文说过UrlRoutingModule的做用是拦截请求,那么它是如何作的呢,仍是来反编译看看吧。
public class UrlRoutingModule : IHttpModule { // Fields private static readonly object _contextKey; private static readonly object _requestDataKey; private RouteCollection _routeCollection; // Methods static UrlRoutingModule(); public UrlRoutingModule(); protected virtual void Dispose(); protected virtual void Init(HttpApplication application); private void OnApplicationPostResolveRequestCache(object sender, EventArgs e); [Obsolete("This method is obsolete. Override the Init method to use the PostMapRequestHandler event.")] public virtual void PostMapRequestHandler(HttpContextBase context); public virtual void PostResolveRequestCache(HttpContextBase context); void IHttpModule.Dispose(); void IHttpModule.Init(HttpApplication application); // Properties public RouteCollection RouteCollection { get; set; } }
重点确定在Init()方法。
图一:
注册HttpApplication对象的PostResolveRequestCache事件。
图二:
封装HttpContext,成为HttpContextWrapper对象
图三:
这部分代码是咱们上述路由理论的代码实践,因此这段代码很重要,咱们将代码拷贝出来:
public virtual void PostResolveRequestCache(HttpContextBase context) { //1.传入当前上下文对象,获得与当前请求匹配的RouteData对象 RouteData routeData = this.RouteCollection.GetRouteData(context); if (routeData != null) { //2.从RouteData对象里面获得当前的RouteHandler对象。其实这里的RouteHandler属性对应就是一个MvcRouteHandler的对象。 IRouteHandler routeHandler = routeData.RouteHandler; if (routeHandler == null) throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[0])); if (!(routeHandler is StopRoutingHandler)) { //3.根据HttpContext和RouteData获得RequestContext对象 RequestContext requestContext = new RequestContext(context, routeData); context.Request.RequestContext = requestContext; //4.根据RequestContext对象获得处理当前请求的HttpHandler(MvcHandler)。 IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); if (httpHandler == null) { object[] args = new object[] { routeHandler.GetType() }; throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoHttpHandler"), args)); } if (httpHandler is UrlAuthFailureHandler) { if (!FormsAuthenticationModule.FormsAuthRequired) throw new HttpException(0x191, SR.GetString("Assess_Denied_Description3")); UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this); } else //5.请求转到HttpHandler进行处理(进入到ProcessRequest方法)。这一步很重要,由这一步开始,请求才由UrlRoutingModule转到了MvcHandler里面 context.RemapHandler(httpHandler); } } }
博主在主要的地方加上了注释。
代码释疑:这里有几点须要说明的。
一、HttpApplication对象的PostResolveRequestCache事件在MSDN上的解释是:在 ASP.NET 跳过当前事件处理程序的执行并容许缓存模块知足来自缓存的请求时发生。查阅相关资料发现,之因此在PostResolveRequestCache事件注册路由、匹配HttpHandler,是为了知足IIS6。能够参考Tom大叔的文章:http://www.cnblogs.com/TomXu/p/3756858.html。
二、 IRouteHandler routeHandler = routeData.RouteHandler; 这里的routeHandler其实是一个MvcRouteHandler类型的对象,为何这么说,咱们来反编译下这个就会一目了然:
图一:
MvcRouteHandler实现了IRouteHandler接口。而后咱们重点来看GetHttpHandler()方法获得的是哪一个HttpHandler。
图二:
看到最后一句是否是立马就明白了。也就是说GetHttpHandler()这个方法决定了采用MvcHandler去处理当前的http请求。因此在上述 IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); 这一句获得的就是一个MvcHandler的实例。
三、 context.RemapHandler(httpHandler); 这一句能够理解为将当前的请求上下文交给httpHandler这个对象去处理。
四、到这里,咱们再反过来看前面的MVC的原理就彻底明朗了。
写到这里,总算把整个过程梳理了一遍,不少细节都未涉及,可是大的过程应该仍是明朗的。通篇比较偏理论,因此总体上比较枯燥,可是仍是但愿园友们可以静下心来慢慢看,由于博主以为这些对于理解MVC原理过重要!!!想一想看,若是你也彻底理解了这个过程,是否是均可以本身经过实现IHttphandler和IHttpModule去搭建一个简单的MVC框架了,不错,博主确实是这样打算的,这篇把理论搞清楚,下篇就是实现的细节了。其实写本身的MVC框架更多的在于学习MVC原理,但愿本身可以坚持下去。若是你以为本文可以帮助你,能够右边随意 打赏 博主,也能够 推荐 进行精神鼓励。你的支持是博主继续坚持的不懈动力。
本文原创出处:http://www.cnblogs.com/landeanfen/
欢迎各位转载,可是未经做者本人赞成,转载文章以后必须在文章页面明显位置给出做者和原文链接,不然保留追究法律责任的权利