客户端发送请求->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等.
由于个人水平有限,理解有误的地方恳请斧正!