现在我们已经可以从控制器的action方法中返回字符串了。我们可以藉此来理解控制器是如何来工作的。但在真正的Web应用程序中大多数情况下返 回的都不仅仅是一个字符串。我们更需要的是当用户输入URL地址请求时,能够返回给浏览器一串HTML格式的输出流,我们可以借由模板文件来更方便地定制 这个HTML格式的输出流,并将其返回。在ASP.NET MVC中,将这个模板文件称之为视图。
3.1 追加一个视图模板
为了要使用视图模板,我们修改HomeController控制器中的Index方法,使其返回一个ActionResult对象,并且将return语句修改为return View(),代码如下所示。
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
}
接下来,我们在应用程序中追加一个视图模板。在Index这个action方法中点击鼠标右键,然后点击“添加视图”,如图3-1所示。
图3-1 添加视图
点击“添加视图”后,弹出一个“添加视图”对话框,如图3-2所示。
图3-2 添加视图对话框
添加视图对话框使我们可以快速地、方便地产生视图模板文件。在默认情况下,添加视图对话框将使用该视图的action方法的名字作为视图模板的名字。这 里,因为我们在HomeController控制器中的Index方法中点击了“添加视图”子菜单,所以在添加视图对话框中的视图模板名字默认为 “Index”。在该对话框中,不做任何修改,直接点击添加按钮。
点击添加按钮之后,Visual Web Developer将会在Views文件夹下的Home文件夹中自动为我们创建一个名为Index.cshtml的视图模板文件,如图3-3所示。
图3-3 Index.cshtml视图模板文件已被创建
Index.cshtml文件的命名与存放位置是非常重要的,而且也遵循了ASP.NET MVC的命名约定。“Views\Home”这个文件夹与HomeController控制器互相对应。Index这个视图模板与控制器中返回该模板文件的Index方法互相对应。
当我们使用了这个默认的命名约定来返回一个视图的时候,我们可以不用显式地指定该视图模板文件的存放位置。在HomeController控制器中书写如下所示的代码,浏览器将自动渲染“Views\Home”这个文件夹中的Index.cshtml视图模板文件。
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
}
查看Index.cshtml文件,代码内容如下所示。
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
在这段代码中,使用到了Razor视图引擎,该视图引擎使用一种比在Web Forms与在之前版本的ASP.NET MVC中更加精简的代码书写方法。在ASP.NET MVC3中,之前的WebForms仍然能够继续使用,但是很多开发者发现Razor视图引擎更加适合ASP.NET MVC的开发。
上面这三行代码中使用ViewBag.Title属性来设置在浏览器中进行显示时的浏览器窗口标题。我们将很快能够在浏览器中查看这一设置的结果,但这里首先让我们更新页面中的标题文字,将<h2>标签中的文字修改为“网站首页”,代码如下所示。
@{
ViewBag.Title = "首页";
}
<h2>网站首页</h2>
运行应用程序,浏览器中的显示结果如图3-4所示。
图3-4 浏览器中的Index视图
3.2 为公共站点元素使用统一布局
大多数网站中都有一个很多页面共同使用的公共内容:导航条,脚注,logo图片,样式表等等。Razor视图引擎使用一个公共页面来管理这些公共内容,从而使得公共内容的使用与修改变得更加容易。这个公共页面被命名为“_Layout.cshtml”,在一开始自动创建应用程序的时候,这个文件也被创建于“/Views/Shared”文件夹中,如图3-5所示。
图3-5 _Layout.cshtml文件已被创建
双击打开该文件,文件中代码如代码清单3-1所示。
代码清单3-1 _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()
MVC3书店这个Web网站拥有一个每个网页上都会出现的公共标题与两个公有链接,一个链接到我们的首页,另一个链接到书籍种类列举显示页面,所以我们将该文件中的代码修改如代码清单3-2所示。
代码清单3-2 添加了公共标题与公共链接的_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>
<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>
3.3 更新样式表
一个空的项目模板中也会包括一个非常简单的CSS样式表文件,在该样式表文件中只包括了显示验证信息时所用到的样式。现在我们想在其中加入网站中所需要用到的其他的一些样式与图片,让我们来看一下应该如何进行添加。首先我们可以将我们的样式文件追加在Content文件夹下,将所有的图片追加在Content文件夹下的Images文件夹下,如图3-6所示。
图3-6 追加样式表与图片
现在让我们来运行我们的应用程序,看一下现在的首页在浏览器中的显示效果,如图3-7所示。
图3-7 添加了样式与图片后的首页
现在让我们暂时回顾一下Visual Web Developer能够进行哪些工作:HomeController控制器的Index这个action方法能够自动找到并显示Views文件夹下的Home文件夹下的Index.cshtml这个视图模板,而我们只需要在代码中写入“return View()”语句,因为我们的视图模板的命名与存放位置是遵循ASP.NET MVC3的标准约定的。
首页中显示了我们在视图模板文件(Views文件夹下的Home文件夹下的Index.cshtml文件)中所指定的“网站首页”文字。
首页中使用了前面所讲的_Layout.cshtml这个公共视图模板文件,所以显示出了在公共视图模板文件中所指定的公共信息。
3.4 使用一个模型来将信息传入我们的视图
如果一个视图模板文件中只是显示静态内容的话,是不可能使网站变得多么生动有趣的。为了要创建一个动态的Web网站,我们需要将信息从控制器传入视图模板文件中。
在MVC(模型-视图-控制器)框架中,“模型”用来映射应用程序中所使用到的各种数据。通常情况下,模型对象用来映射数据库中的数据表,但也可以是其他数据。
控制器中返回ActionResult对象的action方法可以将一个模型对象传入视图。这意味着控制器可以将生成一个响应流时所需要用到的所有数据进 行封装,然后传入一个视图模板文件,用以生成HTML格式的响应流。接下来我们用一个action方法为例,来看看控制器如何将数据传入视图模板中。
首先我们将要创建一些模型类,分别代表书店中的书籍种类信息与某本书籍的具体信息。为了要创建一个Genre模型类(书籍种类),鼠标右击Models文件夹,点击“添加类”,如图3-8所示。
图3-8 添加模型类
在弹出的添加类对话框中,将类命名为“Genre.cs”,如图3-9所示。
图3-9 将模型类命名为“Genre.cs”
点击添加按钮,在打开的类文件中追加一个public(公有)的string(字符串)类型的Name(种类名称)属性,代码如下所示。
public class Genre
{
public int ID { get; set; }
public string Name { get; set; }
}
接下来,用同样的方法添加一个Book(书籍)类,文件名为Book.cs,添加一个Title(标题)属性与一个Genre(书籍种类)属性,代码如下所示。
public class Book
{
public string Title { get; set; }
public Genre Genre { get; set; }
}
接下来,让我们修改StoreController控制器,使其能够使用视图,展示模型中的信息。
首先,我们修改StoreController控制器中的Details这个action方法,使其能够显示单本书籍的信息。在 StoreController控制器类的头部追加一句using语句来引用MvcBookStore.Models命名空间。这样我可以避免在每次使用 Book类的时候都需要键入MvcBookStore.Models.Book。StoreController控制器类的头部处using语句的代码如 下所示。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcBookStore.Models;
接下来,我们修改控制器中Details这个action方法,使其不再返回一个字符串,而是与HomeController控制器中的Index方法同样,返回一个ActionResult对象,代码如下所示。
public ActionResult Details(int id)
现在我们修改Details方法内部的代码,使其向视图中传入一个Book对象。在后文中我们将可以从数据库中首先获取一个Book(书籍)对象的数据, 然后再将该Book对象传入视图,但是现在我们先暂时直接在代码中直接写入Book对象的数据,并传入视图,代码如下所示。
public ActionResult Details(int id)
{
var book = new Book { Title = "书籍 " + id };
return View(book);
}
注意:如果你对C#不很熟悉的话,你也许会认为使用“var”声明的话就意味着代码中的book变量是后期绑定的。事实上并不是如此,C#编译器使用类型 引用的基础是取决于我们给变量赋的值是什么类型的,因为book的类型是Book,所以编译器把book变量编译为一个本地的Book类型的变量,所以我 们仍然可以使用C#中的编译时检查和Visual Web Developer中的代码智能输入功能。
现在让我们创建一个显示单本书籍信息的视图模板。在这之前我们需要重编译一下我们的应用程序,以确保在添加视图对话框中的选择模型类下拉框中可以找到我们刚加入的Book(书籍)模型类。你可以通过点击“调试”菜单中的“生成 MvcBookStore”菜单项来进行编译,如图3-10所示。
图3-10 编译应用程序
现在我们的类已经编译好了,我们可以追加这个展示单本书籍的视图模板了。在Details方法中点击鼠标右键,选择“添加视图”。如图3-11所示。
图3-11 在Details方法中追加视图
我们要追加的视图与之前在HomeController控制器中追加的视图稍微有所不同,这一次我们在“添加视图”对话框中勾选“创建强类型视图”复选框,在模型类下拉框中选择“Book(MvcBookStore.Models)”,如图3-12所示。这样的话添加出来的视图模板会自动认为已经从外面接收到了一个Book对象,并且自动追加视图中对于该对象进行处理的代码。
图3-12 添加展示单本书籍信息所用视图
点击添加按钮,在我们的Views文件夹下面的Store文件夹中会自动添加一个Details.cshtml模板文件,打开该文件,其中已经自动添加如下所示的代码。
@model MvcBookStore.Models.Book
@{
ViewBag.Title = "Details";
}
<h2>Details</h2>
请注意第一行,标示着这个视图是已经使用我们的Book类经过强类型化处理的。Razor视图引擎会自动将其判定为传入了一个Book对象。这样我们可以很轻松地访问我们的模型类中的各个属性,同时也可以使用Visual Studio编辑器中的智能代码输入功能。
将<h2>标签中内容修改为如下所示的代码,使其可以显示传入的Book对象的Title属性中的内容。
请注意当你在@Model后面输入点符号的时候会触发智能代码输入功能,显示Book类中的各个属性。
现在让我们重新运行我们的应用程序,并访问“/Store/Details/1”这个URL地址,浏览器中显示结果如图3-13所示。
图3-13 单本书籍信息展示画面
现在我们对Browse这个action方法做类似修改。首先修改方法使其返回一个ActionResult对象,然后修改方法内部代码,创建一个新的Genre(书籍种类)对象,并将其传入视图,最后返回该视图,代码如下所示。
public ActionResult Browse(int id)
{
String[] name=new string[3]{“小说”,”教材”,”科学”};
var genreModel = new Genre {ID=id,Name = name[id-1] };
return View(genreModel);
}
在Browse方法中点击鼠标右键,然后点击“添加视图”菜单,然后追加一个强类型视图,并且在模型类下拉框中选择“Genre(MvcBookStore.Models)”,如图3-14所示。
图3-14 添加书籍种类展示视图
更新视图模板(Views文件夹下Store文件夹中的Browse.cshtml)中的代码,使其显示书籍种类信息,代码如下所示。
@model MvcMusicStore.Models.Genre
@{
ViewBag.Title = "书籍种类";
}
<h2>书籍种类: @Model.Name</h2>
重新运行我们的应用程序,并访问“/Store/Browse/1”这个URL地址,浏览器中显示结果如图3-15所示。
图3-15 书籍种类展示画面
最后,让我们对StoreController控制器类中的Index这个action方法做一些稍为复杂的修改,使其可以显示由书店中所有书籍种类组成的列表。我们通过使用一个由我们的模型中的Genre对象所组成的列表(List)来实现这一功能,代码如下所示。
public ActionResult Index()
{
var genres = new List<Genre>
{
new Genre { ID=1,Name = "小说"},
new Genre { ID=2, Name = "教材"},
new Genre { ID=3,Name = "科学"}
};
return View(genres);
}
在Index方法中点击鼠标右键,然后点击“添加视图”菜单,然后追加一个强类型视图,并且在模型类下拉框中选择“Genre(MvcBookStore.Models)”,在支架模板下拉框中选择“List”,如图3-16所示。
图3-16 添加书籍种类列表展示视图
点击添加按钮后打开自动创建的视图文件,第一行中代码标识着视图中接收到了一个Genre对象所组成的集合。
@model IEnumerable<MvcMusicStore.Models.Genre>
这行代码告诉Razor视图引擎将要处理一个存放了Genre对象所组成集合的模型(model)对象。这里我们使用 IEnumerable<Genre>,而不是使用List<Genre>,因为前者更加通用,允许我们之后可以将视图中所使用 的模型类型修改为其他任何支持Ienumerable接口的集合。
接下来,我们书写如下所示的代码,对模型中的Genre对象所组成的集合进行一个遍历。
@model IEnumerable<MvcMusicStore.Models.Genre>
@{
ViewBag.Title = "挑选书籍";
}
<h3>书籍种类展示</h3>
<p>
本书店共出售如下 @Model.Count() 种书籍:</p>
<ul>
@foreach (var genre in Model)
{
<li>@genre.Name</li>
}
</ul>
请注意当书写代码时智能代码输入功能始终处于触发状态,例如当我们输入“@Mode”之后编辑器自动显示一个Genre类型的IEnumerable接口所支持的各种方法与属性,如图3-17所示。
图3-17 编辑器自动显示一个Genre类型的IEnumerable接口所支持的各种方法与属性
在“foreach”循环中,Visual Web Developer能够自动将每一个项目判定为Genre类型,编辑器自动显示Genre类型所支持的方法与属性,如图3-18所示。
图3-18编辑器自动显示Genre类型所支持的方法与属性
请注意,在自动创建Genre类型的强类型化视图的时候,能够判别出来该类型有个Name属性,并在进行遍历的时候将该属性内容写出。同时自动生成 Edit(编辑)链接,Details(数据详细信息)链接与Delete(删除)链接。在后文中我们将会针对编辑、删除与数据详细信息展示功能进行详细 介绍,这里暂且略过。
重新运行应用程序,并且访问“/Store”URL地址,浏览器中显示如图3-19所示。
图3-19 书籍种类列表画面
3.5 追加页面与页面之间的链接
现在我们的“/Store”这个URL地址所显示的页面中只是简单地以文字的形式列举出了各种书籍种类的名称。接下来让我们把此处的种类名称修改为可以链 接到“/Store/Browse”这个URL地址,这样的话当用户点击“小说”这个书籍种类的时候,页面可以转至“/Store/Browse/1”这 个URL地址。
更新Views文件夹下的Store文件夹中的Index.cshtml视图中的代码如下所示。
<ul>
@foreach (var genre in Model)
{
<li><a href="/Store/Browse/@genre.ID">@genre.Name</a></li>
}
</ul>
这段代码是有效的,但非常具有局限性。例如,如果之后我们想改变控制器的名称,我们还不得不修改此处的代码。
一种更好的书写方法是借助于一个HTML帮助器方法。在ASP.NET MVC的视图中,可以使用HTML帮助器方法来实现大量类似于此的共通处理。Html.ActionLink()就是其中一个非常有用的HTML帮助器方 法,它使得HTML中的<a>链接的书写方法变得更加容易,同时可以确保链接中使用的URL路径是正确的URL地址格式。
Html.ActionLink()有几个不同的重载方式,从而允许你在链接中指定任何所需要的信息。譬如在如下所示的简单示例中,你可以指定链接文字以 及当链接在客户端被点击时所执行的Action方法。例如,在代码中,我们可以使用“链接到Store地址的Index方法”链接到 StoreController控制器中的Index方法中。
@Html.ActionLink("链接到Store地址的Index方法", "Index")
请注意,在这种情况下,我们不需要指定控制器的名称,因为我们只是链接到了使用当前视图的控制器的另一个action方法中。
链接到书籍种类展示页面的时候还需要提供两个参数,所以我们使用Html.ActionLink()方法的另一种重载方式,使其使用如下参数:
- 使用种类名称作为链接文字。
- 控制器中action方法的名称(Browse)。
- 页面参数,指定ID属性的属性名称与属性值。
最终修改代码如下所示:
<ul>
@foreach (var genre in Model)
{
<li>@Html.ActionLink(genre.Name,"Browse", new {id=genre.ID})
</li>
}
</ul>
现在我们可以重新运行我们的应用程序,访问“/Store”这个URL地址,我们可以看见一个所有书籍种类的列表展示页面,如图3-20所示。每一个种类都带有一个超链接,点击该超链接可以访问“/Store/Browse/[书籍ID]”这个URL地址。
图3-20 所有书籍种类的列表展示页面
查看这个所有书籍种类的列表展示页面的html源代码,代码如下所示。
<ul>
<li><a href="/Store/Browse/1">小说</a></li>
<li><a href="/Store/Browse/2">教材</a></li>
<li><a href="/Store/Browse/3">科学</a></li>
</ul>