Mvc生命周期深度剖析
客户端发送请求->IIS, UrlRouting模块对比URL, 默认如果该URL能对应到实体文件则退出MVC管道把控制权交还给IIS.
如果RegisterRoutes中的路由规则对比成功默认情况下交给MvcRouteHandler(IRouteHandler)处理, IRouteHandler的作用是决策使用哪一个HttpHandler处理本次请求,IRouteHandler接口定义如下:
[TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")] public interface IRouteHandler { IHttpHandler GetHttpHandler(RequestContext requestContext); }
微软的提供的MVC框架中MvcRouteHandler实现了IRouteHandler接口,默认交给MvcHandler处理,代码如下:
public class MvcRouteHandler : IRouteHandler { private IControllerFactory _controllerFactory; public MvcRouteHandler() { } public MvcRouteHandler(IControllerFactory controllerFactory) { this._controllerFactory = controllerFactory; } protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) { requestContext.HttpContext.SetSessionStateBehavior(this.GetSessionStateBehavior(requestContext)); return new MvcHandler(requestContext); } protected virtual SessionStateBehavior GetSessionStateBehavior(RequestContext requestContext) { string str = (string) requestContext.RouteData.Values["controller"]; if (string.IsNullOrWhiteSpace(str)) { throw new InvalidOperationException(MvcResources.MvcRouteHandler_RouteValuesHasNoController); } IControllerFactory factory = this._controllerFactory ?? ControllerBuilder.Current.GetControllerFactory(); return factory.GetControllerSessionBehavior(requestContext, str); } IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) { return this.GetHttpHandler(requestContext); } }
As we know, 所有的HttpHandler的入口点为ProcessRequest,IHttpHandler接口定义如下:
public interface IHttpHandler { void ProcessRequest(HttpContext context); bool IsReusable { get; } }
在MvcRouteHandler.ProcessRequest中首先传入HttpContext以及两个out参数到ProcessRequestInit方法中根据路由参数获取ControllerFactory类以及对应的Controller, 代码如下:
protected internal virtual void ProcessRequest(HttpContextBase httpContext) { IController controller; IControllerFactory factory; this.ProcessRequestInit(httpContext, out controller, out factory); try { controller.Execute(this.RequestContext); } finally { factory.ReleaseController(controller); } } private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) { HttpContext current = HttpContext.Current; if ((current != null) && (ValidationUtility.IsValidationEnabled(current) == true)) { ValidationUtility.EnableDynamicValidation(current); } this.AddVersionHeader(httpContext); this.RemoveOptionalRoutingParameters(); string requiredString = this.RequestContext.RouteData.GetRequiredString("controller"); factory = this.ControllerBuilder.GetControllerFactory(); controller = factory.CreateController(this.RequestContext, requiredString); if (controller == null) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_FactoryReturnedNull, new object[] { factory.GetType(), requiredString })); } }
在Asp.net Mvc中所有的Controller都实现了IController接口,该接口定义了一个Execute方法,代码如下:
public interface IController { void Execute(RequestContext requestContext); }
微软提供的默认框架中实现了该接口的Class为ControllerBase, ControllerBase除了实现Execute方法外还定义了一个抽象方法ExecuteCore,而实现了这个方法的就是我们工作中定义Controller时继承需要继承的System.Web.MvcController类,接上文讲,在MvcRouteHandler.ProcessRequest中取得Controller后接着会进入ControllerBase的Execute方法,代码如下:
protected virtual void Execute(RequestContext requestContext) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } if (requestContext.HttpContext == null) { throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext"); } this.VerifyExecuteCalledOnce(); this.Initialize(requestContext); using (ScopeStorage.CreateTransientScope()) { this.ExecuteCore(); } }
在做了一些验证和初始化后会进入System.Web.MvcController类的ExecuteCore方法,代码如下:
protected override void ExecuteCore() { this.PossiblyLoadTempData(); try { string requiredString = this.RouteData.GetRequiredString("action"); if (!this.ActionInvoker.InvokeAction(base.ControllerContext, requiredString)) { this.HandleUnknownAction(requiredString); } } finally { this.PossiblySaveTempData(); } }
该方法中会通过路由参数得知要运行的ActionName并通过ActionInvoker调用该Action响应客户端, 到此为止就是我们平时经常使用的Action所做的事情了,如果直接用Response客户端或返回数据本次请求的生命周期则到此结束.
若返回ViewResult还有有一些额外的操作, 通过ViewEngine获取到相应的View返回给客户端,我们先看一下IViewEngine接口的定义:
public interface IViewEngine { ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache); ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache); void ReleaseView(ControllerContext controllerContext, IView view); }
.Net内建了两套ViewEngine,一套为WebFormViewEngine,另一套为我们比较常用的RazorViewEngine,代码如下:
public class RazorViewEngine : BuildManagerViewEngine { internal static readonly string ViewStartFileName = "_ViewStart"; public RazorViewEngine() : this(null) { } public RazorViewEngine(IViewPageActivator viewPageActivator) : base(viewPageActivator) { base.AreaViewLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" }; base.AreaMasterLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" }; base.AreaPartialViewLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" }; base.ViewLocationFormats = new string[] { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" }; base.MasterLocationFormats = new string[] { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" }; base.PartialViewLocationFormats = new string[] { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" }; base.FileExtensions = new string[] { "cshtml", "vbhtml" }; } protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) { string layoutPath = null; bool runViewStartPages = false; IEnumerable<string> fileExtensions = base.FileExtensions; return new RazorView(controllerContext, partialPath, layoutPath, runViewStartPages, fileExtensions, base.ViewPageActivator) { DisplayModeProvider = base.DisplayModeProvider }; } protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath) { string layoutPath = masterPath; bool runViewStartPages = true; IEnumerable<string> fileExtensions = base.FileExtensions; return new RazorView(controllerContext, viewPath, layoutPath, runViewStartPages, fileExtensions, base.ViewPageActivator) { DisplayModeProvider = base.DisplayModeProvider }; } }
可以看到在ViewEngine中已经内建了很多找到某个具体View文件的格式,这也是为什么我们如果不自定义MVC框架的某些模块必须严格按照微软提供的目录结构来分割文件。
Tip: 这实际上是有好处的,也是微软提倡的Convention Over Configuration(觉得没法有一个简单词概括所以就不翻译了), 意思就是他提供了一个标准的模式,大家都遵守这样一个规则,以降低不同的人维护同一个项目的复杂度: 我们知道如果每个团队甚至每个人写出的项目如果都是不同的目录结构与项目框架,如果临时换另一个团队或另一个人来接手是需要花很多时间来熟悉的, 项目越复杂也就越难以维护。
接着通过ViewEngine通过CreateView实例化一个IView对象并返回,IView接口定义如下:
public interface IView { void Render(ViewContext viewContext, TextWriter writer); }
显然就是Render用于响应客户端的方法了,在RazorViewEngine中将会返回一个实现了IView接口的RazorView对象并调用Render方法最终输出页面到客户端:
protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance) { if (writer == null) { throw new ArgumentNullException("writer"); } WebViewPage page = instance as WebViewPage; if (page == null) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.CshtmlView_WrongViewBase, new object[] { base.ViewPath })); } page.OverridenLayoutPath = this.LayoutPath; page.VirtualPath = base.ViewPath; page.ViewContext = viewContext; page.ViewData = viewContext.ViewData; page.InitHelpers(); if (this.VirtualPathFactory != null) { page.VirtualPathFactory = this.VirtualPathFactory; } if (this.DisplayModeProvider != null) { page.DisplayModeProvider = this.DisplayModeProvider; } WebPageRenderingBase startPage = null; if (this.RunViewStartPages) { startPage = this.StartPageLookup(page, RazorViewEngine.ViewStartFileName, this.ViewStartFileExtensions); } HttpContextBase httpContext = viewContext.HttpContext; WebPageRenderingBase base4 = null; object model = null; page.ExecutePageHierarchy(new WebPageContext(httpContext, base4, model), writer, startPage); }
至此完整的生命周期介绍完毕,实际上关于后面的ViewEngine部分只是粗浅的介绍了一下运转流程,关于具体细节需要更大的篇幅来介绍在此就不再展开,目前除了微软内建的ViewEngine外, 也有很多优秀的第三方ViewEngine可供大家参考比如SparkViewEngine、NDjango、NHaml等.
由于个人水平有限,理解有误的地方恳请斧正!
Mvc生命周期深度剖析的更多相关文章
- ASP.NET MVC 生命周期
本文的目的旨在详细描述ASP.NET MVC请求从开始到结束的每一个过程.我希望能理解在浏览器输入URL并敲击回车来请求一个ASP.NET MVC网站的页面之后发生的任何事情. 为什么需要关心这些?有 ...
- ASP.NET MVC生命周期介绍(转)
本文以IIS7中asp.net应用程序生命周期为例,介绍了asp.net mvc的生命周期. asp.net应用程序管道处理用户请求时特别强调"时机",对asp.net生命周期的了 ...
- Asp.net MVC生命周期
Asp.net应用程序管道处理用户请求时特别强调"时机",对Asp.net生命周期的了解多少直接影响我们写页面和控件的效率.因此在2007年和2008年我在这个话题上各写了一篇文章 ...
- MVC学习笔记---MVC生命周期
Asp.net应用程序管道处理用户请求时特别强调"时机",对Asp.net生命周期的了解多少直接影响我们写页面和控件的效率.因此在2007年和2008年我在这个话题上各写了一篇文章 ...
- MVC学习笔记---MVC生命周期及管道
ASP.NET和ASP.NET MVC的HttpApplication请求处理管道有共同的部分和不同之处,本系列将体验ASP.NET MVC请求处理管道生命周期的19个关键环节. ①以IIS6.0为例 ...
- [收藏]Asp.net MVC生命周期
一个HTTP请求从IIS移交到Asp.net运行时,Asp.net MVC是在什么时机获得了控制权并对请求进行处理呢?处理过程又是怎样的? 以IIS7中asp.net应用程序生命周期为例,下图是来自M ...
- 1.3 ASP.NET MVC生命周期
ASP.NET MVC的执行生命周期主要分为三个阶段,分别是网址路由对比.执行控制器与动作.执行视图并返回结果.从ASP.NET MVC接受HTTP请求到返回HTTP响应的过程如下图所示.
- asp.net mvc生命周期学习
ASP.NET MVC是一个扩展性非常强的框架,探究其生命周期对用Mock框架来模拟某些东西,达到单元测试效果,和开发扩展我们的程序是很好的. 生命周期1:创建routetable.把URL映射到ha ...
- 【转】.net MVC 生命周期
对于Asp.net MVC,我对它的生命周期还是兴趣很浓,于是提出两个问题: 一个HTTP请求从IIS移交到Asp.net运行时,Asp.net MVC是在什么时机获得了控制权并对请求进行处理呢?处理 ...
随机推荐
- Google镜像站更新地址
越来越觉得某度的行为无耻无下限,Google镜像站地址,不方便访问Google的可以参考,后面会尽量保持更新可用的镜像库. (收藏地址即可,免得镜像站被封) https://g.jikewenku.c ...
- Java中String类的format方法使用总结
可参考: http://www.cnblogs.com/fsjohnhuang/p/4094777.html http://kgd1120.iteye.com/blog/1293633 String类 ...
- (转载)ConcurrentHashMap 原理
集合是编程中最常用的数据结构.而谈到并发,几乎总是离不开集合这类高级数据结构的支持.比如两个线程需要同时访问一个中间临界区 (Queue),比如常会用缓存作为外部文件的副本(HashMap).这篇文章 ...
- hdu 4619 最大匹配问题
思路:把所有涉及到的点按(x+y)的奇偶分成两部分点,对所有的1*2的骨牌,都有(x+y)为偶数的建到奇数的边.求一次最大匹配,就是答案. #include<iostream> #incl ...
- 写css动画
<!doctype html><html lang="en"> <head> <meta charset="UTF-8" ...
- 微信之Android各版本列表
微信在不断地更新迭代,ios微信下载点击这里立即开始(手机电脑都可以,电脑端要安装iTunes),每个版本都放出一些新的功能或修复相关错误,详情可以点击下面的版本链接进行查看.(这里有Android微 ...
- ASP连接ACCESS数据库
Set conn2 = Server.CreateObject("ADODB.Connection") conn2.Open "Provider=Microsoft.Je ...
- 随笔之Android平台上的进程调度探讨
http://blog.csdn.net/innost/article/details/6940136 随笔之Android平台上的进程调度探讨 一由来 最近在翻阅MediaProvider的时候,突 ...
- windows server 2008下装SQL 2008R2x64
1. 在windows server 2008下装SQL 2008出现 This SQL Server Setup media is not supported on a X64 system 使用虚 ...
- JavaScript学习笔记(10)——JavaScript语法之操作DOM
1.页面输出用document.write()方法,但是不可以在window.onload中用,否则整个html页面将被覆盖. 2.通过javascript获取对象后,改变对象中的html内容:doc ...