從零開始學習 ASP.NET MVC 1.0 (五) ViewEngine 深入解析與應用實例
《從零開始學習ASP.NET MVC 1.0》 文章導航
- (一) 開天辟地入門篇
- (二) 識別URL的Routing組件
- (三) Controller/Action 深入解析與應用實例
- (四) View/Model 全解
- (五) ViewEngine 深入解析與應用實例
一.摘要
本文講解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 cacheLocator, Func locator) { ViewEngineResult result; foreach (IViewEngine engine in Items) { if (engine != null) { result = cacheLocator(engine); if (result.View != null) { return result; } } } List<string> searched = new List<string>(); 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); }
通過上面的代碼我們了解到, ViewEngineCollection會首先從Cache中搜索, 如果沒有搜索到結果,則根據路徑格式搜索. 如果最后還是沒有搜索到View對象則拋出找不到View的異常.所以雖然我們可以添加多個ViewEngine, 但是永遠不要為兩個ViewEngine指定同樣的搜索格式(路徑+文件類型), 因為如果出現一個頁面對象符合兩個ViewEngine的搜索格式的情況, 將無法控制使用哪一個ViewEngine輸出頁面.
在ViewBaseResult.ExecuteResult() 方法中, 調用了ViewEngineCollection.Find方法獲取ViewEngineResult對象,并調用其中的IView.Render()方法完成View對象的顯示.
四.開發自定義ViewEngine
下面通過示例演示如何開發自己的ViewEngine.其中要用到StringTemplate這個模板引擎, 在老趙的的MVC視頻教程中也使用的此引擎演示ViewEngine. StringTemplate負責翻譯一個模板頁上面的占位符(aspx頁面中的內嵌代碼), 輸出HTML.目前在StringTemplate的官方網站上已經提供了針對Asp.Net Mvc的ViewEngine.但是官方的ViewEngine模板沒有使用VirtualPathProviderViewEngine基類.下面我將提供一種不能說更好但至少是另一種實現的StringTemplateViewEngine.其中需要使用StringTemplate的模版功能.
1. 實現IView接口
要開發一個自己的ViewEngine, 首先要創建實現了IView接口的類, 在此我們創建了名為StringTemplateView的類
public class StringTemplateView : IView { #region 屬性 Properties /// /// StringTemplate 對象, 在構造函數中創建 /// private StringTemplate StringTemplate { get; set; } #endregion #region 構造函數 Constructed Function private StringTemplateView() { //不用于使用不帶參數的構造函數 this.StringTemplate = new StringTemplate(); } public StringTemplateView(StringTemplate template) { //null check if (template == null) throw new ArgumentNullException("template"); //set template this.StringTemplate = template; } #endregion #region IView 成員 void IView.Render(ViewContext viewContext, System.IO.TextWriter writer) { foreach(var item in viewContext.ViewData) { this.StringTemplate.SetAttribute(item.Key.ToString(), item.Value.ToString()); } //為StringTemplate設置HttpContext this.StringTemplate.SetAttribute("context", viewContext.HttpContext); //輸出模板 NoIndentWriter noIndentWriter = new NoIndentWriter(writer); this.StringTemplate.Write(noIndentWriter); } #endregion }
StringTemplateView是在StringTemplate視圖引擎中View角色的抽象, 所以功能是實現呈現頁面的Render方法. StringTemplate的核心功能就是一套自己定義的模板輸出引擎, 所以在構造StringTemplateView對象是必須傳入一個StringTemplate實例,在Render時只是調用StringTemplate對象的模板輸出方法.
2. 實現IViewEngine接口
有了IView對象. 接下來就要實現最核心的IViewEngine接口. 在具體的StringTemplateViewEngine類中, 要返回一個帶有StringTemplateView對象的ViewEngineResult.
在我的實現方法中,使用了ASP.NET MVC已經提供的VirtualPathProviderViewEngine類作為我們的基類. VirtualPathProviderViewEngine類實現了IViewEngine接口的方法, 提供了在程序中尋找View物理文件路徑的機制, 搜索時要使用在派生類中賦值的搜索路徑.
下面是我們的StringTemplateViewEngine類實現:
public class StringTemplateViewEngine : VirtualPathProviderViewEngine { private string _AppPath = string.Empty; #region 屬性 Properties public static FileSystemTemplateLoader Loader { get; private set; } public static StringTemplateGroup Group { get; private set; } #endregion public StringTemplateViewEngine(string appPath) { _AppPath = appPath; Loader = new FileSystemTemplateLoader(appPath); Group = new StringTemplateGroup("views", Loader); MasterLocationFormats = new[] { "/Views/{1}/{0}.st", "/Views//Shared/{0}.st" }; ViewLocationFormats = MasterLocationFormats; PartialViewLocationFormats = MasterLocationFormats; } protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) { return this.CreateView(controllerContext, partialPath, String.Empty); } protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath) { StringTemplate stringTemplate = Group.GetInstanceOf(viewPath.Replace(".st", "")); StringTemplateView result = new StringTemplateView(stringTemplate); return result; } }
注意首先在我們的StringTemplateViewEngine中,提供了搜索模板文件的路徑,即先從View/{controller}中搜索,再從View/Share中搜索. 這樣VirtualPathProviderViewEngine基類的方法就可以找到我們.st模板文件的具體路徑, 然后使用StringTemplateViewEngine中提供的創建StringTemplateView的方法, 根據具體路徑創建StringTemplateView對象.
在一些開源的ViewEngine中,尤其是MvcContrib項目中的ViewEngine都將創建View對象的功能放在一個ViewFactory類中, 個人認為這個更好的設計, 但是由于我們的StringTemplateViewEngine要繼承VirtualPathProviderViewEngine, 所以沒辦法拆分創建View的方法.
至此我們已經完成了StringTemplateViewEngine的全部工作.
3.使用StringTemplateViewEngine
(1)為 .st 模板頁增加智能感知
首先做一些準備工作. 因為我們的StringTemplate模板文件后綴是".st", 里面寫的大部分都是HTML代碼. 默認情況下Visual Studio是不會在編輯.st功能的時候提供智能感知支持的. 但是可以通過如下設置實現:
單擊菜單中的"工具"->"選項":
在"文本編輯器"的文件擴展名中, 如圖所示的為.st擴展名增加"HTML編輯器".
接下來在.st文件中就可以識別HTML代碼了:
(2) 創建公用的菜單模板
StringTemplate引擎支持模板的嵌套, 所以可以講兩個頁面公用的菜單欄放在menu.st文件中. 而且我們將此文件放在share文件夾中以便供所有模板頁調用. menu.st文件代碼如下:
<ul id="menu"> <li><a href="/StringTemplate/HelloST">HelloSTa>li> <li><a href="/StringTemplate/SharedST">SharedSTa>li> ul>
(3) 創建頁面模板和Controller
在Controller文件夾中, 創建StringTemplateController用于跳轉到我們的模板頁:
public class StringTemplateController : Controller { public ActionResult HelloST() { ViewData["msg"] = "Hello String Template ! "; return View("HelloST"); } }
在View文件夾中創建StringTemplate文件夾, 添加一個HelloST.st文件:
DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>在Shared文件夾中的st頁面title>
<link href="../Content/Site.css" rel="stylesheet" type="text/css" />head>
<body>
<div class="page">
<h1>StringTemplateViewEngine示例程序h1>
<div id="menucontainer">
$Views/Shared/menu()$
div>
<div id="main">
$msg$
div>
div>
body>
html>
示例中的代碼十分簡單, "$msg$"是StringTemplate的模板語言, 可以識別名稱為"msg"的變量. "$Views/Shared/menu()$"也是StringTemplate中的語法, 作用是加載名為menu的模板.
(4) 加載StringTemplateViewEngine 模板引擎
雖然Controller和View文件都建立好了, 但是因為ASP.NET MVC默認的視圖引擎是WebFormViewEngine, 但是可以同時使用多個視圖引擎, 比如可以為所有".st"后綴名的文件使用StringTemplateViewEngine視圖引擎.在Global.asax文件中, 在程序啟動時注冊我們的ViewEngine:
protected void Application_Start() { RegisterRoutes(RouteTable.Routes); //添加StringTemplate視圖引擎 StringTemplateViewEngine engine = new StringTemplateViewEngine(Server.MapPath("/")); ViewEngines.Engines.Add(engine); }
現在, 訪問"localhost/StringTemplate/HelloST",就可以看到我們的自定義的模板引擎的輸出結果了:
在文章最后會提供本實例附帶StringTemplateViewEngine的完整源代碼.
五.其他ViewEngine簡介
除了自己開發, 目前已經有了很多為ASP.NET MVC提供的ViewEngine:
MVCContrib項目中的ViewEngine:
- SparkViewEngine(不推薦)
- BrailViewEngine
- XsltViewEngine
StringTemplateViewEngine
這是StringTemplate項目為ASP.NET MVC開發的ViewEngine, 官方以及下載網址是
http://www.stringtemplate.org/
另外在著名的MonoRail項目中, 還有一些類似于StringTemplate的頁面顯示引擎, 雖然都沒有為ASP.NET MVC開發專門的ViewEngine, 但是還是很有參考價值的.我們可以用上面介紹的方法, 在頁面顯示引擎的基礎上自己開發ASP.NET MVC的ViewEngine:
MonoRail項目中的三個ViewEngine:
- AspNetViewEngine:用傳統的.aspx文件做模板, 可以照常使用aspx語法和服務器控件, 但是由于Webform的生命周期和MonoRail完全不同, 有時候會讓人覺得別扭, 有部分特性也受到了限制.
- NVelocityViewEngine: 用NVelocity做模板引擎, 需要學習VTL語法, 但是使用很簡單, 特別是很多java程序員已經熟悉velocity. 簡單的語法也強迫程序員把邏輯和界面很好的分離開來, 方便跟美工配合.
- BrailViewEngine:基于Boo的模板引擎, Boo是一種語法類似python的.NET語言, 據MonoRail的參考說, Brail引擎是功能最強, 性能最好的選擇, 但Boo是一種陌生的語言, 這成了Brail引擎應用的最大障礙.
六.總結
本篇文章詳細介紹了ViewEngine相關類, 已經如何開發自己的ViewEngine. 花了2周時間創作完成, 讓大家久等了. 說道最近博客園首頁的文章問題, 我覺得一篇文章除了要有知識點, 還有能夠很好的講解, 讓大家明白比讓自己明白更重要.我沒有為了速度草草發表文章,就是希望寫出來的東西能夠有資格發表到博客園首頁.
我希望大家都通過自律來建設博客園, 明白分享知識是一件光榮而且快樂的事情!
文章示例代碼下載: