Asp.net mvc 框架揭秘之Asp.net +Mvc简介
1、什么是MVC?
介绍MVC之前说一下自治视图(Autonomous View):将UI界面的呈现、交互动作的捕捉响应、逻辑处理流程、数据存储等糅合在一起(如WebForm),我们将这种设计模式成为自治视图
自治视图的弊端:
-
视图和逻辑糅合在一起,不利于逻辑的复用
-
不利于对UI组件的调试
MVC是基于关注点分离的方针的架构模式,它将一个人机交互用户的功能分为Model、View、Controller三部分,它们各自具有自己的职责:
Model:是对用于状态和业务功能的封装,是同时包含行为和数据的领域模型,Model接收Controller的请求完成相应的业务处理,在状态改变时向View发出通知
View:实现可视化界面的呈现,同时捕获用户的交互操作
Controller:接收View捕获的交互操作,完成相应的UI逻辑,如果需求涉及业务逻辑的需求,则Controller会直接调用Model来处理,在完成UI逻辑处理后,Controller会控制现有View或创建新的View对用户请求予以响应。
Model-View-Controller间的交互关系
其中Controller不单单是介于Model和View之间的中介,Model和View之间亦可以直接交互,当Model中的状态发生改变时,它可以直接通知View,View亦可以主动查询Model中的状态信息。
MVC与"三层结构"的关系?
两者并没有什么可比性,对于多层架构模式来说,MVC通常是被当做UI层的设计模式(Asp.net mvc通常就被认为是UI层的MVC),而Model更多的表现为业务访问层的入口。
2、IIS与asp.net管道
2.1 IIS 5.x与asp.net
IIS 5.x 运行在InetInfo.exe 进程中,该进程中寄宿着一个叫做W3SVC(World Wide Web Publishing Serivice)的windows服务,该服务的功能包括Http请求的监听、工作进程和配置管理(通过元数据(Metadata)获取相关配置信息)
当收到Http请求后,先根据后缀名判断请求的是否是静态资源,如果是则直接以HTTP返回请求的资源,如果是动态资源,则根据扩展名从IIS的脚本映射找到相应的ISAPI动态链接库(DLL)。
ISAPI(Internet Server Application Programing Interface)是一组本地的Win 32 API,是Web应用和IIS之间的纽带,ASP.NET ISAPI 对应的DLL为aspnet_isapi.dll(位于sysdir:\Windows\Microsoft.NET\Framework\versionInfo),ISAPI支持ISAPI (ISAPI Extension)扩展和ISAPI 筛选(ISAPI Filter),前者是真正处理HTTP请求的接口,后者则是在处理请求之前进行验证、转发或拒绝请求等。
如果请求的是基于asp.net类型的资源时,asp.net ISAPI 被加载,aspnet_isapi 会创建asp.net的工作进程(如果该进程尚未启动),对于IIS5.x该进程为asp.net.exe,工作进程初始化的过程中,工作进程的主线程初始化CLR,
对于某个Web请求,CLR创建应用程序域,在应用程序域中http运行时(IsapiRuntime)被加载并创建相应的应用,在IIS5.x中,所有的Web应用都存在于同一工作进程(aspnet_wp.exe)的不同应用程序域中。
2.2 IIS 6.x与Asp.net
IIS 5的在处理http请求上的缺陷:
-
Asp.net ISAPI 动态链接库被直接加载到InetInfo.exe进程中,它和工作进程之间是基于进程之间的通信,易造成性能上的瓶颈
-
所有的Web应用运行在同一工作进程的不同应用程序域中,基于应用程序域的隔离不能从根本上解决不同应用程序间的影响
为解决如上的第一个问题,IIS6.x将aspnet_isapi动态链接库直接加载到工作进程中,针对第二个问题,IIS 6创建应用程序池的机制,我们可以为一个或多个Web应用创建应用程序池,每一个应用程序池对应一个工作进程,为Web应用提供了基于进程的隔离。
注:图中的HTTP.SYS是一个监听HTTP请求的内核驱动,当HTTP.SYS监听到一个HTTP请求后,将其分发给W3SVC, W3SVC解析出请求的URL,并根据Metadata中的URL与Web应用的映射关系定义请求的Web应用,并进一步得到目标应用运行的工作进程,如果尚未创建则为该请求创建工作进程(称之为请求式创建),在工作进程的初始化过程中aspnet_isapi动态链接库被加载。。。。。,之后Asp.net ISAPI加载CLR,创建应用程序域,初始化应用程序。
2.3 IIS 7与 Asp.net
IIS 7 在请求的监听和分发机制上进行了革新性的改进,主要体现在Windows进程激活服务WAS(Windows Process Activation Service)的引进,将IIS6中W3SVC服务承载的部分功能分流给了WAS,W3SVC承载的功能主要有三个部分:
-
HTTP请求接收:接收HTTP.SYS分发的HTTP请求
-
配置管理:从Metadata中加载配置信息配置相关组件
-
进程管理:进程的创建、回收、监控进程
IIS 7 将后两个功能分给了WAS,而HTTP请求的接收仍然由W3SVC,WAS的引入为IIS 7提供了对非http协议的支持,WAS通过监听器适配器接口抽象出不同协议监听器(asp.net mvc 4框架解密 p17)
不论是从W3SVC接收到的http请求,还是从WCF监听适配器接收到的请求,最终都会提交到WAS来处理,如果相应的工作进程尚未建立,则创建,否则将请求分发给其它的进程进行后续处理,WAS在请求处理过程中,通过相应的配置管理模块加载配置信息,对相关组件进行配置,与IIS5与IIS 6不同的是,IIS7的配置信息并不保存在元数据中,而是保存在xml格式的配置文件中,基本的配置在applicationHost.config中。
3、ASP.NET 管道
HTTP.SYS 接收到HTTP请求后,如果该请求是对该Web应用的第一次请求时,aspnet_isapi加载CLR,CLR会通过APPDomainFactory为该Web应用创建应用程序域,之后一个叫做IsapiRuntime的运行时被加载,被加载的IsapiHttpRuntime会接收该请求,IsapiHttpRuntime会首先创建一个ISAPIWorkerRequest(HttpWorker类的子类,所有的HTTP请求都必须封装到一个HTTPWorkerRequest对象中才能进行后续处理)的对象,并将该对象传递给Asp.net 的运行时HttpRuntime,此时HTTP请求进入Asp.net 管道,HttpRuntime会根据IsapiWorkerRequest对象创建用于当前请求上下文对象HTTPContext对象
当成功创建HttpContext对象后,HttpRuntime会利用HttpApplicationFactory创建新的或获取一个HttpApplication对象,HttpApplicationFactory内部维护着一个HttpApplication对象池,HttpRuntime从池中选取HttpApplication对象来处理Http请求,处理完成后将对象释放会对象池中(每个HttpApplication只能处理一次请求)
在HttpApplication对象的初始化过程中,会根据配置文件加载并初始化已注册的HttpModule对象,对于HttpApplication来说,在处理Http请求的不同阶段会触发不同的事件,而HttpModule就是将这些事件的操作注入到请求处理的过程中(通过注册相应的HttpApplication事件),HttpApplication具有19个管道事件,分别如下:
Asp.Net 的许多功能如身份验证、缓存、授权都是通过HttpModule实现的
有关HttpModule和HttpApplication事件注册:http://www.cnblogs.com/kissdodog/p/3527922.html
真正的Http请求处理是由HttpHandler。对于不同的资源具有不同的HttpHandl(但都实现了IHttpHandler这个接口,IHttpHandler的ProcessRequest方法请求处理的具体实现),例如Web Form对应的HttpHandler为System.Web.PageUI,asp.net Mvc对应的HttpHandler为mvchandler.
HttpApplication是整个Web应用处理的核心,当收到第一次请求后,Asp.net 会创建多个HttpApplication对象,并将其放入池中,选择一个来处理当前请求,处理完成后不是直接回收,而是释放到HttpApplication对象池中,供后续请求使用,如果池中的所有HttpApplication对象都处于繁忙状态,则创建新的HttpApplication对象。
针对HttpApplication管道事件的HttpHandler注册有两种方式,一是通过Web.config 配置文件实现,二是通过Global.asmx文件来实现
方法一:
-
首先定义一个类实现IHttpModule接口,IHttpModule接口的定义如下:
system.web配置元素的子元素httpModules用来配置网站所使用的HttpModule;httpModules的子元素add用来增加一个新的HttpModule;clear将清除前面注册的所有HttpModule。
add元素有两个必选的属性name和type,简介如下:
-
name表示这个HttpModule在程序中的名字,在网站应用程序中,可以通过这个名字来找到HttpModule对象的引用。HttpApplication的Modules属性表示这个对象所关联的所有HttpModule对象,通过这个name作为索引器,可以找到对应的HttpModule对象。
-
type表示HttpModule对象的类型名,Asp.net网站可以使用这个类型名,通过反射来动态创建HttpModule对象。类型的写法就是反射中要求的类型名称写法,如果这个类定义在网站中,那么,就是一个包含命名空间的类的全名,否则,在全名的后面,使用逗号(,)分隔,还需要跟上类型所在的程序集的名称,这个程序集的名称不需要包含.dll扩展名。
方法二:通过Global.asmx 文件来实现
在global.asax中,针对HttpApplication的事件处理,可以通过定义特殊命名的方法来实现。首先,这些方法必须符合System.EventHandler,因为所有的HttpApplication管道事件都使用这个委托定义。第二,方法的作用域必须是public。第三,方法的命名格式必须如下:Application_注册的事件名称。按照这种命名方法定义在global.asax中的方法将被自动注册到对应的事件中。
例如,希望在global.asax中注册PostAuthenticateRequest事件处理,那么在global.asax中应该定义一个如下的方法:
4、Asp.Net MVC 整体处理流程
4.1 URL解析
针对Asp.net mvcWeb应用来说,对Http的请求处理是通过Controller相应的Action方法来实现的,因此Asp.net mvc 的首要任务就是从请求URL中解析出相应的Controller和Action,在Asp.net mvc中URL的解析是通过UrlRoutingModule来实现的。UrlRoutingModule实现了IHttpModule接口,在其的Init()方法中,注册HttpApplication管道事件的第7个事件PostResolveRequtestCache
如下为PostResolveRequestCache 的主要处理代码:
在此期间主要做了几件事:
-
将封装有当前请求信息HttpContext对象进一步封装为HttpContextWraper(为HttpContextBase的子类)对象,并将其作为参数传入PostResolveRequestCache方法中
-
调用RouteCollection(内部具有一个保存有当前已注册的路由信息的Dictionary集合)的GetRouteData(),如果匹配成功,就返回一个封装有匹配到的Route对象的RouteData对象,否则,就返回null,
-
将获取到的RouteData和HttpContext进一步封装为RequestContext对象
-
将上步封装好的RequestContext对象作为参数调用RouteDatad的GetRouteHandler属性获取一个IRouteHandler类型的对象(MvcRouteHander)
-
调用IRouteHandler的GetHttpHander方法,返回一个IHttpHander类型的对象(mvcHandler)
4.2 Controller的激活
UrlRoutingModule通过路由表解析当前的Http请求后得到一个用于封装路由数据的RouteData,之后调用RouteHandler的GetHttpHandler方法得到HttpHandler对象并注册到Http上下文中,由于RouteData的RouteHandler来自Route的RouteHander,默认情况下是一个mvcrouteHander,所以默认情况下用于处理mvc请求的就是这个mvchandler(可以查看RouteCollectionExtension的maproute方法来确认),Controller对象的激活和执行就是在mvcHandler中进行的。
MvcHandler 实现了IHttpHandler接口,在其ProcessRequest方法中实现了Controller的激活和执行
PR方法中的代码如下:
主要功能的实现在封装的内部函数ProcessRequestInit()中,方法如下
其中主要做了如下几件事:
-
从参数中获取当前请求的Http上下文(参数中传入的是一个HttpContextWraper)
-
从RequestContext(封装的RouteData和HttpContext)中获取Controller名称
-
通过ControllerBuilder(用来设置或获取Controller对象工厂)对象中获取创建Controller的工厂对象(默认为DefaultControllerFactory对象)
-
调用工厂对象的CreateController方法根据RequestContext和控制器名称创建Controller对象(通过反射)
4.3 Action的执行
Action的执行是在Controller的Excute(RequestContext context)方法中执行的(Controller的Excute()方法是在mvcHandler的ProcessRequest()方法中执行的(见PR方法截图)),其核心在于对应Action方法的执行和作为方法返回的ActionResult的执行
注意:ControllerBase和Controller都实现了IController接口,而Controller又继承自ControllerBase,在ControllerBase中显式实现了接口IController
在IController的Excute方法中调用的同名的实例方法Excute(RequestContext requestContext),
可以看出,实例方法Excute的主要操作有调用了ControllerBase的ExcuteCore()方法,而该方法是一个虚方法,所以任何继承ControllerBase的类都必须重写该方法,在Controller方法中并没有显式实现IController的Excute(可能说的有歧义,这里指在Controller继承了ControllerBase的Excute方法,并没有自己实现),而是重写ExcuteCore方法,重写的Excute方法如下:
首先调用GetActionName方法获取请求的action名称,其后调用ActionIInvoker的InvokeAction方法,这个方法主要做了如下的几件事:
-
执行Action过滤器方法
-
执行Action方法
-
执行作为方法返回的ActionResult
如果这个ActionResult是ViewResult的话,就调用相应的视图引擎渲染视图
以上就是Asp.net mvc 的一次整体请求处理流程。