最近正在看一本書《asp.net mvc 4 in action》,寫的不錯,想着一邊看一邊翻譯出來,這樣可以一句一句仔細看,以加深理解。
第三章 視圖基礎
視圖是asp.net mvc應用程序的主要部分——它們提供了一種清晰的方式來分割表現層和邏輯層。在上一章,我們簡略地看到我們的留言板程序用Razor模版引擎編寫的一些簡單視圖,在章節的最后,我們還看到如何在一個程序里用布局方式來使所有頁面都擁有一個一致的外觀感受。
在這一章,我們會更加深入的探討視圖——我們將檢驗asp.net mvc如何呈現視圖並且傳遞數據到視圖的各種選項。最后,我們將揭示最初在asp.net mvc 2中介紹的模版功能。為了展示這些功能,我們將給留言板程序增加一個編輯頁面。
3.1 視圖介紹
視圖的職責非常簡單。它們存在的意義就是利用傳遞給它的模型把內容展現出來。因為控制器及其相關的服務已經執行完了所有的業務邏輯,並把結果打包成了一個模型對象,視圖只需要知道如何使用模型並把它轉換成HTML。
雖然這種關注點分離減少了傳統asp.net程序所有的令人煩惱的大部分職責,但是視圖仍然需要仔細慎重的設計,以確保視圖不會變的復雜和難以維護。
在介紹傳遞數據到視圖的不同方式之前,先讓我們考察一下mvc框架是如何決定視圖被呈現的。
3.1.1 選擇要呈現的視圖
在第2章你已經看見通過在控制器的動作里調用View方法來呈現視圖。以下是GuestbookController里Create動作:
Public ActionResult Create()
{
Return View();
}
在這里,Views/Guestbook/Create.cshtml視圖文件被呈現出來。但是mvc框架是如何知道呈現這個特定的視圖而不是其他的視圖(如Index.cshtml)呢?
調用View方法返回的ViewResult對象知道如何呈現特定的視圖。當不帶參數調用這個方法時,框架推斷出要呈現的視圖名稱應該和動作(Create)的名稱一致。在以后的mvc管道中,框架的ControllerActionInvoker類執行ViewResult,告訴它呈現視圖。這時候,框架請求ViewEngineCollection定位適當的視圖來呈現。(正如你在第2章看到過的,視圖引擎默認是在Views/<controller name>目錄和Views/Shared目錄里查找視圖)。
視圖引擎
不同的視圖引擎用不同的格式呈現視圖。默認的,asp.net mvc有兩個視圖引擎——RazorViewEngine和WebFormViewEngine。Razor引擎用Razor格式呈現視圖(或者是.cshtml文件或者是.vbhtml文件),而Web Form引擎是用來支持舊的Web Form視圖(.aspx和.ascx文件)。以前的asp.net mvc默認只包括Web Form引擎。
為什么asp.net mvc 3要增加一個新的引擎?從asp.net 1.0版開始,Web Form允許代碼和標記符共同存在於aspx頁面里。然而,通常的開發實踐不鼓勵把原生的c#邏輯代碼放到aspx文件里。相反,開發者努力不所有邏輯代碼放在code-behind里。貫穿所有asp.net版本的進步是在aspx文件里包括更好的數據綁定和其他更多的是針對開發的。
在各種mvc框架里,鼓勵和要求直接使用標記符編寫視圖。因為aspx視圖引擎不是為此目的設計的,所以asp.net團隊決定建立一個完整的新的帶有code-focused模版方法的視圖引擎。結果,一個更智能的分析引擎出現了,不用開發者明確說明就能非常容易的計算出代碼在哪里結束,標記符在哪里開始。
加入其它的視圖引擎也是可能的,因此你可以使用第三方的格式呈現視圖。在第10章里我們使用流行的開源Spark視圖引擎來呈現視圖。
3.1.2 重新定義視圖名稱
如果願意,你可以重新定義動作名稱和視圖名稱相同的約定。例如,視圖名稱是New.cshtm而不是Create.cshtml,你調用View方法的第二個重載,它接受一個視圖名稱:
Return View(“New”);
作為選擇,如果視圖與相同名稱的控制器不在同一個子目錄下,你可以指定相對於應用程序的路徑:
Return View(“~/Views/SomeotherDirectory/New.cshtml”);
視圖只呈現本身不是很有用——通常我們想給視圖傳遞一些數據來起一些作用。接下來的部分,我們會看到為此目的一些不同的方法。
3.2 傳遞數據到視圖
在留言板應用程序里,我們已經看到一種傳遞GuestbookEntry對象集合到視圖的方法。在這部分,我們會看到三種不同的方法,分別是用ViewDataDictionary,ViewBag和強類型視圖。
3.2.1 ViewDataDictionary
用來傳遞模型信息到視圖的主要對象是ViewDataDictionary類。就像其他mvc框架,asp.net mvc暴露一個字典使控制器活動能傳遞任何數量的模型對象和信息給視圖。隨着字典對象,我們能傳遞適當呈現視圖需要的大量的數據。
例如,讓我們看看如何擴展留言板頁面,以便任何人都能查看留言板,但只有當前登錄的用戶能編輯留言板記錄。為了在留言板記錄詳細頁面顯示留言信息,我們直接把GuestbookEntry對象傳遞到視圖,下面是GuestbookEntry對象:
public class GuestbookEntry
{
public int Id { get; set; }
public string Name { get; set; }
public string Message { get; set; }
public DateTime DateAdded { get; set; }
}
雖然GuestbookEntry類有所有要顯示的信息,但是不包括當前登陸者或是否顯示編輯鏈接的任何信息。我們需要給視圖比GuestbookEntry對象更多的信息。我們可以使用ViewDataDictionary來提供額外的信息,如下顯示:
清單3.1 控制器的Show活動
public ViewResult Show(int id)
{
var entry = _db.Entries.Find(id);
bool hasPermission = User.Identity.Name ==entry.Name;
ViewData["hasPermission"] =hasPermission;
return View(entry);
}
在控制器基類里,我們可以利用在ViewData屬性里要傳遞到視圖的ViewDataDictionary對象。我們檢查當前用戶的名稱,比較它和留言記錄里所示的Name屬性,把比較的結果放進ViewData的hasPermission鍵里。接着,我們用View方法創建一個ViewResult對象,設置ViewData的Model屬性為GuestbookEntry對象。
在視圖上,我們從ViewData里取出hasPermission信息,用它來隱藏編輯鏈接。
清單3.2 用ViewData信息隱藏一個鏈接
<p>
@{
bool hasPermission =(bool) ViewData["hasPermission"];
}
@if (hasPermission)
{
@Html.ActionLink("Edit","Edit",new {id = Model.Id})
}
@Html.ActionLink("Back toEntries", "Index")
</p>
在視圖里,我們從ViewData里提取hasPermission信息。接着,我們基於hasPermission變量有條件的顯示編輯鏈接。最后,我們給用戶顯示一個回到留言板記錄列表頁面的鏈接。最后呈現的頁面見圖3.1.
雖然ViewDataDictionary非常方便(你可以在里邊存任何東西),但在句法上並不是很好——你不得不執行類型轉換在從字典里獲取數據時。Asp.net mvc包括一個在ViewData里動態存儲數據的變通的方法——ViewBag。
3.2.2 ViewBag
就像ViewDataDictionary,viewBag提供一種從控制器傳遞數據到視圖的方法,但是ViewBag利用了c# 4里邊動態語言的特點。取代在字典里用字符串鍵存儲數據,你可以在控制器里的動態ViewBag屬性上簡單地設置它:
ViewBag.HasPermission = hasPermission;
ViewBag的屬性在視圖里也是有效的,於是取代從ViewData里獲取數據並轉換成布爾類型,我們可以直接訪問ViewBag:
<p>
@if (ViewBag.HasPermission)
{
@Html.ActionLink("Edit","Edit", new {id = Model.Id})
}
@Html.ActionLink("Back to Entries", "Index")
</p>
雖然ViewData和ViewBag都提供了極大地方便,但也帶來了成本。這些技術既對代碼重構不友好,也不能在編譯時發現你偶爾鍵入動態屬性名稱時的錯誤。另外,Visual Studio對動態特性或ViewData沒有智能提示(雖然第三方工具提供這些如JetBrains ReSharper)。
此外,你不容易把元數據附加到動態屬性上。Asp.net mvc使用特性(attributes)附加元數據給特定類型(例如,在System.ComponentModel.DataAnnotations名字空間里的驗證特性能被用於標記一個域是必須的,或者一個域的最大長度)。這些特性不能與動態ViewBag屬性一起使用。
作為一個選擇方案,可以使用強類型視圖,強類型視圖表示一個視圖使用了一個特定已知的強類型類。用這種方式,你仍然可以利用智能提示和Visual Studio的反射工具,還能受益於使用特性驅動的元數據。我們將在下一部分看到這些是如何工作的。