MVC源码学习:打造自己的MVC框架(上)

Posted DotNet

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MVC源码学习:打造自己的MVC框架(上)相关的知识,希望对你有一定的参考价值。


来源:懒得安分 

链接:cnblogs.com/landeanfen/p/5989092.html


前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎。这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之处,还希望大家斧正,博主感激不尽!


一、MVC原理解析


最近园子里Asp.Net Core火了一阵,不管微软的开源动作有多么迟缓,还是希望微软能够给力一次。作为Core的主要Web框架——MVC,虽然已经开源,但是读起来着实费劲,并且感觉很多核心部件都找不到。


于是只能通过Reflector去反编译MVC5的组件以及参考博客园Fish Li等大神的文章去自己理解了。



1、MVC原理


之前的文章有介绍MVC的路由机制,其实路由机制算是MVC的原理的核心之一。在此我们还是要不厌其烦再来谈谈整个过程,因为这是理解MVC原理不可逾越的鸿沟。当我们收到一个URL的请求时,服务端收到请求,主要经历以下几个步骤:


  1. 请求被UrlRoutingModule部件拦截


  2. 封装请求上下文HttpContext,成为HttpContextWrapper对象。


  3. 根据当前的HttpContext,从Routes集合中得到与当前请求URL相符合的RouteData对象。


  4. 将RouteData与HttpContext请求封装成一个RequestContext对象。


  5. 根据RequestContext对象,从RouteData的RouteHandler中获取IHttpHandler(MVC里面会有一个IHttpHandler的实现类MvcHandler)。


  6. 执行IHttpHandler(MvcHandler),然后就是通过反射激活具体的controller,执行具体的action。


附上一张大致的流程图:



纵观整个过程,看上去很复杂,各种对象缠绕,看得人晕晕的。其实如果你静下心来仔细研读MVC的源码你会发现其实并没有想像中的那般复杂,请有点耐心听博主慢慢道来。


1、整个过程有两个核心的组件,文中博主用红色标记了出来:UrlRoutingModuleMvcHandler,上文提到的各个过程都和两个组件有紧密的联系。而这两个组件分别继承至IHttpModule和IHttpHandler接口,熟悉Asp.net管线事件的朋友应该会记得这两个接口,在管道事件里面这两个接口扮演着重要角色。要理解MVC的上述原理,必须要先理解这两类接口的原理以及使用。


2、UrlRoutingModule的作用可以理解为通过一系列的与路由相关的组件去解析当前请求的Controller与Action名称,其实简单点理解,比如我们请求http://localhost:8080/Home/Index这个url的时候,UrlRoutingModule拦截到这个请求,然后通过一系列的方式得到这里的“Home”和“Index”,这样理解有没有简单一点呢。


3、MvcHandler的作用就更加直接,上述通过拦截组件得到了请求的Controller和Action的名称,MvcHandler组件将当前请求的Controller名称反射得到对应的控制器对象,然后执行对应的Action方法。比如还是上述http://localhost:8080/Home/Index这个请求,通过字符串“Home”反射成为Home这个类型的控制器对象,然后调用这个对象的Index()方法。


4、综上,联合这两个组件来理解,UrlRoutingMudule组件的主要作用是解析当前的Controller与Action名称,MvcHandler的作用是将得到的Controller名称激活,得到具体的Controller对象,然后执行对应的Action方法。


所以,要理解MVC的原理,必须要了解这两个组件的基本原理以及作用。下面就根据这两个组件分别展开说明,相信理解了下面的内容,你对mvc的原理会有一个新的认识。


二、HttpHandler


上文说过MvcHandler是继承至IHttpHandler接口的!为什么这里大标题会用HttpHandler而不是MvcHandler呢?


因为博主觉得,HttpHandler实在是太重要了,首先得理解了HttpHandler这么一个大的东西,然后再来看具体的MvcHandler才有意义。


1、HttpHandler、IHttpHandler、MvcHandler的说明


  • HttpHandler指所有实现IHttpHandler接口一类类型的统称,它是一个大的称谓。这些类型有一个共同的功能,那就是可以用来处理Http请求。


  • IHttpHandler是微软定义的一类接口,用来约束所有能够处理Http请求的类型的接口规则。


  • MvcHandler是Mvc里面实现IHttpHandler接口的类型,也就是说,MvcHandler是Mvc里面处理Http请求的类型。


总而言之,HttpHandler只是一个逻辑称谓,它并不具体存在。而IHttpHandler和MvcHandler是.net framework里面具体存在的接口和实现类,是前者的表现形式。


2、IHttpHandler解析


2.1、Asp.net管线事件简易说明


做过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。


2.2、Asp.net中常见的HttpHandler类型


首先还是来看看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类转到定义:


MVC源码学习:打造自己的MVC框架(上)



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几乎“无处不在”。


2.3、自定义HttpHandler


当然,除了上述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这个类去处理。得到效果:


MVC源码学习:打造自己的MVC框架(上)


3、MvcHandler解析


上文介绍了那么多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()方法


MVC源码学习:打造自己的MVC框架(上)


将HttpContext转换为HttpContextBase对象,继续转到定义。


MVC源码学习:打造自己的MVC框架(上)


这里声明了一个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()方法并不是太难。


关注「DotNet」 

看更多精选 .Net 技术文章

↓↓↓

以上是关于MVC源码学习:打造自己的MVC框架(上)的主要内容,如果未能解决你的问题,请参考以下文章

自己动手模仿 springmvc 写一个 mvc框架

自己动手模仿 springmvc 写一个 mvc框架

ASP.NET MVC5的一个轻量级的框架学习的第一天

SSM源码解密-第2章 Spring MVC整体架构

开发自己的PHP MVC框架

轮子狂魔抛弃IIS,打造个性的Web Server - WebAPI/Lua/MVC(附带源码)