1.1理解視圖約定
當創建一個項目模版時,可以注意到,項目以一種非常具體的方式包含了一個結構化的Views目錄。在每一個控制器的View文件夾中,每一個操作方法都有一個同名的視圖文件與其對應。這就提供了視圖與操作方法關聯的基礎。
1 public ActionResult Index() 2 { 3 return View(); 4 }
視圖選擇邏輯在/Views/ControllerName目錄(這里就是去掉Controller后綴的控制器名)下查找與操作方法同名的視圖。此處選擇的是/Views/Home/Index.cshtml。
與ASP.NET MVC中的大部分方法一樣,這一約定是可以重寫的。想讓Index操作方法渲染一個不同的視圖,可以向其提供一個不同的視圖名稱,代碼如下:
1 public ActionResult Index() 2 { 3 return View("NotIndex"); 4 }
對於上面的編碼,操作方法依然在/Views/Home目錄中查找視圖,但選擇的不再是Index.cshtml,而是NotIndex.cshtml。
如果需要制定完全位於不同目錄結構中的視圖,編碼如下:
1 public ActionResult Index() 2 { 3 return View("~/Views/Example/Index.cshtml"); 4 }
1.2 強類型視圖
假設需要編寫一個顯示Album實例列表的視圖,一種方法是將專輯添加到ViewBag中,然后在視圖中進行迭代。
1 public ActionResult List() 2 { 3 var albums = new List<Album>(); 4 for (int i = 0; i < 10; i++) 5 { 6 albums.Add(new Album { Title = "Product" + i }); 7 } 8 ViewBag.Albums = albums; 9 return View(); 10 }
然后,再在視圖中迭代顯示,如下代碼:
1 <ul>
2 @foreach (Album a in (ViewBag.Albums as IEnumerable<Album>)) 3 { 4 <li>@a.Tilte</li>
5 } 6 </ul>
注意在枚舉之前需要將動態的ViewBag.Albums轉換為IEnumerable<Album>類型。為了使代碼整潔,可以使用dynamic關鍵字,但是當訪問每個Album對象的屬性時,就不能再使用智能感知功能。
1 <ul> 2 @foreach (dynamic p in ViewBag.Albums) 3 { 4 <li>@p.Tilte</li> 5 } 6 </ul>
強類型視圖既能獲得dynamic的簡潔語法,又能獲得強類型和編譯時檢查的好處(比如正確的輸入屬性和方法名稱)。強類型視圖允許設置視圖的模型類型。因此可以從控制器向視圖傳遞一個在兩端都是強類型的模型對象,從而獲得智能感知、編譯器檢查等好處。
在Controller方法中,可以通過向重載的View方法中傳遞模型實例來指定模型,代碼如下:
1 public ActionResult List() 2 { 3 var Musics = new List<MusicModels>(); 4 for (int i = 0; i < 10; i++) 5 { 6 Musics.Add(new MusicModels { MusicName = "MusicName" + i.ToString() }); 7 } 8 return View(Musics); 9 }
下一步是告知視圖哪種類型的模型正在使用@model聲明。但要注意這里需要輸入模型類型的完全限定類型名(名稱空間和類型名稱),如下所示
1 @model IEnumerable<MvcMusicStore.Models.MusicModels>
2 <ul>
3 @foreach(MvcMusicStore.Models.MusicModels music in Model) 4 <li>@music.SingerName</li>
5 </ul>
如果不想輸入模型類型的完全限定類型名,可使用@using關鍵字,如下所示
1 @using MvcMusicStore.Models 2 @model IEnumerable<MusicModels>
3 <ul>
4 @foreach(MusicModels music in Model) 5 <li>@music.SingerName</li>
6 </ul>
對於在視圖中經常使用的名稱空間,好的方法是在Views目錄下的web.config文件中聲明:
<add namespace="MvcMusicStore.Models">
1.3 理解ViewBag、ViewData和ViewDataDictionary
之前介紹了使用ViewBag從控制器向視圖傳遞信息,然后介紹了傳遞強類型模型。現實中,這些都是通過ViewDataDictionary傳遞的。
從技術的角度看,數據從控制器傳送到視圖是通過一個名為ViewData的ViewDataDictionary(這是一個特殊的字典類)。我們可以使用標准的字典語法設置或讀取其中的值:
ViewData["CurrentTime"] = DateTime.Now;
盡管這種語法現在也能用,但是MVC3提供了更簡單的語法,可以利用C#4的dynamic關鍵字。ViewBag是ViewData的動態封裝器。這樣我們就可以按照下面的方式來設置值:
ViewBag.CurrentTime = DateTime.Now;
ViewBag.CurrentTime和ViewData["CurrentTime"] 起到了等同的作用。
一般來說,大部分代碼使用ViewBag,而不是ViewData,這兩種語法並不存在技術上的差異,僅僅是因為ViewBag相對於字典語法而言看上去好看。
注意,ViewBag和ViewData的差異:
- 只有當要訪問的關鍵字是一個有效的C#標識符時,ViewBag才起作用。例如,如果在ViewData["Key With Spaces"]中存放一個值,那么就不用使用ViewBag訪問,因為無法通過編譯。
- 動態值不能作為一個參數傳遞給擴展方法,因為C#編譯器為了選擇正確的擴展方法,在編譯時必須知道每一個參數的真正類型。
2.視圖模型
在操作方法上右擊 --> “添加視圖”
彈出添加視圖頁:
- View name:當從一個操作方法的上下文中打開這個對話框時,視圖的名稱默認被填充為操作方法的名稱。視圖的名稱是必須有的。
- Template:一旦選擇一個模型類型,就可以選擇一個基架模版。這些模版利用Visual Studio模版系統來生成基於選擇模型類型的視圖。
- 引用腳本庫:這個選項用來指示要創建的視圖是否應該包含指向JavaScript庫(如果對視圖有意義的話)的引用。默認情況下,_Layout.cshtml文件既不引用jQuery Validation庫,也不引用Unobtrusive jQuery Validation庫,只引用主jQuery庫。
當創建一個包含數據條目表單的視圖(如Edit視圖或者Create視圖)時,選擇這個選項會添加對jqueryval捆綁的腳本引用。如果要實現客戶端驗證,那么這些庫就是必須的。除這種情況外,完全可以忽略這個復選框。
- 創建一個分部視圖:選擇這個意味着要創建的視圖不是一個完整的視圖,因此,Layout選項是不可選用的。生成的部分視圖除了在其頂部沒有<html>標簽和<head>標簽外,很像一個常規的視圖。
- 使用布局頁:這個選項決定了要創建的視圖是否引用布局,還是成為一個完全獨立的視圖。如果選擇使用默認的布局,就沒必要指定一個布局了,因為在_ViewStart.cshtml文件中已經指定了布局。這個選項是用來重寫默認布局文件的。
2.2 Razor視圖引擎
ASP.NET MVC中提供了兩種不同的視圖引擎:較新的Razor視圖引擎和較早的WebForms視圖引擎。
Razor中的核心轉換字符是(@),這個單一字符用作標記-代碼的轉換字符,有時也反過來用作代碼-標記的轉換字符。
這里一共有兩種基本類型的轉換:代碼表達式和代碼快。
<h1>Listing @items.Length items.</h1>
表達式@items.Length是作為隱式表達式來求解的,然后輸出表達式的值3。這里不需要指出代碼表達式的結束位置。
但是Razor自動從代碼轉回標記的能力,也帶來了二義性的問題:
1 @{ 2 string rootNamespace = "MyApp"; 3 } 4 <span>@rootNamespace.Models</span>
這個示例想要的輸出結果是: <span>MyApp.Models</span> ,然而實際會出現錯誤,提示string沒有Models屬性,此時需要通過圓括號解決:
1 @{ 2 string rootNamespace = "MyApp"; 3 } 4 <span>@(rootNamespace).Models</span>
這樣可以告訴Razor,.Models是字面量文本,而不是代碼表達式的一部分。
對於電子郵件地址時的情況,Razor可以辨別出郵件的模式,進而不處理這種形式的表達式:
<span>support@megacorp.com</span>
但是如果確實想將這種形式的字符串作為一個表達式,也需要合理用圓括號:
對於
<li>Item_@item.Length</li>
期望的輸出結果是<li>Item_3</li>,但是Razor會將其按照字符串進行打印。
處理的方法是:
<li>Item_@(item.Length)</li>
有時也需要使用@符號來進行轉義:
1 <p>
2 You should follow 3 @@aspnet 4 </p>
3.Razor語法示例
常見用途下的Razor語法;
- 隱式代碼表達式
代碼表達式將被計算並將值寫入到響應中,這就是視圖中顯示值的一般原理。
<span>@model.Message</span>
- 顯示代碼表達式
代碼表達式的值將被計算並寫入到響應中,這就是在視圖中顯示值的一般原理
<span>1 + 2 = @(1 +2 )</span>
- 無編碼代碼表達式
有些情況下,需要顯式的渲染一些不應該采用HTML編碼的值,這時可以采用Html.Raw方法來保證該值不被編碼
<span>@Html.Raw(model.Message)</span>
- 代碼塊
不像代碼表達式先求的表達式的值,然后再輸出到響應,代碼塊是簡單地執行代碼部分
1 @{ 2 int x = 123; 3 string y = "because."
4 }
- 文本和標記相結合
這個例子顯示了在Razor中混用文本和標記的概念,具體如下:
1 @foreach (var item in items) 2 { 3 <span>Item @item.Name.</span>
4 }
- 混合代碼和純文本
Razor查找標簽的開始位置以確定何時將代碼轉換為標記。然而,有時可能想在一個代碼塊之后立即輸出純文本。例如,在下面的這個例子中就是展示如何在一個條件語句塊中顯示純文本
1 @if (showMessage) 2 { 3 <text>this is plain text</text>
4 }
或者
1 @if (showMessage) 2 { 3 @:this is plain text. 4 }
第一種使用<text>標簽,這樣只是把標簽內容寫入到響應中,而標簽本身則不寫入。第二種方式使用一種特殊的語法,實現代碼到純文本的轉換,但是這種方法每次只能作用於一行文本。
- 轉義代碼分隔符
可使用“@@”來編碼“@”以達到顯示“@”的目的。此外,時鍾都可以選擇使用HTML編碼來實現。
Razor:
the asp.net twitter handle is @aspnet
或者
the asp.net twitter handle is @@aspnet
- 代碼注釋
1 @* 2 代碼塊 3 *@
4.布局
Razor的布局有助於使應用程序的多個視圖保持一致的外觀。可以使用布局為網站定義公共模版(或只是其中的一部分)。公公模版包含一個或多個占位符,應用程序中的其他視圖為它們提供內容。從某些角度看,布局很像視圖的抽象基類。如下則是一個簡單的布局:
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <title>@ViewBag.Title</title>
5 </head>
6 <body>
7 <h1>@ViewBag.Title</h1>
8 <div id="main-content">@RenderBody()</div>
9 <footer>@RenderSection("Footer")</footer>
10 </body>
11 </html>
對應視圖的代碼如下:
1 @{ 2 Layout = "~Views/Shared/SiteLayout.cshtml"; 3 ViewBag.Title = "The Index"; 4 } 5 6 <p>this is the main content!</p> 7 8 @section Footer{ 9 this is the <strong>footer</strong>. 10 }
@section語法為布局中定義的一個節指定了內容。
默認情況下,視圖必須為布局中定義的每一個節提供內容。如果向當前的布局中添加一個新節時,會使得引用該布局的每一個視圖都不能正常運行。
但是,RenderSection方法有一個重載版本,允許指定不需要的節。可以給required參數傳遞一個false值來標記Footer節是可選的。
<footer>@RenderSection("Footer", required: false)</footer>
有時也在視圖中定義一些默認內容
1 <footer>
2 @if (IsSectionDefined("Footer")) 3 { 4 RenderSection("Footer"); 5 } 6 else 7 { 8 <span>this is the default footer</span>
9 } 10 </footer>
5.ViewStart
在創建一個默認的ASP.NET MVC項目后,會在Views目錄下自動添加一個_ViewStart.cshtml文件,它指定了一個默認布局
1 @{ 2 Layout = "~/Views/Shared/_Layout.cshtml" 3 }
如果多個視圖都使用都使用同一個布局,就會產生冗余。_ViewStart.cshtml中的代碼先於任何視圖運行,所以一個視圖可以重寫Layout屬性的默認值,從而重新選擇了一個不同的布局。如果一組視圖擁有共同的設置,那么_ViewStart.cshtml中的代碼可以用來對共同的視圖進行統一配置。如果有視圖需要覆蓋統一的設置,只需要修改對應的屬性值即可。
6.指定部分視圖
除了返回視圖之外,操作方法也可以通過PartialView方法以PartialViewResult的形式返回部分視圖:
1 public class HomeController : HomeController 2 { 3 public ActionResult Message() 4 { 5 ViewBag.Message = "this is a partial view."; 6 return PartialView(); 7 } 8 }
這種情形下,渲染的是視圖Message.cshtml,但是如果布局是由_ViewStart.cshtml頁面指定(而不是直接在視圖中)的,將無法渲染布局。
除了不能指定布局之外,部分視圖看起來和正常視圖沒有分別:
<h2>@ViewBag.Message</h2>
在使用Ajax技術進行更新時,部分視圖是很有用的。示例使用jQuery將一個部分視圖的內容加載到一個使用了Ajax調用的當前視圖中:
1 <div id="result"></div>
2 @section scripts{ 3 <script type="text/javascript">
4 $(function(){ 5 $('#result').load('/home/message'); 6 }); 7 </script>
8 }
代碼使用jQuery的load方法向Message操作方法發出一個Ajax請求,而后使用請求的結果更新id屬性值為result的DIV元素。