本文讲解ViewEngine的作用, 并且深入解析了实现ViewEngine相关的所有接口和类, 最后演示了如何开发一个自定义的ViewEngine. 本系列文章已经全部更新为ASP.NET MVC 1.0版本.希望大家多多支持!
二.承上启下
首先注意: 我会将大家在MVC之前一直使用的ASP.NET页面编程模型称作ASP.NET WebForm编程模型.
上一讲中我们已经学习了如何向View传递Model, 以及如何在View中使用Model对象. 目前为止我们使用的都还是ASP.NET WebForm的页面模型,比如aspx页面,用户控件,母版页等. 最后这些页面中都要转换为HTML代码. 比如页面中的内嵌代码:
<% = ViewData["model"] %>
你是否思考过, 为何页面会支持<% %>这种语法? 为何最后一个aspx页面会在浏览器中以HTML代码的形式展现?
有人会回答这是ASP.NET自带的语法和功能. 没有错, ASP.NET帮我们做了编译页面, 输出HTML, 返回HTML给客户端浏览器等一系列工作.但是这些工作在MVC框架中有很多是属于View角色的职责. 为了继续使用原有的ASP.NET WebForm页面引擎, ASP.NET MVC抽象出来了ViewEngine这个角色. 顾名思义ViewEngine即视图引擎, 其主要作用就是找到View对象, 编译View对象中的语言代码(执行语言逻辑), 并且输出HTML. 下面讲解的WebFormViewEngine就是使用ASP.NET WebForm的页面编译/呈现功能实现的.
三.ViewEngine解析
下面将讲解和ViewEngine有关的各个接口和类.
IView接口
IView接口是对MVC结构中View对象的抽象, 此接口只有一个方法:
void Render(ViewContext viewContext, TextWriter writer);
Render方法的作用就是展示View对象, 通常是将页面HTML写入到Writer中供浏览器展示.
在本系列第三篇文章中我曾经分析过, 虽然IView对象是MVC中View角色的抽象, 并且提供了Render方法, 但是实际上真正的View角色的显示逻辑在ViewPage/ViewUserControl类中. 这是由于ASP.NET MVC提供的WebFormViewEngine视图引擎是使用原有的ASP.NET Web From的页面显示机制, 我们无法直接将WebForm模型中的页面转化为IView对象.
于是最后使用了一个折中的办法:
在IView对象的Render方法中调用WebForm页面的Render方法. WebFormView是目前ASP.NET MVC中唯一实现了IView接口的类
所以如果我们使用自定义的ViewEngine引擎, 就可以直接创建一个实现了IView接口的类实现Render方法.
IViewEngine接口
ViewEngine即视图引擎, 在ASP.NET MVC中将ViewEngine的作用抽象成了 IViewEngine 接口.
虽然IViewEngine的职责是寻找View对象, 但是其定义的两个方法:
• FindPartialView
• FindView
返回的结果是ViewEngineResult对象, 并不是View对象. 我们可以将ViewEngineResult理解为一次查询的结果, 在ViewEngineResult对象中包含有本次找到的IView对象.
ASP.NET MVC 提供了下面两个实现了IViewEngine接口的类:
• VirtualPathProviderViewEngine
• WebFormViewEngine
WebFormViewEngine是VirtualPathProviderViewEngine的派生类.
VirtualPathProviderViewEngine类实现了FindPartialView/FindView方法, 更够根据指定的路径格式搜索页面文件, 并且使用了提供了Cache机制缓存数据. 注意因为使用的是ASP.NET Cache,依赖HttpContext对象, 这就导致Cache无法在WebService或者WCf等项目中使用. VirtualPathProviderViewEngine寻找页面的方法依赖下面三个属性:
• MasterLocationFormats
• ViewLocationFormats
• PartialViewLocationFormats
在VirtualPathProviderViewEngine中只定义了这三个属性, 具体的值在派生类WebFormViewEngine中指定:
public WebFormViewEngine() {
MasterLocationFormats = new[] {
"~/Views/{1}/{0}.master",
"~/Views/Shared/{0}.master"
};
ViewLocationFormats = new[] {
"~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Views/Shared/{0}.aspx",
"~/Views/Shared/{0}.ascx"
};
PartialViewLocationFormats = ViewLocationFormats;
}
上面的代码中我们可以一步了然ViewEngine都搜索哪些路径.甚至还可以添加我们自己的路径和文件类型.
因为有了VirtualPathProviderViewEngine类, 在开发自定义的ViewEngine时不需要再编写搜索View文件的逻辑了.只需要定义搜索路径即可. 如果不使用ASP.NET WebForm的页面显示方式, 就需要自己定义的View对象如何显最后转化为HTML代码.
在后面的实例中会演示创建一个我们自定义的ViewEngine.
ViewEngineResult
ViewEngineResult是ViewEngine寻找View的查询结果.ViewEngineResult类没有派生类, 也就是说不同的ViewEngine返回的结果都是ViewEngineResult对象.
ViewEngineResult类有一个很重要的构造函数:
public ViewEngineResult(IView view, IViewEngine viewEngine)
以WebFormViewEngine为例, 在WebFormViewEngine类中定义了 MasterLocationFormats/ViewLocationFormats /PartialViewLocationFormats , 在调用FindPartialView/FindView方法时, 首先找到View对象的磁盘路径, 然后使用CreatePartialView/CreateView方法将磁盘路径转化实现了IView接口的WebFormView对象.
WebFormView中依然保存这页面对象的磁盘路径, 在调用Render时会根据磁盘路径创建ViewPage对象, 调用页面的Render方法.ASP.NET MVC编译页面时, 使用了.NET Framework 2.0以上的版本中提供的根据虚拟路径编译页面的函数:
BuildManager.CreateInstanceFromVirtualPath(string virtualPath, Type requiredBaseType)
命名空间为System.Web.Compilation.
ViewEngineCollection
ViewEngineCollection是IViewEngine对象的集合类. 在我们的系统中可以使用多个ViewEngine, 在寻找时会返回第一个匹配的ViewEngineResult, 下面是ViewEngineCollection类的Find方法代码:
private ViewEngineResult Find(Func
ViewEngineResult result;
foreach (IViewEngine engine in Items) {
if (engine != null) {
result = cacheLocator(engine);
if (result.View != null) {
return result;
}
}
}
List
foreach (IViewEngine engine in Items) {
if (engine != null) {
result = locator(engine);
if (result.View != null) {
return result;
}
searched.AddRange(result.SearchedLocations);
}
}
return new ViewEngineResult(searched);
}