重複使用使用主版頁面和部分頁面的 UI
由 Microsoft 提供
這是免費的 "NerdDinner" 應用程式教學課程的第 7 個步驟,詳細介紹了如何使用 ASP.NET MVC 1 建置一個小型但完整的 Web 應用程式。
步驟 7 查看我們可以在檢視範本中套用「DRY 原則」的方式,以使用部分檢視範本和主版頁面來消除程式碼重複。
如果使用 ASP.NET MVC 3,建議遵循 MVC 3 使用者入門或 MVC Music 市集教學課程。
NerdDinner 步驟 7:部分和主版頁面
ASP.NET MVC 其中一個設計理念是「不重複」原則 (通常稱為 "DRY")。 DRY 設計有助於消除程式碼和邏輯的重複,最終可更快速地建置應用程式並更容易維護。
DRY 原則已經套用至數個 NerdDinner 案例。 我們在模型層內實作驗證邏輯,因此可以在控制器中的編輯和建立案例中強制執行;我們會在 [編輯]、[詳細資料] 和 [刪除] 動作方法之間重複使用 “NotFound” 檢視範本;我們在檢視範本中使用慣例命名模式,這樣就無需在呼叫 View() 輔助方法時明確指定名稱;我們也在 [編輯] 和 [建立] 動作案例重複使用 DinnerFormViewModel 類別。
現在讓我們看看如何在檢視範本中套用「DRY 準則」同時消除程式碼重複。
重新造訪我們的 [編輯] 和 [建立] 檢視範本
我們目前使用 “Edit.aspx” 和 “Create.aspx” 這兩個不同的檢視範本,以顯示我們的 Dinner 表單 UI。 對它們進行快速視覺效果比較可以醒目提示它們的相似之處。 以下是建立表單的外觀:
以下是 [編輯] 表單的外觀:
沒有太大差別嗎? 除了標題和標頭文字以外,表單版面配置和輸入控制項都相同。
如果我們開啟 "Edit.aspx" 和 "Create.aspx" 檢視範本,就會發現它們包含相同的表單配置和輸入控制項程式碼。 這種重複意味著我們每次導入或變更新的 Dinner 屬性時,最終都必須進行兩次變更 - 這不好。
使用部分檢視範本
ASP.NET MVC 支援定義 [部分檢視] 範本的功能,可用來封裝頁面子部分的檢視轉譯邏輯。 「部分」提供一個有用的方法來定義檢視轉譯邏輯一次,然後在應用程式的多個位置重複使用它。
為了協助 "DRY-up" 我們的 Edit.aspx 和 Create.aspx 檢視範本重複,我們可以建立名為 "DinnerForm.ascx" 的部分檢視範本,封裝這兩者通用的表單配置和輸入元素。 我們將會以滑鼠右鍵按一下 /Views/Dinners 目錄,然後選擇 [新增 -> 檢視] 選單命令來執行此動作:
這將顯示 [新增檢視] 對話方塊。 我們將要建立的新檢視命名為 “DinnerForm”,在對話方塊中選取 [建立部分檢視] 核取方塊,並指示我們將向其傳遞一個 DiningFormViewModel 類別:
當我們按一下 [新增] 按鈕時,Visual Studio 會在 “\Views\Dinners” 目錄中為我們建立新的 “DinnerForm.ascx” 檢視範本。
然後,我們可以將重複的表單配置/輸入控制程式碼從 Edit.aspx/Create.aspx 檢視範本複製/張貼到新的 “DinnerForm.ascx” 部分檢視範本中:
<%= Html.ValidationSummary("Please correct the errors and try again.") %>
<% using (Html.BeginForm()) { %>
<fieldset>
<p>
<label for="Title">Dinner Title:</label>
<%= Html.TextBox("Title", Model.Dinner.Title) %>
<%=Html.ValidationMessage("Title", "*") %>
</p>
<p>
<label for="EventDate">Event Date:</label>
<%= Html.TextBox("EventDate", Model.Dinner.EventDate) %>
<%= Html.ValidationMessage("EventDate", "*") %>
</p>
<p>
<label for="Description">Description:</label>
<%= Html.TextArea("Description", Model.Dinner.Description) %>
<%= Html.ValidationMessage("Description", "*") %>
</p>
<p>
<label for="Address">Address:</label>
<%= Html.TextBox("Address", Model.Dinner.Address) %>
<%= Html.ValidationMessage("Address", "*") %>
</p>
<p>
<label for="Country">Country:</label>
<%= Html.DropDownList("Country", Model.Countries) %>
<%= Html.ValidationMessage("Country", "*") %>
</p>
<p>
<label for="ContactPhone">Contact Phone #:</label>
<%= Html.TextBox("ContactPhone", Model.Dinner.ContactPhone) %>
<%= Html.ValidationMessage("ContactPhone", "*") %>
</p>
<p>
<input type="submit" value="Save"/>
</p>
</fieldset>
<% } %>
然後,我們可以更新 [編輯] 和 [建立] 檢視範本,以呼叫 DinnerForm 部分範本,並消除表單重複。 我們可以在檢視範本中呼叫 Html.RenderPartial (“DinnerForm”) 來執行此動作:
Create.aspx
<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
Host a Dinner
</asp:Content>
<asp:Content ID="Create" ContentPlaceHolderID="MainContent" runat="server">
<h2>Host a Dinner</h2>
<% Html.RenderPartial("DinnerForm"); %>
</asp:Content>
Edit.aspx
<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
Edit: <%=Html.Encode(Model.Dinner.Title) %>
</asp:Content>
<asp:Content ID="Edit" ContentPlaceHolderID="MainContent" runat="server">
<h2>Edit Dinner</h2>
<% Html.RenderPartial("DinnerForm"); %>
</asp:Content>
呼叫 Html.RenderPartial 時,您可以明確限定您想要的部分範本路徑 (例如:~Views/Dinners/DinnerForm.ascx")。 不過,在上述程式碼中,我們會利用 ASP.NET MVC 中的慣例型命名模式,並只是將 "DinnerForm" 指定為要轉譯的部分名稱。 當我們這樣做時,ASP.NET MVC 會先查看以慣例為基礎的檢視目錄 (針對 DinnersController,這會是 /Views/Dinners)。 如果找不到部分範本,則會在 /Views/Shared 目錄中尋找它。
使用部分檢視的名稱呼叫 Html.RenderPartial() 時,ASP.NET MVC 會傳遞至呼叫檢視範本所使用的相同 Model 和 ViewData 字典物件部分檢視。 或者,Html.RenderPartial() 的多載版本可讓您傳遞替代的 Model 物件和/或 ViewData 字典,以供部分檢視使用。 這適用於您只想要傳遞完整 Model/ViewModel 子集的案例。
側邊主題:為什麼是 <% %> 而不是 <%= %>? |
---|
您可能在上面的程式碼中注意到的一個微妙的事情是,我們在呼叫 Html.RenderPartial() 時使用 <% %> 區塊而不是 <%= %> 區塊。 ASP.NET 中的 <%= %> 區塊表示開發人員想要呈現指定的值 (例如:<%= "Hello" %> 將呈現 "Hello")。 相反,<% %> 區塊表示開發人員想要執行程式碼,並且其中的任何轉譯輸出都必須明確完成 (例如:< % Response.Write("Hello") %>。 我們之所以使用 <% %> 區塊搭配上述 Html.RenderPartial 程式碼,是因為 Html.RenderPartial() 方法不會傳回字串,而是將內容直接輸出至呼叫檢視範本的輸出串流。 基於效能效率的原因,它會避免建立一個 (可能非常大的) 暫存字串物件的需求。 這樣可減少記憶體使用量,並改善整體應用程式輸送量。 使用 Html.RenderPartial() 時的一個常見錯誤,就是當呼叫位於 <%%> 區塊內時,忘記在呼叫結尾新增分號。 例如,此程式碼會造成編譯程式錯誤:<% Html.RenderPartial("DinnerForm") %> 您需要撰寫: <% Html.RenderPartial("DinnerForm"); %> 這是因為 <% %> 區塊是獨立的程式碼陳述式,而使用 C# 程式碼陳述式時必須以分號終止。 |
使用部分檢視範本來釐清程式碼
我們建立了 "DinnerForm" 部分檢視範本,以避免在多個位置複製檢視轉譯邏輯。 這是建立部分檢視範本的最常見原因。
有時候即使只在單一位置呼叫部分檢視,建立部分檢視還是有道理的。 當非常複雜的檢視範本的檢視轉譯邏輯被提取並劃分為一個或多個命名良好的部分範本時,通常會變得更容易閱讀。
例如,請考慮專案中 Site.master 檔案的下列程式碼片段 (稍後介紹)。 程式碼相對簡單易讀,部分原因是在螢幕右上角顯示登入/登出連結的邏輯封裝在 “LogOnUserControl” 部分:
<div id="header">
<div id="title">
<h1>My MVC Application</h1>
</div>
<div id="logindisplay">
<% Html.RenderPartial("LogOnUserControl"); %>
</div>
<div id="menucontainer">
<ul id="menu">
<li><%=Html.ActionLink("Home", "Index", "Home")%></li>
<li><%=Html.ActionLink("About", "About", "Home")%></li>
</ul>
</div>
</div>
每當您發現自己在嘗試理解檢視模板中的 html/程式碼標記時感到困惑,請考慮如果將其中一些標記提取並重構為命名良好的部分檢視,是否會更清晰。
主版頁面
除了支援部分檢視之外,ASP.NET MVC 也支援建立可用來定義網站通用版面配置和頂層 HTML 的 [主版頁面] 範本。 然後,可以將內容預留位置控制項新增至主版頁面,以識別可以被檢視覆寫或「填入」的可替換區域。 這提供了一種非常有效 (且 DRY) 的方法來在應用程式中套用常見的版面配置。
預設情況下,新的 ASP.NET MVC 專案會自動新增一個主版頁面範本。 此主版頁面名為 “Site.master”,並位於 \Views\Shared\ 資料夾中:
預設的 Site.master 檔案如下所示。 它會定義網站的外部 HTML,以及頂端導覽的功能表。 它包含兩個可替換的內容預留位置控制項 – 一個用於標題,另一個用於應替換頁面主要內容的位置:
<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage"%>
<!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 runat="server">
<title>
<asp:ContentPlaceHolder ID="TitleContent" runat="server" />
</title>
<link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="page">
<div id="header">
<div id="title">
<h1>My MVC Application</h1>
</div>
<div id="logindisplay">
<% Html.RenderPartial("LogOnUserControl"); %>
</div>
<div id="menucontainer">
<ul id="menu">
<li><%=Html.ActionLink("Home", "Index", "Home")%></li>
<li><%=Html.ActionLink("About", "About", "Home")%></li>
</ul>
</div>
</div>
<div id="main">
<asp:ContentPlaceHolder ID="MainContent" runat="server" />
</div>
</div>
</body>
</html>
我們為 NerdDinner 應用程式所建立的所有檢視範本 (“List”、“Details”、“Edit”、“Create”、“NotFound” 等) 都是以此 Site.master 範本為基礎。 當我們使用 [新增檢視] 對話方塊建立檢視時,預設會新增至頂端 <% @ Page %> 指示詞的 “MasterPageFile” 屬性來表示:
<%@ Page Inherits="System.Web.Mvc.ViewPage<NerdDinner.Controllers.DinnerViewModel>" MasterPageFile="~/Views/Shared/Site.Master" %>
這表示我們可以變更 Site.master 內容,並在轉譯任何檢視範本時自動套用及使用變更。
讓我們更新 Site.master 的標頭區段,讓應用程式的標頭是 “NerdDinner”,而不是 “My MVC 應用程式”。 讓我們更新我們的導覽功能表,讓第一個索引標籤是 "Find a Dinner" (由 HomeController 的 Index() 動作方法處理),讓我們新增一個名為 “Host a Dinner” 的新索引標籤 (由 DinnersController 的 Create() 動作方法處理):
<div id="header">
<div id="title">
<h1>NerdDinner</h1>
</div>
<div id="logindisplay">
<% Html.RenderPartial("LoginStatus"); %>
</div>
<div id="menucontainer">
<ul id="menu">
<li><%=Html.ActionLink("Find Dinner", "Index", "Home")%></li>
<li><%=Html.ActionLink("Host Dinner", "Create", "Dinners")%></li>
<li><%=Html.ActionLink("About", "About", "Home")%></li>
</ul>
</div>
</div>
當我們儲存 Site.master 檔案並重新整理瀏覽器時,我們會看到標頭變更顯示在應用程式內的所有檢視中。 例如:
使用 /Dinners/Edit/[id] URL:
後續步驟
部分和主版頁面提供非常靈活的選項,可讓您清楚組織檢視。 您會發現它們可協助您避免複製檢視內容/程式碼,並讓您的檢視範本更容易閱讀和維護。
現在讓我們重新瀏覽我們稍早建置的清單案例,並啟用可調整的分頁支援。