ASP.NET MVC 音乐商店 - 3. 视图与模型
MusicStore 项目的完整配套视频!
上一篇中有同学提到为什么不使用视图,而使用字符串,这一篇我们就开始使用视图来处理。
我们已经可以从控制器的 Action 中返回一个字符串,这可以帮助我们更好地理解 Controller 是如何工作的。但是对于创建一个 Web 程序来说还是不够的。下面我们使用更好的方法来生成 HTML,主要是通过模板来生成需要的 HTML,这就是视图所要做的。
增加视图模板
为了使用视图模板,我们需要将HomeController 中的 Index 这个 Action 的返回类型修改为 ActionResult,然后,让它像下面一样返回一个视图。
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
}
上面的修改表示我们将使用视图来替换掉原来的字符串,以便生成返回的结果。
现在为我们的项目增加一个视图,为达到这个目的,我们将光标移到 Index 方法内,然后,点击鼠标的右键,在右键菜单中选择“添加视图(D)…”,这样将会弹出增加视图的对话框。
添加视图的对话框允许我们快速,简单地创建一个视图模板,默认情况下,视图的名称使用当前 Action 的名字。因为我们是在 Index 这个 Aciton 上添加模板,所以添加视图对话框中,视图的名字就是 Index,我们不需要修改这个名字,点击添加。
在点击添加之后,Visual Studio 将会创建一个名为 Index.cshtml的视图模板,放置在 \Views\Home 目录中,如果没有这个目录,MVC 将会自动创建它。
Index.cshtml 所在文件夹的名称和位置是很重要的,它是根据ASP.NET MVC 的约定来指定的。目录名称 \Views\Home ,匹配的控制器就是 HomeController ,视图模板的名字 Index,匹配将要使用这个视图的 Action 方法的名字。
当使用默认的约定的时候,ASP.NET MVC 允许我们不用显式设置这些名字和位置,当我们的代码如下所示的时候,将会默认寻找 \Views\Home\Index.cshtml。
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
}
Visutal Studio 创建并打开了Index.cshtml 视图模板,其中的内容如下:
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
视图使用了 Razor 语法,这比 Web Form 视图引擎的语法更加简单。
前三行使用 ViewBag.Title 设置了页面的标题,我们马上就可以看到这样做的效果,但是,首先,我们我们替换一下网页的内容,将 <h1> 标记中的内容修改为 This is the Home Page 。
@{
ViewBag.Title = "Index";
}
<h2>This is the Home Page</h2>
现在,我们的首页应该变成下面的样子。
为页面的公共内容使用布局
大多数的网站在页面之间有许多共享的内容:导航,页首,页脚,公司的 Logo,样式表等等。Razor 引擎默认使用名为 _Layout.cshtml 的布局来自动化管理,它保存在 /Views/Shared 文件夹中。
打开之后,可以看到下列内容:
<!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>
@RenderBody()
</body>
</html>
来自内容视图中的内容,将会被通过 @RenderBody() 来显示,任何出现在网页中的公共内容就加入到 _Layout.cshtml 中,我们希望我们的 MVC 音乐商店有一个公共的页首,其中含有链接到我们的首页和商店区域的链接,所以,我们将这些内容直接添加到这个布局中。
<!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>
更新样式表
在创建项目使用的空项目模板中,仅仅包含很简单的用来显示验证信息的样式。我们的设计师提供了一些额外的 CSS 样式和图片来改进网站的观感,现在我们就使用它们。
首先,到网站 mvcmusicstore.codeplex.com 下载 MvcMusicStore-v3.0.zip,这里面有一个文件夹 MvcMusicStore-Assets,将这个文件夹的Content 文件夹的内容复制到项目的 Content 文件夹中。
你会被询问是否需要覆盖存在的文件,选择是。
现在,网站的 Content 文件夹中的内容如下所示:
重新运行程序,现在的页面变成了这样。
我们回顾一下,什么发生了变化:HomeController 的 Index 的 Action 方法寻找并通过 \Views\Home\Index.cshtml 模板生成输出结果,代码中是通过 return View() 实现的,因为默认的命名约定,Index 这个 Action 方法将会默认使用 Index 视图输出。
而 Index 视图使用了我们的 _Layout.cshtml 模板,所以,欢迎信息被包含在标准的 HTML 布局中。
使用模型为视图传递信息
仅仅使用硬编码的 HTML 不能创建令人感兴趣的网站,创建动态网站,我们需要从控制器的 Action 传送信息给视图模板。
在 MVC 模式中,术语 Model 表示应用表现的数据,通常,模型对象用来表示数据库中保存在表中的数据,也不一定如此。
控制器的 Action 方法通过返回的 ActionResule 可以传送模型对象给视图。这就允许控制器可以将所有生成回应需要的数据打包,然会传送给视图模板,以便生成适当的 HTML 回应,在 Action 方法中可以很容易理解,让我们开始吧。
首先,我们将创建一些模型类来表示商店中的唱片类型和专辑类型,从创建类型 Genre 类开始,在项目中,右击模型 Models 文件夹,然后选择增加类选项,然后命名为 Genre.cs。
在新创建的类中增加一个属性。
public class Genre
{
public string Name { get; set; }
}
注意:这里的 { get; set; } 是 C# 的自动属性特性,这使得我们不需要在创建属性的时候,先创建一个成员字段
现在,用同样的方法创建专辑类 Album,它有两个属性:Title 和 Genre .
public class Album
{
public string Title { get; set; }
public Genre Genre { get; set; }
}
现在,我们修改 StoreController 通过模型来使视图显示动态信息,为了演示方便,我们定义专辑基于一个唯一的标识 Id, 我们将在视图中显示这个标识。
我们从修改 Details 这个 Action 使得可以显示单个的专辑开始,在 StoreController.cs 的开始部分增加一些 using 语句来包含 MvcMusicStore.Models 命名空间,这使得我们不用总是输入这个命名空间。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcMusicStore.Models;
然后,我们更新 Details Action ,使得返回 ActionResult 类型的结果而不是字符串,就像在 HomeController 中的 Index 方法中做得一样。
public ActionResult Details(int id)
现在,修改方法的处理逻辑,返回一个专辑对象到视图中,在这个项目最后,显示的数据将会来自数据库,现在我们仅仅填充一些数据而已。
public ActionResult Details(int id)
{
var album = new Album { Title = "Album " + id };
return View(album);
}
如果你对 c# 不太熟悉,可能你会认为使用 var 定义变量使用了迟绑定,这是不正确的,C# 编译器使用赋予变量的值来推定变量的类型,所以,实际上变量的类型就是 Album 类型,因此不仅在编译时, Visual Studio 的代码编辑器中也会有类型支持。
下面创建一个使用专辑来生成 HTML 的模板,在这样做之前,我们需要编译项目,以便增加视图的对话框知道我们新创建的专辑类型。你可以通过菜单“生成”的“生成解决方案”来完成。
另外,也可以通过热键 Ctrl – Shift – B 来编译项目。
已经可以创建视图模板了,在 Details 方法中右键选择“增加视图…”
像以前一样,我们看到创建视图的对话框,不一样的是,我们要选中“创建强类型视图”,然后在下面的列表中选择“Album”类,这样视图将会期望得到一个 Album 类型的对象。
在点击增加之后,我们的视图模板 \Views\Store\Details.cshtml 被创建了,其中包含的如下的代码:
@model MvcMusicStore.Models.Album
@{
ViewBag.Title = "Details";
}
<h2>Details</h2>
注意第一行,表示视图使用强类型的 Album 类。Rozer 视图引擎理解传送来的 Album 对象,所以我们可以容易地访问模型的属性,在 Visual Studio 中得到智能感知的帮助。
更新 <h2> 标记,使得可以显示专辑的 Title 属性
@model MvcMusicStore.Models.Album
@{
ViewBag.Title = "Details";
}
<h2>Album: @Model.Title</h2>
注意,智能感知使得可以提示 Album 的属性和方法。
再次运行并访问 /Store/Details/5,可以得到下面的结果。
现在,我们继续修改 Browse 方法,更新方法返回 ActionResult 类型的结果,修改方法的处理,返回一个 Genre 类型的对象实例。
public ActionResult Browse(string genre)
{
var genreModel = new Genre { Name = genre };
return View(genreModel);
}
在方法上右击,选择“增加视图…”,增加一个强类型的视图。
修改 <h2> 标记显示 Genre 的信息
@model MvcMusicStore.Models.Genre
@{
ViewBag.Title = "Browse";
}
<h2>Browsing Genre: @Model.Name</h2>
重新运行,访问 /Store/Browse?Genre=Disco,可以看到如下的显示
最后,将 Index 也修改为强类型的视图,显示所有唱片的类别,我们使用 Genre 的一个列表,而不是单个的 Genre 对象。
public ActionResult Index()
{
var genres = new List<Genre>
{
new Genre { Name = "Disco"},
new Genre { Name = "Jazz"},
new Genre { Name = "Rock"}
};
return View(genres);
}
创建一个强类型的视图
首先,我们将期望得到多个 Genre 对象而不是一个,将第一行修改为如下内容。
@model IEnumerable<MvcMusicStore.Models.Genre>
这告诉视图引擎模式是一个包含多个 Genre 对象的集合,我们使用 IEnumerable<Genre> 而不是 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>
注意,此时有完全的智能提示
在 foreach 循环中,也同样有提示。
再次运行程序,我们可以看到如下的结果。
增加页面之间的链接
现在,我们的 /Store 可以使用纯文本列出当前的分类名称,下一步,我们将这些纯文本替换成可以链接到浏览分类的链接 /Store/Browse 上,这样,当用户点击音乐分类“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>
这样就可以工作了,但是这里使用了硬编码的字符串,如果我们希望修改控制器的名称,那么,我们就要找到所有这样的位置进行修改
更好的处理方式是使用 HTMLHelper 的助手方法,ASP.NET MVC 包含了一个 HTML 的助手类,其中的方法专门用于在视图模板中完成多种常见的任务,其中的Html.ActionLink() 助手方法就是常用的一个,这使得可以容易地创建 <a> ,包括关于链接的一些细节处理,像地址需要进行 URL 编码之类。
Html.ActionLink() 有多个重载用于多种情况,在简单的情况下,你只需要提供提示的文本,以及指向的 Action 方法即可,在客户端,举个例子,我们希望链接到 /Store 的 Index 方法,提示文本为 Go to the Store Index,那么下面的代码就可以。
@Html.ActionLink("Go to the Store Index", "Index")
注意:在这个例子中,我们不需要再特别指定控制器的名称,因为我们在使用同一个控制器的不同 Action 方法。
我们的链接还需要一些参数,我们可以使用另外一种重载来传递三个参数。
1. 链接的提示文本,这里显示分类的名称
2. 控制器的名称,Browse
3. 路由参数,提供名字 genre 和值,genre 的名字
合在一起,下面就是需要写在视图模板中的内容
<ul> @foreach (var genre in Model)
{
<li>@Html.ActionLink(genre.Name, "Browse", new { genre = genre.Name })</li>
}
</ul>
现在,当我们运行程序,访问 /Store 的时候,将会看到一个分类的列表,每一个分类都是一个超级链接,当点击链接的时候,将会被导航到 /Store/Browse?genre=[genre] 的地址
页面中生成的分类链接如下:
<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>