作为 MVC 的最后一步,就是要将精心准备的数据展示出来。Controller 提供了几个重载的 RenderView() 来完成这个工作。
public class Controller : IController
{
protected virtual void RenderView(string viewName, string masterName, object viewData)
{
ViewContext viewContext = new ViewContext(this.ControllerContext, viewName, masterName, viewData, this.TempData);
this.ViewEngine.RenderView(viewContext);
}
public IViewEngine ViewEngine
{
get
{
return (this._viewEngine ?? new WebFormViewEngine());
}
set
{
// ...
this._viewEngine = value;
}
}
}
方法很简单,将一路传递过来的相关 "环境数据" (上下文) 再次包装。当然,这次依然会多出些东西,里面就有我们向视图传递的数据 —— viewData & tempData。作为默认选择,MVC 创建 WebForm 视图引擎来展示结果 (我个人更倾向使用 NVelocity Template Engine,会在后面的章节提供源代码)。继续跳转,我们看看这个 WebFormViewEngine 是何方高人。
public class WebFormViewEngine : IViewEngine
{
protected virtual void RenderView(ViewContext viewContext)
{
// ...
string viewLocation = this.ViewLocator.GetViewLocation(viewContext, viewContext.ViewName);
// ...
object obj2 = this.BuildManager.CreateInstanceFromVirtualPath(viewLocation, typeof(object));
// ...
ViewPage page = obj2 as ViewPage;
if (page != null)
{
if (!string.IsNullOrEmpty(viewContext.MasterName))
{
string masterLocation = this.ViewLocator.GetMasterLocation(viewContext, viewContext.MasterName);
// ...
page.MasterLocation = masterLocation;
}
page.SetViewData(viewContext.ViewData);
page.RenderView(viewContext);
}
else
{
ViewUserControl control = obj2 as ViewUserControl;
// ...
if (!string.IsNullOrEmpty(viewContext.MasterName))
{
throw new InvalidOperationException(MvcResources.WebFormViewEngine_UserControlCannotHaveMaster);
}
control.SetViewData(viewContext.ViewData);
control.RenderView(viewContext);
}
}
public IViewLocator ViewLocator
{
get
{
if (this._viewLocator == null)
{
this._viewLocator = new WebFormViewLocator();
}
return this._viewLocator;
}
set
{
this._viewLocator = value;
}
}
}
精简了一下代码,看上去不算很复杂。首先会创建一个 WebFormViewLocator 对象来获取视图存放路径,下面的代码对于已经使用 MVC 的兄弟应该很熟悉了。
public class WebFormViewLocator : ViewLocator
{
public WebFormViewLocator()
{
base.ViewLocationFormats = new string[]
{
"~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Views/Shared/{0}.aspx",
"~/Views/Shared/{0}.ascx"
};
base.MasterLocationFormats = new string[]
{
"~/Views/{1}/{0}.master",
"~/Views/Shared/{0}.master"
};
}
protected virtual string GetViewLocation(RequestContext requestContext, string viewName)
{
// ...
string str = this.GetPath(requestContext, this.ViewLocationFormats, viewName);
// ...
return str;
}
}
在获取路径后,WebFormViewEngine 通过一个包装类 BuildManagerWrapper 间接调用 System.Web.Compilation.BuildManager 的静态方法 CreateInstanceFromVirtualPath() 将视图进行编译,并返回一个对象实例。(System.Web.Compilation.BuildManager 提供一组有助于管理 ASP.NET 应用程序编译的方法)
再往后的方法就很有趣了,通过 as 转换结果来判断视图是 ViewPage 还是 ViewUserControl。呵呵~~~ 我承认,我也经常写类似的代码。ViewPage 继承自我们所熟悉的 System.Web.UI.Page,它的 RenderView() 方法也不过是完成 Page.ProcessRequest() 的最终处理而已。
public class ViewPage : Page, IViewDataContainer
{
public virtual void RenderView(ViewContext viewContext)
{
this.ViewContext = viewContext;
this.Ajax = new AjaxHelper(viewContext);
this.Html = new HtmlHelper(viewContext);
this.Url = new UrlHelper(viewContext);
this.ProcessRequest(HttpContext.Current);
}
}
反而是 ViewUserControl 比较好玩,你会注意到,我们可以直接在 Controller 中 RenderView 一个用户控件(ViewUserControl),这似乎有些古怪。如果你继续深入跟踪 "control.RenderView(viewContext);" 就会看到如下代码,某些 "好人" 会替我们创建了一个 "空白页" 来装载这个控件。当然,RenderView(ViewUserControl) 有个限制,就是不能有 Master。
public class ViewUserControl : UserControl, IViewDataContainer
{
public virtual void RenderView(ViewContext viewContext)
{
viewContext.HttpContext.Response.Cache.SetExpires(DateTime.Now);
new ViewUserControlContainerPage(this).RenderView(viewContext);
}
}
private sealed class ViewUserControlContainerPage : ViewPage
{
public ViewUserControlContainerPage(ViewUserControl userControl);
}
好了,RenderView() 到此为止。
我们从 UrlRoutingModule 开始,历经 MvcRouteHandler、MvcHandler、Controller、ActionFilterAttribute,直到最后的 ViewEngine、ViewPage,总算是完成了一次 "探索之旅"。虽然过程有些简单,但对于我们理解和更好地使用 MVC 却多少有些帮助。后面章节相关功能开发和扩展,都会用到这些分析结果,大家还是不要偷懒的好……
---------------------
附: 流程分析图
查看大图