天天看點

《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】

在前面很多的章節裡面的,最常用的action result是視圖呈現并傳回給用戶端的ViewResult類型。本章會專注于視圖的原理,首先展示MVC架構是如何使用視圖引擎處理ViewResults的,包括闡釋如何建立一個視圖引擎。接着介紹使用Razor視圖引擎的一些技術。最後是關于建立和使用部分視圖,子actions,以及Razor片段,這些都是涉及高效MVC開發的本質話題。

建立一個自定義視圖引擎(Creating a Custom View Engine)

MVC架構包含了兩個内置的,功能完善,容易了解的視圖引擎:

a.Razor引擎:MVC3裡面引入的一個新的視圖引擎,具有更加簡潔和優雅的文法。

b.ASPX引擎:使用的是webform裡面的标簽文法"<%..%>",用于維護相容舊的MVC程式。

這裡建立一個自定義的視圖引擎的目的是為了闡釋請求管道的運作原理以及完善我們對MVC架構運作原理的認識,包含了了解視圖引擎在轉化一個ViewResult到響應用戶端的輸出有多少自主性。

視圖引擎實作了IViewEngine接口,如下所示:

《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】
《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】

View Code

視圖引擎扮演的角色是将請求轉換成ViewEngineResult對象,前面兩個方法包含的參數分别是:描述請求,處理請求的控制器,視圖及其布局,以及是否允許從它的緩存裡面取出前一次的結果。這些方法在ViewResult被處理時調用。最後一個方法當視圖不在需要時被調用。

注:MVC架構對視圖引擎的支援是通過ControllerActionInvoker類實作的,這個類是IActionInvoker接口的實作。如果我們實作的是自己的action調用者或控制器工廠的話,這樣是不會自動通路視圖引擎的功能。

ViewEngineResult類允許視圖引擎在一個視圖被請求時響應給MVC架構,我們通過選擇兩個構造器中的一個來表達結果。

如果視圖引擎能夠提供一個針對請求的視圖,那麼我們可以使用的構造器是:public ViewEngineResult(IView view, IViewEngine viewEngine)

否則,能使用的構造器為:public ViewEngineResult(IEnumerable<string> searchedLocations) ,參數提供能夠找到視圖的位置。

視圖引擎系統最後一塊是IView接口,如下:

《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】
《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】

我們傳遞一個IView接口的實作給ViewEngineResult對象的構造器,然後從視圖引擎的方法傳回。MVC架構調用Render方法,ViewContext參數包含了關于請求的資訊和action方法的輸出。TextWriter參數将輸出寫到用戶端。下面會建立一個視圖引擎,隻傳回一種視圖,該視圖會呈現包含請求資訊的結果以及由action方法産生的視圖資料。從建立視圖引擎的過程裡面能讓我們了解視圖引擎運作的方式,這裡是沒有涉及到解析視圖模版的。

建立一個自定義的IView接口(Creating a Custom IView)

《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】
《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】

建立IViewEngine實作(Creating an IViewEngine Implementation)

我要明确視圖引擎的目的是生産一個ViewEngineResult對象,該對象包含一個IView接口或者是一個包含視圖位置的清單。上面建立了IView接口,下面接着建立視圖引擎,如下:

《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】
《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】

這裡建立的視圖引擎支援DebugData視圖,當有一個針對該視圖的請求時,則傳回一個IView接口實作的執行個體,像這樣:return new ViewEngineResult(new DebugDataView(), this);

如果我們要實作更加正式的視圖引擎,需要利用這個時機尋找模版,并考慮布局和提供緩存設定。這裡的例子僅僅需要建立一個DataDebugView類的執行個體,如果接收到其他的請求,會傳回如:return new ViewEngineResult(new string[] { "Debug Data View Engine" }); IViewEngine接口假定視圖引擎具有它需要用來尋找視圖的位置資訊,這是非常合理的假設,因為視圖是典型的存儲在項目裡的模版檔案。這裡并不需要去找,我們傳回是一個虛拟的位置。這裡也沒有支援部分視圖,也沒有實作ReleaseView方法,因為沒有資源需要釋放。

注冊自定義的視圖引擎(Registering a Custom View Engine)

注冊視圖引擎的方法有很多,首選的是在Global的Application_Start方法裡面添加 ViewEngines.Engines.Add(new DebugDataViewEngine());

靜态的ViewEngine.Engines集合包含了一套安裝在應用程式的視圖引擎。當一個ViewResult正被處理時,action調用者獲得已經安裝的視圖引擎的集合并輪流調用它們的FindView方法。當action調用者收到一個包含IView接口的ViewEngineResult對象時停止調用FindView方法。這意味着添加到ViewEngines.Engines裡面的引擎的順序非常重要,特别是在有多個引擎服務同名的視圖時。

如果想要我們的視圖引擎優先,可以利用集合的插入方法如下:

ViewEngines.Engines.Insert(0, new DebugDataViewEngine());

還有一種方法是使用全局依賴解析器,當應用程式啟動時,依賴解析器會請求任何可用的IViewEngine接口實作。如果使用NinjectDependencyResolver類,我們可以在AddBinds方法裡注冊視圖引擎,

如:private void AddBindings()

{

    //綁定

    Bind<IViewEngine>().To<DebugDataViewEngine>();   

}

使用這種方式不能控制視圖引擎被處理的順序,如果我們要關注視圖引擎會互相競争,最好還是選擇ViewEngines.Engines集合裡面的Insert方法來實作。下面測試我們自定義的視圖引擎,建立一個HomeController類如下:

《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】
《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】

這時可以運作程式,結果如下:

《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】

出現上面的結果是因為針對我們處理的視圖的FindView被調用了。如果我們改變下Index方法以至于ViewResult請求一個視圖時沒有安裝的引擎能夠響應,如下:

return View("No_Such_View"); 這時我們運作程式,action調用者會去周遊每一個可用的視圖引擎并調用它們的FindView方法。沒有一個能夠服務這個請求,并且會傳回它們尋找過位置集合。如圖:

《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】

使用Razor引擎(Working with the Razor Engine)

在前面我們通過實作兩個接口建立了自定義的視圖引擎,當然是非常簡單的,隻是為了說明其原理。視圖引擎的複雜性來自于視圖模版系統,包含了代碼分片,支援布局,可編譯來優化性能。這些在我們自定義的視圖引擎裡面是沒有的,也不需要,因為Razor引擎考慮了所有的。

了解Razor視圖渲染(Understanding Razor View Rendering)

Razor視圖引擎通過編譯視圖來改善性能,視圖會被轉變為C#類,然後編譯。這也是為什麼我們能夠在視圖裡面包含C#代碼片段的原因。看下Razor視圖建立的源碼會非常有益的,因為它幫助我們放置了很多Razor功能在上下文Context裡面。下面是一個簡單的Razor視圖,擷取了一個字元串數組作為視圖模型對象:

《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】
《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】

在應用程式啟動時,視圖才會被編譯,也隻有在這個時候才能看到由Razor建立的C#類,我們需要啟動應用程式并導航到一個action方法。然後一個actin方法都行,因為初始化請求時會觸發視圖編譯過程。非常友善的是,建立的C#類會作為C#代碼檔案寫入磁盤然後編譯,這意味着我們能夠檢視到C#具體的代碼。如果是Windows7系統,檢視路徑為:c:\Users\yourLoginName\AppData\Local\Temp\Temporary ASP.NET Files。下面是一個示例檔案:

《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】
《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】

首先,可以注意到這個類是從WebViewPage<T>派生的,T表示model的類型,這也是能夠處理強類型視圖的原因。也可以發現視圖檔案的路徑是怎樣被編碼為類名的。這也是Razor映射視圖的請求到已編譯的類的執行個體的原理。在Execute方法裡,我們能夠看到視圖裡面語句和元素是怎樣被處理的。HTML元素被WirteLiteral方法處理,這個方法可以将參數的内容寫到結果裡面。這剛好跟Write方法相反,Write方法對C#變量使用并對字元串的值編碼讓它們能夠在HTML頁面安全使用。

WriteLiteral和Write方法都是将内容寫到一個TextWriter對象裡面。這個對象同樣被傳遞給了IView.Render方法。一個已經編譯的Razor視圖的目的是為了建立靜态和動态的内容并通過TextWriter發送給用戶端。

添加依賴注入到Razor視圖(Adding Dependency Injection to Razor Views)

MVC請求處理管道的每一個部分都支援DI(Dependency Injection),Razor也不例外。然而所不同的是,這裡擴充了類和視圖之間的邊界。下面是一個例子來說明:

建立一個接口ICalculator如下:

《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】
《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】

如果我們想要一個視圖能夠使用ICalculator并且具有注入的實作,那麼我們需要建立一個派生自WebViewPage的抽象類,如下:

《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】
《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】

支援DI最簡便的方式就是使用屬性注入(property injection),即:我們的DI容器注入依賴到一個屬性裡面,而不是最開始時使用的構造器注入。這裡使用的了Inject特性,為了在視圖裡面利用它,我們使用Razor繼承元素,如下所示:

《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】
《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】

我們能夠使用@inherits指定基類,這樣就能夠通路Calculator屬性并接收ICalculator接口的實作,所有這一切都沒有建立view和ICalculator接口之間的依賴。使用了@inherits元素的效果就是改變了實體編譯建立的類的基類,如:public class _Page_Views_Home_Calculate_cshtml : Views.Models.ViewClasses.CalculatorView {...}

由于建立的類是從我們的抽象類派生的,是以能夠通路我們自己定義的Calculator屬性。剩下的就是使用依賴解析器注冊ICalculator接口實作,以至于Ninject将被用來建立視圖類并且有機會實施屬性注入。如下:

《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】
《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】

這時可以運作程式得到如下結果:

《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】

配置視圖搜尋位置(Configuring the View Search Locations)

Razor視圖引擎遵循早期MVC版本裡面尋找視圖的建立的約定,如果請求與Home控制器關聯的Index視圖,Razor按照下面的清單尋找:

•  ~/Views/Home/Index.cshtml

•  ~/Views/Home/Index.vbhtml

•  ~/Views/Shared/Index.cshtml

•  ~/Views/Shared/Index.vbhtml

Razor不是真的在磁盤上尋找視圖檔案,因為視圖這個時候已經編譯成了C#類。Razor實際上是尋找的這些代表視圖的編譯後的C#類,.cshtml檔案是包含C#語句的模版。我們可以通過建立RazorViewEngine的子類來改變Razor搜尋的視圖檔案。它是建立在一序列的基類之上的,這些基類包含了決定哪一個視圖檔案被搜尋的一套屬性。如下:

《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】

占位符{0},{1},{2}分别表示:視圖名,控制器名,區域名。

為了改變搜尋的位置,我們建立一個從RazorViewEngine派生的類并改變屬性的值,下面是一個示例:

《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】
《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】

代碼裡面我們為ViewLocationFormats設定了新的值,新的數組包含了全部的.cshtml檔案。另外,還改變了公共視圖的尋找位置,不再是Views/Shared而是Views/Common。接着需要在Application_Start方法裡面注冊:

《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】
《Pro ASP.NET MVC 3 Framework》學習筆記之二十七【視圖1】

記住一點:action調用者輪流去每一個視圖引擎尋找視圖,等到我們添加視圖到集合裡面時,它已經包含了标準的Razor視圖引擎。為了避免跟其他的視圖引擎競争,我們調用Clear方法移除任何可能已經注冊的視圖引擎并調用Add方法注冊我們自己實作的視圖引擎。

添加動态的内容到Razor視圖(Adding Dynamic Content to a Razor View)

視圖的全部意圖就是允許我們将領域模型作為使用者接口呈現出來。為了達到這個目的,我們需要添加動态的内容,動态内容是在運作時建立的,并且每一次請求建立的内容都可以不同。這個跟靜态内容是相反的,像HTML這些靜态的内容在每次請求時都是一樣的。添加動态内容的方式有四種:内嵌代碼(Inline code),HTML輔助方法(HTML helper methods),部分視圖(Partial views),子action(Child actions).下面分别對每一種方式進行介紹:

使用内嵌代碼(Using Inline Code)

建立動态内容最簡便的方式就是使用内嵌代碼——一行或多行以@符合開始的C#代碼。這是Razor視圖引擎的核心。

内嵌代碼與分解關注點:學過ASP.NET WebForms的人可能非常想知道為什麼會對内嵌代碼如此着迷,畢竟在WebForms裡面的約定是盡可能的将代碼放在代碼隐藏檔案裡面。這很容易混淆,特别是當我們認為代碼隐藏檔案常常是對分解關注點的維護時。這個分歧的出現是因為MVC和WebForms對哪一個關注點應該被分解以及分界線應該在哪有不同的概念。WebForms将陳述性的HTML标記和程式邏輯分開,ASPX包含HTML标簽,代碼隐藏檔案包含邏輯。與之相對的是MVC,它是将展示邏輯和應用邏輯分開,控制器和領域模型分别負責應用邏輯和領域邏輯,視圖包含展示邏輯。是以内嵌代碼更好的适應了MVC架構。Razor句法讓我們很容易的建立可維護,可擴充的視圖。

内嵌代碼功能的靈活性很容易模糊應用程式的邊界,并且讓分解關注點的思想瓦解。作為一種紀律限制:必須通過某種方式來維護分解關注點的有效性,那就是與MVC設計模式保持一緻。當我們着手使用MVC架構時,建議将關注點放在了解功能上。當我們有了更多的經驗時,會自然形成将視圖和MVC元件的職責分開的感覺。

好了,今天的筆記就到這裡,關于這章的筆記在下一篇接着做完。

晚安!

繼續閱讀