有些人要问题,为什么我要学框架?这里我简单说一下,深入理解一个框架,给你带来最直接的好处:
使用框架时,遇到问题可以快速定位,并知道如何解决;
当框架中有些功能用着不爽时,你可以自由扩展,实现你想要的操作,甚至可以拿到源码直接修改;
想成为框架师的必经之路;
提取框架中的优秀代码和思想,为己所用;
更多好处,你可以自己去体会,有兴趣的可以看一下asp.net中 mvc部分的源码:http://aspnetwebstack.codeplex.com/
本文目的
上一篇文章是让你明白MVC最核心的两个流程(http://www.cnblogs.com/DotCpp/p/3269043.html)。我们接着上篇从ControllerActionInvoker
的InvokeAction
方法执行Action
说起:
//执行Action,并得到ActionResult
ActionResult actionResult = method.Invoke(controllerContext.Controller,
parameters.ToArray()) as ActionResult;
//最终ActionResult用HttpResponse将数据传回客户进行显示
actionResult.ExecuteResult(controllerContext);
本文的目的就是让你明白这段代码到底做了哪些事情?MVC
中的Controller
如何找到View
,并进行显示。
开始旅程
先放上与本文相关的类结构图:
此图看起来结构相当复杂,但其实他很简单,给我两分钟,我会让你明白这些鬼东西到底是什么、有什么关系?
先说ActionResult
,从图上看,ActionResult
是个抽象类,他的子类有很多,比如JsonResult
,ContentResult,ViewResult,EmptyResult
等等。这个东西大家用MVC的时候常常接触,比如:
public class HomeController:Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult GetInfo()
{
...
return Json(obj);
}
public ActionResult GetContent()
{
return Content("test");
}
}
上面三个Action返回分别对应ViewResult、JsonResult、ContentResult
,而我图中只画ViewResult
,因为它是我们最常用的一个ActionResult
,而且是最复杂的一个(因为要负责View的显示)。而看看ContentResult
的核心源码,我想简单的大家都笑翻了:
public class ContentResult : ActionResult
{
public string Content { get; set; }
public override void ExecuteResult(ControllerContext context)
{
HttpResponseBase response = context.HttpContext.Response;
if (Content != null)
{
response.Write(Content);
}
}
}
它的实现和上面我画的结构图完全没有半毛钱关系,直接一个Response.Write
输出就完成了。所以我用ViewResult
来写本文。
接下来注意ViewEngines
这个静态类,看一下它的源码:
public static class ViewEngines
{
private static readonly ViewEngineCollection _engines = new ViewEngineCollection
{
new WebFormViewEngine(),
new RazorViewEngine(),
};
public static ViewEngineCollection Engines
{
get { return _engines; }
}
}
只有一个静态只读的ViewEngineCollection
类型的成员,初始化时封装了两个视图引擎,WebFormViewEngine
和RazorViewEngine
?这两个是什么?为了方便大家理解,我们看看RazorViewEngine
的源码(WebFormViewEngine
基本一样,篇幅限制下面我只用Razor
举例):
public class RazorViewEngine : BuildManagerViewEngine
{
internal static readonly string ViewStartFileName = "_ViewStart"; //存储ViewStart模板的
public RazorViewEngine(IViewPageActivator viewPageActivator)
: base(viewPageActivator)
{
//这些构造大家应该觉得很亲切
ViewLocationFormats = new[]
{
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
};
MasterLocationFormats = new[]
{
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
};
PartialViewLocationFormats = new[]
{
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
};
FileExtensions = new[]
{
"cshtml",
"vbhtml",
};
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
var view = new RazorView(controllerContext, viewPath,
layoutPath: masterPath, runViewStartPages: true, viewStartFileExtensions: FileExtensions, viewPageActivator: ViewPageActivator)
{
DisplayModeProvider = DisplayModeProvider
};
return view;
}
}
从代码中可以看出,RazorViewEngine
只是封装了View
文件的相关路径。后面我会说明他们是怎么被用到的。
知道上面几个类的基本情况后,看一下它们的执行流程,你会对各个类的功能有个大致的了解,当控制器的Action
返回一个ViewResult
时并执行ExecuteResult
时(文章开始部分介绍的代码):
从
ViewEngines
的Engines
中获取ViewResultEngine
对象,实质是遍历RazorViewEngine
和WebFormViewEngine
,然后通过它们本身继承VirtualPathProviderViewEngine
的成员函数FindView
创建ViewResultEngine
【从下面的时序图中可以看出,虽然MVC把这个东西藏的很深,但基本都是在父类与子类间传递,结构还算清晰】;通过得到的
ViewResultEngine
中的View
执行Render
进行界面显示,内部会调用RazorView
的RenderView
进行最终的显示处理;
核心流程就是上面两步,执行时序图如下所示
现在注意流程的第17步,即执行BuildManagerCompliedView
的Render
函数,这个函数是View
显示的灵魂:
public virtual void Render(ViewContext viewContext, TextWriter writer)
{
object instance = null;
//这个ViewPath就是根据RazorViewEngine的模板位置得到的View具体路径,在RazorViewEngine创建ViewEngineResult传进来的
Type type = BuildManager.GetCompiledType(ViewPath);
if (type != null)
{
instance = ViewPageActivator.Create(_controllerContext, type);
}
if (instance == null)
{
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentCulture,
MvcResources.CshtmlView_ViewCouldNotBeCreated,
ViewPath));
}
RenderView(viewContext, writer, instance);
}
上面根据ViewPath
生成的那个instance
是什么?看看RazorView
中RendView
的实现就知道了:
protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance)
{
WebViewPage webViewPage = instance as WebViewPage;
//其它代码先不管
webViewPage.ExecutePageHierarchy(new WebPageContext(context: viewContext.HttpContext, page: null, model: null), writer, startPage);
}
原来是个WebViewPage
的对象,查一查MSDN
就知道,所有View
都是从WebViewPage
的泛型WebViewPage<TMode>
直接继承出来的。我们来看一下这个类:
public abstract class WebViewPage : WebPageBase, IViewDataContainer, IViewStartPageChild
{
private ViewDataDictionary _viewData;
private DynamicViewDataDictionary _dynamicViewData;
public AjaxHelper<object> Ajax { get; set; }
public HtmlHelper<object> Html { get; set; }
public object Model
{
get { return ViewData.Model; }
}
public TempDataDictionary TempData
{
get { return ViewContext.TempData; }
}
public UrlHelper Url { get; set; }
public dynamic ViewBag
{
get
{
if (_dynamicViewData == null)
{
_dynamicViewData = new DynamicViewDataDictionary(() => ViewData);
}
return _dynamicViewData;
}
}
public ViewContext ViewContext { get; set; }
public override void ExecutePageHierarchy()
{
Execute(); //这个函数是基类中定义的抽象函数,会在最终aspx/cshtml生成的类中被重载
}
public virtual void InitHelpers()
{
Ajax = new AjaxHelper<object>(ViewContext, this);
Html = new HtmlHelper<object>(ViewContext, this);
Url = new UrlHelper(ViewContext.RequestContext);
}
}
是不是在里面看到了很多平时最常用的属性。当用ExecutePageHierarchy
生成页面时,实际会调用Execute
函数,这个函数会在最终cshtml,aspx
等生成的类中被重载。我们看一个简单的例子:
假设我们有一个强类型的视图:/Views/Home/Index.cshtml
,一个简单的DemoModel
类型,只有一个UserName
属性:
@model Controllers.DemoModel
<div>Index</div>
@Model.UserName
那么这个Index.cshtml
被编译后就会生成下面这个类:
public class _Page_Views_Home_Index_cshtml : WebViewPage<DemoModel>
{
public override void Execute()
{
this.WriteLiteral("<div>Index</div");
this.Write(Model.UserName);
}
}
就这样,最终将WEB
显示出来。