MVC音乐商店 - 第三部分:视图和视图模型
到目前为止,我们仅完成了从控制器返回简单的字符串,这是一个了解控制器如何工作的好途径,但这并不是我们想要的web应用程序。我们想要一个更好的方法来生成HTML(现在跑起来看下程序,是不是只有一行字符串呢?),使用模板文件可以更容易地定制发回的HTLM内容。这正是VIEW(视图)需要做的东西。
添加视图模板
若要使用一个视图模板,就需要修改HomeController中的Index方法,让他返回ActionResult,就像下面这样返回View():
public class HomeController : Controller { // // GET: /Home/ public ActionResult Index() { return View(); } }
上述的更改现在已经被替代,不是之前返回简单的字符串了,而是使用View生成结果,然后返回。
我们现在需要为项目添加一个视图模板,把光标停留在Index方法上面,单击右键,
“添加视图”对话框允许我们来快速、轻松的生成视图的模板文件。默认情况下,“添加视图”对话框将预先填写好模板里的内容,因此,当我们在控制器HomeController的Index方法中右键创建视图时,会默认使用当前动作方法的名称,我们不需要更改任何选项,所以请单击“添加”按钮。
点击添加按钮后,VS会在Views\Home目录中创建一个新的视图模板-Index.cshtml,如果没有Views文件夹,IDE环境将创建一个新的以Views命名的文件夹。
“Index.cshtm”文件的名称及文件夹非常重要,遵循了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(); } }
按下“添加视图”中的“添加”按钮后,VS将创建并打开Index.cshtml视图模板,Index.cshtml视图模板的内容如下所示:
@{ ViewBag.Title = "Index"; } <h2>Index</h2>
视图使用了“Razor”,这是比ASP.NET及以前版本的ASP.NET MVC 中使用的Web Forms 引擎更简洁的语法。ASP.NET MVC 3 同时也支持Web Forms视图引擎(在视图引擎选择处有2个选项(Razor(cshtml)/ASPX(C#))),但很多的开发者发现Razor视图引擎比Web Forms引擎更适合ASP.NET MVC 的开发。
首三行用ViewBag.Title 设置页面的标题,我们将很快会看到他是怎样工作的,但现在我得改变视图页面的文本内容,新的内容要显示成-“这是主页!”,如下面的<h2>标签:
@{ ViewBag.Title = "Index"; } <h2>Index</h2>
F5运行程序,我们将看到新的文本内容。
常用的网站元素布局
大多数的网站在多个页面之间有相同的内容:导航、页脚、Logo、样式引用等,Razor视图引擎也自动地在/Views/Shared文件夹下创建了一个_Layout.cshtml,他使我们更容易管理这些文件。
双击这个文件,显示内容如下:
<!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音乐商店所有的页面都有一个链接到Home和Store的头部,因此我们将他添加到模板中@RenderBody()中的前面(如外链JS文件)。
<!DOCTYPE html> <html> <head> <title>@ViewBag.Title</title> <link href="@Url.Content("http://www.cnblogs.com/Content/Site.css")"rel="stylesheet" type="text/css" /> <script src="@Url.Content("http://www.cnblogs.com/Scripts/jquery-1.4.4.min.js")"type="text/javascript"></script> </head> <body> <div id="header"> <h1> ASP.NET MVC 音乐商店</h1> <ul id="navlist"> <li class="first"><a href="/" id="current">主页</a></li> <li><a href="/Store/">商店</a></li> </ul> </div> @RenderBody() </body> </html>
更新样式表
新建项目里的CSS只是一个精简的,只包括用于显示验证消息。现在提供一些附件的css和图像来美化我们的网站外观。现在就去下载:MvcMusicStore-Assets.zip ,所有的样式脚本和图片都在MvcMusicStore-Assets文件夹里,将他们添加到项目里。
因为有相同名称的文件存在,提示是否覆盖现有文件,选择”是“
添加后的项目如下图所示
现在运行程序,看下更改之后的样子
现在看看做了哪些修改:
- 按照视图模板默认的命名约定,HomeController的Index方法通过调用View()找到并显示\Views\Home\Index.cshtml视图模板
- 在\Views\Home\Index.cshtml上定义了主页上显示的一条简单的信息
- 在主页上使用了_Layout.cshtml模板,并且以html格式输出(浏览器里查看源代码)
使用模型(Model)把信息传递给视图
视图模板仅显示硬编码的HTML内容,这并不是一个有趣的web站点。若要创建一个动态的web站点,我们需要从控制器中传递信息到视图模板。
在Model-View-Controller(模型-视图-控制器)模式中,模型对象表示程序中的数据,通常情况下,模型对象对应于数据库中的表,但这里不是。
返回ActionResult的控制器方法可以把模型对象传递给视图,这使得控制器干净地封装了所有的信息,然后将这些信息传递给视图模板并生成适当的HTML。这是最简单的方式,让我们开始吧。
首先,我们创建一些模型类来描述音乐商店的类别和专辑,我们从Gener类开始。在项目中,右键单击“Models”文件夹,选择“添加 ->类”,并将文件命名为Genre.cs
然后将一个公用的字符串属性-Name添加到创建的类中
public class Genre { public string Name { get; set; } }
注意:这里你可能会感到奇怪,{get;set;}是C#自动属性让我们方便定义属性而不需要声明字段。
下面,按刚才的步骤添加专辑类(Album.cs),他有两个属性:Title和Genre
public class Album { public string Title { get; set; } public Genre Genre { get; set; } }
现在,我们可以修改StoreController,使用视图显示模型信息
我们通过修改控制器StoreController中的Details方法,让他显示单个专辑的信息。需要引用using MvcMusicStore.Models命名空间,在以后使用Album类时,就不需要每次都输入MvcMusicStore.Models.Album了,代码显示如下:
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 可替代任何类型,因为编译器会根据上下文来判断您到底是想用什么类型,当在您无法确定自己将用什么类型时,就可以用var,类型Object,但效率比Object高点。(和原文不一致)
现在让我们给Album生成一个HTML响应的视图模板,在这之前,我们需要生成项目,以便让“添加视图”对话框知道我们要创建Alubm类。在“生成”菜单中选择“生成MvcMusicStore”(或者使用快捷键Ctrl+Shift+B)。
现在,我们已经设置好了支持的类,我们准备创建视图模板,单击右键,然后从菜单中选择“添加视图”。
我们要为Details创建一个视图模板,就像之前为HomeController创建视图模板一样,因为我们是在StoreController创建,他会默认生成生成\Views\Store\Details.aspx。
和先前不同的是,这次我们要把“创建强类型视图”选项勾上,然后在“视图数据类”下拉框中选择Album类,“添加视图”对话框将产生视图模板,并将Album对象传递给他。
当单击"添加"按钮时,将创建\Views\Store\Details.cshtml 视图模板,代码如下:
@model MvcMusicStore.Models.Album @{ ViewBag.Title = "Details"; } <h2>Details</h2>
注意:第一行代码表示Album的强类型视图,使用Razor引擎可以让我们更容易访问模板属性,支持Visual Studio的“智能感应器”。
更新<h2>标签里的代码,显示Album的Title属性,如以下代码:
<h2>Album: @Model.Title</h2>
注意当输入@的时候,看智能提示中的属性与方法,现在运行程序,并访问 /Store/Details/5,我们将看到下面页面显示的内容:
现在我们同样来修改StoreController的Browse方法,修改方法的返回值为ActionResult,修改方法的业务逻辑返回一个Gener对象给视图。
public ActionResult Browse(string genre) { var genreModel = new Genre { Name = genre }; return View(genreModel); }
光标停留在Browse方法上,单击右键选择“添加视图”,添加一个Gener的强类型视图。
同样更新视图中(/Views/Store/Browse.cshtml)<h2>标签中的代码,如下:
@model MvcMusicStore.Models.Genre @{ ViewBag.Title = "Browse"; } <h2>Browsing Genre: @Model.Name</h2>
运行项目,并访问 /Store/Browse?Genre=Disco,我们将看到以下页面:
是不是觉得前面的太简单了?现在,让我们做一个稍微复杂点的修改,让StoreController的Index方法和视图显示中所有的列表,我们这样做是使用Gener的集合而不是单个Gener对象。
public ActionResult Index() { var genres = new List<Genre> { new Genre { Name = "流行"}, new Genre { Name = "爵士"}, new Genre { Name = "摇滚"} }; return View(genres); }
在Index方法上单击右键,选择“添加视图”,类型选择Gener,按“添加确定”
首先,需要更改/Store/Index.cshtml里第一行的指令:
@model IEnumerable<MvcMusicStore.Models.Genre>
这将告诉Razor 引擎,他将不是对象而是一个集合,我是在使用 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>
当我们输入“@”符号的时候需要注意IDE环境的智能感应出的属性及方法。
输入 /Store 运行项目,看下更新后的效果:
添加页面之间的链接
我们目前是把类别的名称作为纯文本列出。现在让我们用一个指向/Store/Browse 的链接来替换这些纯文本,比如在“流行”上点击时,可以链接到/Store/Browse?genre=流行页面,我们需要修改 \Views\Store\Index.cshtml视图模板,如下:
<ul> @foreach (var genre in Model) { <li><a href="/Store/Browse?genre=@genre.Name">@genre.Name</a></li> } </ul>
可以运行,但他以后可能会出现问题,因为他依赖与硬编码。如果,重命名控制器,我们必须把相关的地方也修改。
一个可以改进的方法,利用HTML helper(帮助器)中的方法。ASP.NET MVC中包含的HTML helper(帮助器)方法运行我们从视图模板中执行这种常见的任务。Html.ActionLink() helper(帮助器)是一个特别有用的方法之一,可以方便的生HTML<a>标签和处理那些令人讨厌的细节,如,URL路径是正确的URL编码?
Html.ActionLink()方法有很多重载,允许我们选择我们所需要的链接。在这里,当链接被单击时,就链接到文本中去。例如,创建一个链接到Index的方法:
@Html.ActionLink("Go
to the Store Index", "Index")
注意:在这种情况下,我们不需要指定控制器的名称,因为我们是从当前的视图链接到同一个控制器里的另一个方法。
我们在链接页面时,需要传递参数,不过,我们可以用Html.ActionLink(),他有三个参数的重载:
- 链接的文本—只用类型名称(gener.Name)
- 控制器方法—Borwse
- 路径参数值,指定的名称(gener)和值(gener.Name)
把这些都加起来,如下代码:
<ul> @foreach (var genre in Model) { <li>@Html.ActionLink(genre.Name, "Browse", new { genre = genre.Name })</li> } </ul>
现在再次运行项目,并访问/Store,我们可以看到类别列表,每个类别都是一个超链接,点击之后会链接进/Store/Browse?genre=[genre]
类型列表中的HTML如下所示:
<ul> <li><a href="/Store/Browse?genre=流行">Disco</a> </li> <li><a href="/Store/Browse?genre=爵士">Jazz</a> </li> <li><a href="/Store/Browse?genre=摇滚">Rock</a> </li> </ul>
第三部分总结:
1.添加控制器:在Controller目录,单击右键,添加控制器即可;
2.添加视图:在控制器里,方法单击右键 ->添加视图,会在Views文件夹下以控制器命名文件夹下自动生成 方法名.cshtml的视图模板,.cshtml和以前写的.aspx差不多;
3._Layout.cshtml是公用模板,在Views\Shared文件夹下面,@RenderBody()是用来呈现各个视图模板的,显示通用的链接(详细在上面有介绍);
4.增加了样式,从官方网站中下载文件:http://mvcmusicstore.codeplex.com下载,打开压缩包,把里面的文件复制到解决方案中(上面有详细介绍);
5.Model里新建了2个类(Genre/Album),Genre表示商品的类型,只有一个Name属性;Album类里有2个属性(Title/Genre)—>标题与音乐类型;
6.后面在Store的Index视图里增加超链接;
还记得这张图吗?