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是在什么时机获得了控制权并对请求进行处理呢?处理 ...
随机推荐
- python(1) - 安装篇
本次主要在windows下安装pyton2 和python3,实现 在cmd中输入 ptynon进入python2 在cmd中输入python3进入python3 第一步 当然是要下载python安装 ...
- [转]T4模版引擎之生成数据库实体类
本文转自:http://www.cnblogs.com/lzrabbit/archive/2012/07/18/2597953.html 在通过T4模版引擎之基础入门 对T4有了初步印象后,我们开始实 ...
- [Wordpress]Wordpress使用SMTP发送电邮
参考:phpmailer_init中的代码,可以配置使用SMTP发送电邮 官方的案例代码是: add_action( 'phpmailer_init', 'my_phpmailer_example' ...
- request常用方法小结
HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,开发人员通过这个对象的方法,可以获得客户这些信息. req ...
- ADO.NET笔记(一)XML导入导出和数据库
数据导出成XML文件 #region 数据导出成XML文件 string sql = "select Id, CallerNumber, TelNum, StartDateTime, End ...
- 开源而又强大的迷你型web服务器推荐
appweb显然是不二之选,看了下最新版,已经到了4了 下载下来,http://appwebserver.org/software/appweb-4.4.4-0-src.tgz,十几M,直接吓傻,离我 ...
- Spring boot 1.3.5 RELEASE 官方文档中文翻译--Part2:新手入门
Part II. 新手入门 如果你刚刚开始学习Spring boot或"普通"的Spring,这部分非常适合你!在这里,我们回答了最基础的"什么是?".&quo ...
- Polyline转Polygon
IGeometry geo = feature.ShapeCopy; IGeometryCollection pPolyline = geo as IGeometryCollection; ISegm ...
- 设置图层符号风格为用已有mxd里的同名图层风格
//要加载的IFeatureClass IFeatureClass pFeatClass = dataset as IFeatureClass; //新建要加载到mxd文档中的图层 IFeatureL ...
- NOIP2011(提高组)DAY2---观光公交(vijosP1741)
描述 风景迷人的小城Y市,拥有n个美丽的景点.由于慕名而来的游客越来越多,Y市特意安排了一辆观光公交车,为游客提供更便捷的交通服务.观光公交车在第0分钟出现在1号景点,随后依次前往2.3.4……n号景 ...