第 3 部分:视图和 ViewModel

作者 :Jon Galloway

MVC 音乐应用商店是一个教程应用程序,介绍并逐步说明如何使用 ASP.NET MVC 和 Visual Studio 进行 Web 开发。

MVC 音乐商店是一个轻量级的示例商店实现,用于在线销售音乐专辑,并实现基本的网站管理、用户登录和购物车功能。

本系列教程详细介绍了生成 ASP.NET MVC 音乐应用商店示例应用程序所执行的所有步骤。 第 3 部分介绍视图和 ViewModel。

到目前为止,我们刚刚从控制器操作返回字符串。 这是了解控制器工作原理的好方法,但这不是你希望构建真实 Web 应用程序的方式。 我们希望有一种更好的方法将 HTML 生成回访问我们网站的浏览器 ,这样我们就可以使用模板文件更轻松地自定义发送回的 HTML 内容。 这正是视图的功能。

添加视图模板

若要使用视图模板,我们将更改 HomeController Index 方法以返回 ActionResult,并使其返回 View () ,如下所示:

public class HomeController : Controller
{
    //
    // GET: /Home/
    public ActionResult Index()
    {
        return View();
    }
}

上述更改表明,我们不想返回字符串,而是要使用“View”来生成返回结果。

现在,我们将向项目添加相应的视图模板。 为此,我们将文本光标置于 Index 操作方法中,然后右键单击并选择“添加视图”。 此时会显示“添加视图”对话框:

显示“添加视图”选项的菜单的屏幕截图。“添加视图”对话框的屏幕截图,其中包含用于选择和添加视图的菜单选项。

“添加视图”对话框使我们能够快速轻松地生成视图模板文件。 默认情况下,“添加视图”对话框预先填充要创建的视图模板的名称,使其与将使用它的操作方法匹配。 由于我们在 HomeController 的 Index () 操作方法中使用了“添加视图”上下文菜单,因此上面的“添加视图”对话框默认预填充了“索引”作为视图名称。 我们不需要更改此对话框中的任何选项,因此请单击“添加”按钮。

单击“添加”按钮时,Visual Web Developer 将在 \Views\Home 目录中为我们创建新的 Index.cshtml 视图模板,如果文件夹尚不存在,则创建该文件夹。

解决方案资源管理器下拉菜单的屏幕截图,其中显示了 M V C 音乐商店中的不同文件。

“Index.cshtml”文件的名称和文件夹位置非常重要,并遵循默认 ASP.NET MVC 命名约定。 目录名称 \Views\Home 与名为 HomeController 的控制器匹配。 视图模板名称 Index 与将显示视图的控制器操作方法匹配。

ASP.NET MVC 允许我们避免在使用此命名约定返回视图时显式指定视图模板的名称或位置。 默认情况下,当我们在 HomeController 中编写如下所示的代码时,它将呈现 \Views\Home\Index.cshtml 视图模板:

public class HomeController : Controller
{
    //
    // GET: /Home/
    public ActionResult Index()
    {
        return View();
    }
}

在单击“添加视图”对话框中的“添加”按钮后,Visual Web Developer 创建并打开了“Index.cshtml”视图模板。 Index.cshtml 的内容如下所示。

@{
    ViewBag.Title = "Index";
}
<h2>Index</h2>

此视图使用 Razor 语法,该语法比 ASP.NET Web Forms 和以前版本的 ASP.NET MVC 中使用的Web Forms视图引擎更简洁。 Web Forms视图引擎在 ASP.NET MVC 3 中仍然可用,但许多开发人员发现 Razor 视图引擎非常适合 MVC 开发 ASP.NET。

前三行使用 ViewBag.Title 设置页面标题。 我们稍后将更详细地了解其工作原理,但首先让我们更新文本标题文本并查看页面。 更新 <h2> 标记以显示“这是主页”,如下所示。

@{
    ViewBag.Title = "Index";
}
<h2>This is the Home Page</h2>

运行应用程序会显示新文本在主页上可见。

音乐商店浏览器主页的屏幕截图,其中显示了徽标图像下的文本“这是主页”。

对常见网站元素使用布局

大多数网站都有在许多页面之间共享的内容:导航、页脚、徽标图像、样式表引用等。使用 Razor 视图引擎,可以使用名为 _Layout.cshtml 的页面(已在 /Views/Shared 文件夹中自动为我们创建)管理此功能。

“音乐应用商店”下拉菜单的屏幕截图,其中显示了位于视图文件夹内的共享文件夹的文件路径。

双击此文件夹以查看内容,如下所示。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")"
rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")"     
            type="text/javascript"></script> 
    <script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")"
            type="text/javascript"></script>
</head>
<body>
    @RenderBody()
</body>
</html>

单个视图中的内容将由 命令显示 @RenderBody() ,而我们想要在 外部显示的任何常见内容都可以添加到 _Layout.cshtml 标记中。 我们希望 MVC 音乐商店有一个公共标头,其中包含指向网站所有页面上的主页和应用商店区域的链接,因此我们会将该标题添加到该 @RenderBody() 语句正上方的模板中。

<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")"
rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")"
type="text/javascript"></script>
</head>
<body>
    <div id="header">
        <h1>
            ASP.NET MVC MUSIC STORE</h1>
        <ul id="navlist">
            <li class="first"><a href="/"
id="current">Home</a></li>
            <li><a
href="/Store/">Store</a></li>
        </ul>
    </div>
    @RenderBody()
</body>
</html>

更新 StyleSheet

空项目模板包含一个非常简化的 CSS 文件,该文件仅包含用于显示验证消息的样式。 我们的设计器提供了一些额外的 CSS 和图像来定义网站的外观,因此我们现在将添加这些内容。

更新后的 CSS 文件和图像包含在 MVC-Music-Store 提供的 MvcMusicStore-Assets.zip 的内容目录中。 我们将在 Windows 资源管理器中选择这两者,并将其拖放到 Visual Web Developer 中解决方案的内容文件夹中,如下所示:

内容目录和音乐应用商店下拉菜单的并排屏幕截图,其中显示了内容文件夹中图像文件夹的文件路径。

系统将要求确认是否要覆盖现有的 Site.css 文件。 单击“是”。

显示的警告弹出框的屏幕截图,其中请求通过询问是否要替换现有文件来确认覆盖操作。

应用程序的 Content 文件夹现在如下所示:

音乐商店的屏幕截图,下拉菜单突出显示了内容文件夹,其中显示了新的图像文件夹,其下方有图像列表。

现在,让我们运行应用程序,看看更改在主页上的外观。

音乐应用商店浏览器窗口主页的屏幕截图,其中选择了图像,下面还有“这是主页”字样。

  • 让我们看看更改的内容:HomeController 的 Index 操作方法找到并显示 \Views\Home\Index.cshtmlView 模板,尽管我们的代码名为“return View () ”,因为我们的 View 模板遵循标准命名约定。
  • 主页显示 \Views\Home\Index.cshtml 视图模板中定义的简单欢迎消息。
  • 主页使用 _Layout.cshtml 模板,因此欢迎消息包含在标准网站 HTML 布局中。

使用模型将信息传递给视图

仅显示硬编码 HTML 的视图模板不会成为一个非常有趣的网站。 为了创建动态网站,我们需要将信息从控制器操作传递到视图模板。

在“模型-视图-控制器”模式中,术语“模型”是指表示应用程序中数据的对象。 通常,模型对象对应于数据库中的表,但它们不必对应。

返回 ActionResult 的控制器操作方法可以将模型对象传递给视图。 这允许控制器完全打包生成响应所需的所有信息,然后将此信息传递给视图模板,以用于生成适当的 HTML 响应。 这是最容易理解的,因为它的操作,所以让我们开始吧。

首先,我们将创建一些 Model 类来表示商店中的流派和相册。 让我们首先创建一个流派类。 右键单击项目中的“Models”文件夹,选择“添加类”选项,并将文件命名为“Genre.cs”。

三个并排菜单框的屏幕截图,其中显示了从右到左到类选择的文件路径方向

“添加新项”菜单选项的屏幕截图,其中显示了选择模板、排序样式和类型的三个菜单;然后是底部的名称字段栏。

然后,将公共字符串 Name 属性添加到已创建的类:

public class Genre
{
    public string Name { get; set; }
}

注意:如果你想知道,{ get; set; } 表示法正在使用 C# 的自动实现属性功能。 这为我们提供了属性的好处,而无需声明支持字段。

接下来,按照相同的步骤 (创建名为 Album.cs 的 Album 类) ,该类具有 Title 和 Genre 属性:

public class Album
{
    public string Title { get; set; }
    public Genre Genre { get; set; }
}

现在,我们可以修改 StoreController 以使用显示模型中的动态信息的视图。 如果 (出于演示目的)我们现在根据请求 ID 命名了我们的相册,我们可以显示该信息,如下面的视图所示。

浏览器主页的屏幕截图,其中显示了图像徽标、当前专辑名称以及右上角的可单击主页和应用商店按钮。

我们将首先更改“应用商店详细信息”操作,使其显示单个相册的信息。 将“using”语句添加到 StoreControllers 类的顶部以包含 MvcMusicStore.Models 命名空间,因此我们无需在每次要使用专辑类时都键入 MvcMusicStore.Models.Album。 该类的“usings”部分现在应如下所示。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcMusicStore.Models;

接下来,我们将更新 Details 控制器操作,使其返回 ActionResult 而不是字符串,就像使用 HomeController 的 Index 方法一样。

public ActionResult Details(int id)

现在,我们可以修改逻辑以将 Album 对象返回到视图。 在本教程的后面部分,我们将从数据库中检索数据,但现在我们将使用“虚拟数据”开始。

public ActionResult Details(int id)
 {
    var album = new Album { Title = "Album " + id };
    return View(album);
 }

注意:如果你不熟悉 C#,你可能会认为使用 var 意味着我们的专辑变量是后期绑定的。 这不正确 - C# 编译器使用基于我们分配给变量的内容的类型推理来确定专辑的类型,并将本地专辑变量编译为 Album 类型,因此我们获得了编译时检查和 Visual Studio 代码编辑器支持。

现在,让我们创建一个使用相册生成 HTML 响应的视图模板。 在执行此操作之前,我们需要生成项目,以便“添加视图”对话框知道新创建的 Album 类。 可以通过选择“调试”⇨“生成 MvcMusicStore”菜单项来生成项目, (以获取额外额度,可以使用 Ctrl-Shift-B 快捷方式) 生成项目。

音乐商店文档编辑器的屏幕截图,其中在下拉菜单中选择了“生成”选项卡,突出显示了“生成 M V C 音乐存储”选项。

设置支持类后,即可生成视图模板。 在“详细信息”方法中右键单击,然后选择“添加视图...”。从上下文菜单。

视图模板菜单的屏幕截图,显示在代码片段上,并突出显示了“添加视图”选项。

我们将像以前使用 HomeController 一样创建新的视图模板。 由于我们从 StoreController 创建它,因此默认情况下,它将在 \Views\Store\Index.cshtml 文件中生成。

与之前不同,我们将检查“创建强类型”视图复选框。 然后,我们将在“查看数据类”下拉列表中选择“Album”类。 这将导致“添加视图”对话框创建一个视图模板,该模板需要将 Album 对象传递给它以使用。

“添加视图”菜单窗口的屏幕截图,其中显示了“创建强类型视图”可单击复选框和相册的模型类。

单击“添加”按钮时,将创建包含以下代码的 \Views\Store\Details.cshtml 视图模板。

@model MvcMusicStore.Models.Album
@{
    ViewBag.Title = "Details";
}
<h2>Details</h2>

请注意第一行,它指示此视图已强类型化到我们的 Album 类。 Razor 视图引擎了解它已传递 Album 对象,因此我们可以轻松访问模型属性,甚至可以在 Visual Web 开发人员编辑器中利用 IntelliSense。

<更新 h2> 标记,以便通过修改该行来显示 Album 的 Title 属性,如下所示。

<h2>Album: @Model.Title</h2>

请注意,当你输入关键字 (keyword) 之后@Model的句点时,IntelliSense 会触发,其中显示了 Album 类支持的属性和方法。

现在,让我们重新运行项目并访问 /Store/Details/5 URL。 我们将看到相册的详细信息,如下所示。

主页浏览器窗口的屏幕截图,左上角是图像徽标,下方是相册名称。

现在,我们将对应用商店浏览操作方法进行类似的更新。 更新方法,使其返回 ActionResult,并修改方法逻辑,以便创建一个新的 Genre 对象并将其返回到视图。

public ActionResult Browse(string genre)
 {
    var genreModel = new Genre { Name = genre };
    return View(genreModel);
 }

右键单击 Browse 方法,然后选择“添加视图...”然后,在上下文菜单中添加强类型视图,将强类型添加到 Genre 类。

上下文菜单的屏幕截图,其中显示了正在选中的“创建强类型视图”,当前模型类以红色装箱。

<更新 /Views/Store/Browse.cshtml) 中视图代码 (的 h2> 元素以显示流派信息。

@model MvcMusicStore.Models.Genre
@{
    ViewBag.Title = "Browse";
}
<h2>Browsing Genre: @Model.Name</h2>

现在,让我们重新运行项目并浏览到 /Store/Browse?Genre=Disco URL。 我们将看到如下所示的“浏览”页面。

浏览器主页窗口的屏幕截图,其中显示了徽标图像下的“浏览流派:迪斯科舞厅”消息。

最后,让我们对 Store Index 操作方法进行稍微复杂的更新,并查看以显示商店中所有流派的列表。 为此,我们将使用流派列表作为模型对象,而不仅仅是单个流派。

public ActionResult Index()
{
    var genres = new List<Genre>
    {
        new Genre { Name = "Disco"},
        new Genre { Name = "Jazz"},
        new Genre { Name = "Rock"}
    };
    return View(genres);
 }

右键单击“应用商店索引”操作方法,选择“添加视图”(如前所述),选择“流派”作为 Model 类,然后按“添加”按钮。

“添加视图”窗口菜单的屏幕截图,其中显示了红色框中的模型类选择,然后显示下面的“添加”按钮。

首先,我们将更改 @model 声明,以指示视图需要多个 Genre 对象,而不仅仅是一个。 更改 /Store/Index.cshtml 的第一行,如下所示:

@model IEnumerable<MvcMusicStore.Models.Genre>

这会告知 Razor 视图引擎,它将处理可保存多个 Genre 对象的模型对象。 我们使用 IEnumerable<流派> 而不是 List<Genre> ,因为它更通用,允许我们稍后将模型类型更改为支持 IEnumerable 接口的任何对象类型。

接下来,我们将循环访问模型中的 Genre 对象,如下面完成的视图代码所示。

@model IEnumerable<MvcMusicStore.Models.Genre>
@{
    ViewBag.Title = "Store";
}
<h3>Browse Genres</h3>
<p>
    Select from @Model.Count()
genres:</p>
<ul>
    @foreach (var genre in Model)
    {
        <li>@genre.Name</li>
    }
</ul>

请注意,输入此代码时,我们有完整的 IntelliSense 支持,以便在键入“@Model”时看到类型为 Genre 的 IEnumerable 支持的所有方法和属性。

H T M L 代码片段的屏幕截图,上方有一个菜单栏,选择“count <>”命令。

在我们的“foreach”循环中,Visual Web 开发人员知道每个项的类型为“Genre”,因此我们看到了每种“流派”类型的 IntelliSense。

“foreach 循环”代码的屏幕截图,其中显示了一个下拉菜单窗口,并选择了“name”选项,旁边弹出了“string Genre dot name”。

接下来,基架功能检查了 Genre 对象,并确定每个对象都具有 Name 属性,因此它会循环访问并写出它们。它还生成指向每个单独项的“编辑”、“详细信息”和“删除”链接。 稍后我们会在商店经理中利用这一点,但现在我们希望有一个简单的列表。

运行应用程序并浏览到 /Store 时,可以看到流派的计数和列表都已显示。

浏览器窗口的屏幕截图,其中显示了“浏览流派”标题,然后是一条消息,要求选择流派,后跟其下方的标题。

列出“流派”的 /Store URL 当前仅以纯文本形式列出流派名称。 让我们对此进行更改,而不是纯文本,而是将流派名称链接到相应的 /Store/Browse URL,以便单击音乐流派(如“Disco”)将导航到 /Store/Browse?genre=Disco URL。 我们可以更新 \Views\Store\Index.cshtml 视图模板,以使用如下代码输出这些链接 (不键入此内容 - 我们将) 对其进行改进

<ul>
    @foreach (var genre in Model)
    {
        <li><a href="/Store/Browse?genre=@genre.Name">@genre.Name</a></li>
    }
</ul>

这可行,但以后可能会导致问题,因为它依赖于硬编码的字符串。 例如,如果想要重命名控制器,则需要在代码中搜索需要更新的链接。

我们可以使用的替代方法是利用 HTML 帮助程序方法。 ASP.NET MVC 包括 HTML 帮助程序方法,这些方法可从我们的视图模板代码中获取,用于执行各种常见任务,如下所示。 Html.ActionLink () 帮助程序方法是一种特别有用的方法,它可以轻松地生成 HTML <链接> 并处理烦人的详细信息,例如确保 URL 路径已正确编码 URL。

Html.ActionLink () 具有多个不同的重载,允许指定链接所需的尽可能多的信息。 最简单的情况是,在客户端上单击超链接时,只需提供要转到的链接文本和 Action 方法。 例如,我们可以使用以下调用通过链接文本“转到 Microsoft Store 索引”链接到“应用商店详细信息”页上的“/Store/”Index () 方法:

@Html.ActionLink("Go
to the Store Index", "Index")

注意:在这种情况下,我们不需要指定控制器名称,因为我们只是链接到呈现当前视图的同一控制器中的另一个操作。

不过,指向“浏览”页面的链接需要传递参数,因此我们将使用 Html.ActionLink 方法的另一个重载,该方法采用三个参数:

    1. 链接文本,其中将显示流派名称
    1. 浏览) (控制器操作名称
    1. 路由参数值,指定 (“流派”) 的名称和 (“流派名称”的值)

综合起来,下面介绍如何将这些链接写入“应用商店索引”视图:

<ul>
    @foreach (var genre in Model)
    {
        <li>@Html.ActionLink(genre.Name,
"Browse", new { genre = genre.Name })</li>
    }
</ul>

现在,当我们再次运行项目并访问 /Store/ URL 时,我们将看到流派列表。 每个流派都是一个超链接 - 单击时,它将转到 /Store/Browse?genre=[genre] URL。

浏览器窗口的屏幕截图,其中显示了“浏览流派”标题,其中消息“从 3 种流派中选择”,后跟三个项目符号的流派选择。

流派列表的 HTML 如下所示:

<ul>
    <li><a href="/Store/Browse?genre=Disco">Disco</a>
</li>
    <li><a href="/Store/Browse?genre=Jazz">Jazz</a>
</li>
    <li><a href="/Store/Browse?genre=Rock">Rock</a>
</li>
</ul>