ASP.NET MVC 音乐商店 - 3. 视图与模型

来源:岁月联盟 编辑:exp 时间:2011-11-12

 

上一篇中有同学提到为什么不使用视图,而使用字符串,这一篇我们就开始使用视图来处理。

我们已经可以从控制器的 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>  

 

作者 冠军