使用母版页和部分重用 UI

Microsoft

下载 PDF

这是免费 “NerdDinner”应用程序教程 的步骤 7,介绍如何使用 ASP.NET MVC 1 生成小型但完整的 Web 应用程序。

步骤 7 查看在视图模板中使用部分视图模板和母版页来应用“DRY 原则”以消除代码重复的方法。

如果你使用的是 ASP.NET MVC 3,我们建议你遵循入门与 MVC 3MVC 音乐应用商店教程。

NerdDinner 步骤 7:分部和母版页

MVC 所 ASP.NET 的设计理念之一是“不要重复自己”原则 (通常称为“DRY”) 。 DRY 设计有助于消除代码和逻辑的重复,最终使应用程序的生成速度更快且更易于维护。

我们已经看到 DRY 原则应用于多个 NerdDinner 方案。 一些示例:我们的验证逻辑在模型层中实现,这使它可以在控制器中的编辑和创建方案中强制实施;我们正在编辑、详细信息和删除操作方法中重新使用“NotFound”视图模板;我们将约定命名模式与视图模板配合使用,这样就无需在调用 View () 帮助程序方法时显式指定名称;我们正在将 DinnerFormViewModel 类重新用于“编辑”和“创建”操作方案。

现在,让我们看看在视图模板中应用“DRY 原则”以消除代码重复的方法。

重新访问我们的编辑和创建视图模板

目前,我们使用两个不同的视图模板(“Edit.aspx”和“Create.aspx”)来显示 Dinner 窗体 UI。 快速直观地比较它们会突出显示它们的相似程度。 创建窗体如下所示:

“我的 M V C 应用程序”页的屏幕截图。将显示“主持晚餐”窗体。

“编辑”窗体如下所示:

显示“我的 M V C 应用程序”分页的屏幕截图。将显示“编辑”窗体。

没有太大的区别? 除了标题和标题文本外,窗体布局和输入控件是相同的。

如果打开“Edit.aspx”和“Create.aspx”视图模板,我们会发现它们包含相同的表单布局和输入控件代码。 这种重复意味着我们最终不得不做出两次更改,每当我们引入或更改新的晚餐属性 - 这是不好的。

使用分部视图模板

ASP.NET MVC 支持定义可用于封装页面子部分的视图呈现逻辑的“部分视图”模板的功能。 “分部”提供了一种有用的方法,用于定义视图呈现逻辑一次,然后在应用程序中的多个位置重复使用它。

为了帮助“干化”Edit.aspx 和 Create.aspx 视图模板的复制,我们可以创建一个名为“DinnerForm.ascx”的部分视图模板,该模板封装两者共有的表单布局和输入元素。 为此,请右键单击 /Views/Dinners 目录,然后选择“添加视图>”菜单命令:

解决方案资源管理器导航树的屏幕截图。突出显示了晚餐。已选择“添加”和“查看”。突出显示了视图。

这将显示“添加视图”对话框。 我们将要创建的新视图命名为“DinnerForm”,选中对话框中的“创建分部视图”复选框,并指示我们将向其传递 DinnerFormViewModel 类:

“添加视图”对话框的屏幕截图。已选择并突出显示“创建分部视图”。

单击“添加”按钮时,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>
编辑.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\ 文件夹中:

Nerd Dinner 导航树的屏幕截图。突出显示并选择了“网站母版”。

默认的 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 模板。 这是通过“MasterPageFile”属性指示的,当我们使用“添加视图”对话框创建视图时,该属性默认添加到顶部 <% @ Page %> 指令中:

<%@ Page Inherits="System.Web.Mvc.ViewPage<NerdDinner.Controllers.DinnerViewModel>" MasterPageFile="~/Views/Shared/Site.Master" %>

这意味着我们可以更改 Site.master 内容,并在呈现任何视图模板时自动应用和使用更改。

让我们更新 Site.master 的标头部分,以便应用程序的标头为“NerdDinner”而不是“我的 MVC 应用程序”。 我们还更新导航菜单,使第一个选项卡是“查找晚餐” (由 HomeController 的 Index () 操作方法) 处理,然后添加一个名为“主持晚餐”的新选项卡 (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 文件并刷新浏览器时,会看到头更改显示在应用程序中的所有视图中。 例如:

Nerd Dinner 即将推出的晚餐列表页的屏幕截图。

使用 /Dinners/Edit/[id] URL:

显示“Nerd Dinner 编辑表单”页的屏幕截图。

下一步

分部和母版页提供了非常灵活的选项,使你能够清晰地组织视图。 你会发现,它们有助于避免重复视图内容/代码,并使视图模板更易于阅读和维护。

现在,让我们重新审视我们之前构建的列表方案,并启用可缩放的分页支持。