视图的作用与基础知识
控制器需要向视图提供一些信息, 所以它会传递一个数据转移对象,叫做模型。视图将这个模型转化为适合显示 给用户的格式。
在 ASP.NET MVC中, 完成这一过程由两部分操作。其中一个是检查由控制器提交的模型对象,另一个是将其内容转换为HTML格式。
一个控制器相当于按某个功能划分出来的模块, 它可以对应多个视图。
如:Home 下的:
1. Index.cshtml
@{
ViewBag.Title = "Home Page";
}
<div class="jumbotron">
<h1>ASP.NET</h1>
<p class="lead">ASP.NET is a free web framework for building great Web sites and Web applications using HTML, CSS and JavaScript.</p>
<p><a href="http://asp.net" class="btn btn-primary btn-lg">Learn more »</a></p>
</div>
<div class="row">
<div class="col-md-4">
<h2>Getting started</h2>
<p>
ASP.NET MVC gives you a powerful, patterns-based way to build dynamic websites that
enables a clean separation of concerns and gives you full control over markup
for enjoyable, agile development.
</p>
<p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301865">Learn more »</a></p>
</div>
<div class="col-md-4">
<h2>Get more libraries</h2>
<p>NuGet is a free Visual Studio extension that makes it easy to add, remove, and update libraries and tools in Visual Studio projects.</p>
<p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301866">Learn more »</a></p>
</div>
<div class="col-md-4">
<h2>Web Hosting</h2>
<p>You can easily find a web hosting company that offers the right mix of features and price for your applications.</p>
<p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301867">Learn more »</a></p>
</div>
</div>
2. About.cshtml
@{
ViewBag.Title = "About";
}
<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>
<p>Use this area to provide additional information.</p>
3. Contact.cshtml
@{
ViewBag.Title = "Contact";
}
<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>
<address>
One Microsoft Way<br />
Redmond, WA 98052-6399<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>
<address>
<strong>Support:</strong> <a href="mailto:Support@example.com">Support@example.com</a><br />
<strong>Marketing:</strong> <a href="mailto:Marketing@example.com">Marketing@example.com</a>
</address>
HomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MvcMusicStore.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
}
视图相当于模板引擎。ViewBag 适合于传递简单数据。
控制器中的模型,传递到视图, 结合那些静态的html, 就形成了最终的显示效果。
控制器与视图的约定
控制器中的每一个方法默认对应视图中的同名视图。
如果希望Index方法渲染一个不同的视图,则应该写:
public ActionResult Index()
{
return View("NoIndex");//最终会查找:NoIndex.cshtml
}
如果希望Index渲染其它目录下的视图,则应该写:
public ActionResult Index()
{
return View("~/Views/Example/Index.cshtml");//最终会查找:~/Views/Example/Index.cshtml
}
强类型视图
Store 中方法:
public ActionResult List()
{
var albums = new List<Album>();
for (int i = 0; i < 10; i++)
{
albums.Add(new Album { Title = "Product " + i });
}
ViewBag.Albums = albums;
return View();
}
List视图:
<ul>
@foreach (MvcMusicStore.Models.Album a in (ViewBag.Albums as IEnumerable<MvcMusicStore.Models.Album>)) {
<li>@a.Title</li>
}
</ul>
注意:需要将 ViewBag.Albums 转换为 IEnumerable<MvcMusicStore.Models.Album>)
为了使代码整洁, 也可以用 dynamic:
<ul>
@foreach (dynamic a in (ViewBag.Albums as IEnumerable<MvcMusicStore.Models.Album>)) {
<li>@a.Title</li>
}
</ul>
但是这样就没有智能感知了。
对于Controller 可以通过向重载的View 方法中传递模型实例来指定模型, 代码如下所示:
public ActionResult List()
{
var albums = new List<Album>();
for (int i = 0; i < 10; i++)
{
albums.Add(new Album { Title = "Product " + i });
}
ViewBag.Albums = albums;
return View(albums); //原来是 View();
}
下一步是告知哪种类型的模型正在使用 @model 声明。 注意这里需要输入模型类型的完全限定类型名(名称空间和类型名称), 如下所示:
<ul>
@model IEnumerable<MvcMusicStore.Models.Album>
@foreach (MvcMusicStore.Models.Album a in Model) {
<li>@a.Title</li>
}
</ul>
如果不想输入模型的完全限定类型名, 可使用 @using 关键字声明, 如下所示:
<ul>
@using MvcMusicStore.Models;
@model IEnumerable<Album>
@foreach (Album a in Model) {
<li>@a.Title</li>
}
</ul>
对于在视图中经常使用的名称空间, 一个较好的办法是在Views 目录下的 Web.config 文件中声明。
为了查看先前的两个例子的实例应用, 使用 NuGet 将 Wrox.ProMvc5.Views.AlbumList 包安装到一个默认的 ASP.NET MVC5 项目中, 如下所示:
Install-Package Wrox.ProMvc5.Views.AlbumList
这样就把两个视图例子放进了文件夹 \Views\Albums中, 并且把相应的控制器代码放进了文件夹\Samples\AlbumList中。按 Ctrl+F5 快捷键 运行项目, 在浏览器中访问 /albums/listweaklytyped 和 /albums/liststronglytyped, 就可以看到代码的运行效果。
http://localhost/MvcMusicStore/Albums/ListWeaklyTyped
http://localhost/MvcMusicStore/Albums/ListStronglyTyped
理解ViewBag, ViewData, 和 ViewDataDictionary
ViewData["CurrentTime"]=DateTime.Now;
ViewBag.CurrentTime = DateTime.Now;
两者是等同的, ViewBag 是ViewData 的动态封装器。但ViewBag 更受欢迎。
类似 ViewData["Key With Spaces"] 无法使用ViewBag 访问, 因为它不是一个合法的C#标识符。
如果其中任何一个参数是动态的, 那么就不会通过编译。
@Html.TextBox("name", ViewBag.Name)
正确的做法=>
@Html.TextBox("name", ViewData.["Name"]);
@Html.TextBox("name", (string)ViewBag.Name);
ViewDataDictionary 是一个特殊的字典类, 而并不是一个通用的Dictionary。原因之一在于, 它有一个额外 的Model属性, 允许向视图提供一个具体的模型对象。因为ViewData只能有一个模型对象。所以使用ViewDataDictionary向视图传递具体的类十分方便。
视图模型
视图通常需要显示各种没有直接映射到域模型的数据。例如,可能需要视图来显示单个商品的详细信息。有时在同一视图上也需要显示商品附带的其他 信息,比如当前登录系统的用户名、该用户是否有权编辑商品等。
把与视图主模型无关的数据存放在ViewBag属性中,可以很容易地实现这些数据在视图中的显示。当具有一个清晰定义的模型和一些额外的引用数据时, 这种方法尤为有用。这种技术的一种常见的应用是使用ViewBag为下拉列表提供表单选项。例如,MVC Music Store项目的Album Edit视图需要填充Genres和Albums 下拉列表, 但是这些下拉列表不适合放到Albums模型中。 为了应对这种情况, 同时不使用无关信息影响 Album 模型, 我们可以将 Genre和Album的信息保存到 ViewBag 中。
public ActionResult Edit(int id = 0)
{
Album album = db.Albums.Find(id);
if (album == null)
{
return HttpNotFound();
}
ViewBag.GenreId = new SelectList(
db.Genres, "GenreId", "Name", album.GenreId);
);
ViewBag.ArtistId = new SelectList(
db.Artists, "ArtistId", "Name", album.ArtistId);
);
}
这么做当然能够完成要求, 并且也为视图中显示数据提供了一种灵活的方法。但是这并不是一种应该经常使用的方法。 由于前面介绍过的原因, 一般应该坚持使用强类型模型对象——必须使所有数据都是强类型数据, 以便视图编写人员能够利用智能感知功能。
可能采用的一个方法是编写自定义的视图模型类。这里的视图模型可以看作仅限于向视图提供信息的模型。注意这里使用的术语“视图模型”不同于Model View ViewModel (MVVM)模型中视图模型的概念。这也是当在讨论视图模型时, 作者倾向于使用“视图特定”的原因所在。
例如, 如果需要一个购物车汇总页面, 用来显示商品列表、购物车中商品的总金额, 以及显示给用户的消息, 就可以创建 ShoppingCarViewModel 类, 如下所示:
public class ShoppingCartViewModel
{
public IEnumerable<Product> Products { get; set; }
public decimal CartTotal { get; set; }
public string Message { get; set; }
}
现在可使用如下的 @model 指令, 向这个模型中强制性地输入一个视图。
@model ShoppingCartViewModel
这就不需要改变Model类的情况下带来的强类型视图的好处, 其中包括类型检查、智能感知以及免于转换无类型的ViewDataDictory对象。
查看购物车视图模型的例子, 在NuGet 中运行:
Install-Package Wrox.ProMvc5.Views.ViewModel
添加视图
虽然手动创建 视图文件, 添加到 Views 目录下, 但VS中的ASP.net MVC工具的Add View 对话框使得创建视图非常容易。
在 HomeController 控制器中添加 Edit 方法:
public ActionResult Edit()
{
return View();
}
下一步, 在操作方法中右击, 选择 Add View 菜单项, 打开
视图名:默认为操作方法名。
模板:一旦选择一个模型类型,就可以选择一个基架模板。这些模板利用 VS 模板系统来生成基于选择模型类型的视图。 图3-6显示了这些模板, 其描述如下表:
基架 | 描述 |
Create | 创建一个视图, 其中带有创建模型新实例的表单,并为模型类型的每一个属性生成一个标签和输入框 |
Delete | 创建一个视图,其中带有删除现有模型实例的表单,并为模型的每一个属性显示一个标签以及该属性的值 |
Details | 创建一个视图,它显示了模型类型的每一个属性的标签及其对应值 |
Edit | 创建一个视图,其中带有编辑现有模型实例的表单。并为模型类型的每一个属性生成一个标签和输入框 |
Empty | 创建一个空视图,使用@model语法指定模型类型 |
Empty(without model) | 同Empty, 但没有模型。 |
List | 创建一个带有模型实例的视图。 为模型类型的每一个属性生成一列。确保操作方法向视图传递的是 IEnumerable<YourModelType> 类型。同时为了执行创建、编辑、删除操作, 视图中还包含了指向操作的链接。 |
引用脚本库:这个选项用来指示要创建的视图是否应该包含指向javascrip库(如果对视图有意义的话)。 默认情况下, _Layout.cshtml 文件既不引用 jQuery Validation 库, 也不引用 Unobtrusive jQuery Validation 库, 只引用主 jQuery 库。
当创建一个包含数据条目表单的视图(如 Edit 视图或 Create 视图)时, 选择这个选项会添加对 jQueryval 捆绑的脚本的引用。如果要实现客户端验证, 那么这些库就是必需的。除了这种情况之外, 完全可以忽略这个复选框。
创建为分布视图:选择这个选项意味着要创建的视图不是一个完整的视图, 因此, Layout选项是不可用的。 生成的部分视图除了在其顶部没有 <html> 和 <head> 标签之外, 很像一个常规的视图。
使用布局页:这个选项决定了要创建的视图是否引用布局, 还是成为一个完全独立的视图。 如果选择使用默认布局, 就没有必要指定一个布局了, 因为在 _ViewStart.cshtml 文件中已经指定了布局。 这个选项是用来重写默认布局文件的。
自定义模板在 16 章中介绍。
Razor 视图引擎
前面的部分介绍了如何在控制器中指定视图以及添加视图。 然而这些内容并没有涉及到在视图中执行的语法。 ASP.net MVC提供了两种不同的引擎:较新的Razor视图引擎和较早的Web Forms 引擎。 本节中只介绍Razor 引擎, 包括Razor语法、布局和部分视图等。
Razor的概念
Razor 视图引擎是 ASP.NET MVC3 中新扩展的内容,并且也是它的默认视图引擎。
代码表达式
Razor中的核心转换字符是“at”符号。 这里有两种基本类型的转换:代码表达式和代码块。
求出表达式的值, 然后将值写入到响应中。
例如:在下面的代码段中:
<h1> Listing @items.Length items.</h1>
注意,此表达式是作为隐式表达式求解的。 这里不需要指出代码表达式的结束位置。 相比之下, Web Form视图只支持显式代码表达式, 这样上面的代码段将是:
<h1> Listing <%= stuff..Length %> items.</h1>
相比之下, Razor 有代码自动回转的功能。
但由此可能出现潜在的二义性。比如:
@{
string rootNamespace="MyApp";
}
<span>@rootNamespace.Models</span>
然而,这样反而出现了错误, 提示string 没有Models属性。在这种边界情况下, Razor诚然不能理解我们的意图,而会认为@rootNamespace.Models是表达式。
这种情况下, 可以用圆括号括起来以支持显式代码表达式:
<span>@(rootNamespace).Models</span>
这样就告知了Razor, .Models 是字面量文本, 而不是代码表达式的一部分。
但对于电子邮件地址, 如: <span>support@message.com</span>
Razor 足够智能, 而不会去处理。
如果有类似电子邮件的情形:<li>Item_@item.Length</li> , 会认为是 Email 原样输出。
希望输出 Item_3 , 则应改为:<li>Item_@(item.Length)</li>
对于 @ 符号, 应该用 @@ 来转义。
<p>
You should follow
@asp.net
</p>
应改为:
<p>
You should follow
@@asp.net
</p>
HTML编码
因为许多情况下都需要用视图显示用户输入。如博客评论或产品评论等,所以总是存在潜在的跨站脚本攻击(也称XSS,这点将在第7章详细介绍)。值得称赞的是Razor表达式用HTML自动编码的。
@{
string message = "<script>alert('haacked!');</script>";
}
<span> @message </span>
这段代码将不会弹出一个警告圣诞框,而会呈现编码的HTML:
<span> <script>alert('haacked!');</script> </span>
如果想展现HTML标记, 就返回一个 System.Web.IHtmlString 对象的实例,Razor并不对它进行编码。
简单来说, 可以用 HtmlRaw 便捷方法:
@{
string message = "<strong>This is bold!</strong>";
}
<span>@Html.Raw(message)</span>
这样就会显示不经过HTML编码的消息:
<span><strong>This is bold!</strong></span>
虽然这种自动的HTML编码通过对以HTML形式显示的用户输入进行编码有效缓和了XSS的脆弱性,但是对于在JavaScript中显示用户输入来说还是不够的。
例如:
<script type="text/javascript">
$(function(){
var message = 'Hello @ViewBag.Username';
$("#message").html(message).show("slow");
});
</script>
尽管在message字符串中对用户名进行了HTML编码,但是仍然具有潜在的XSS脆弱性。例如,如果用户提供以下的字符串作为用户名,HTML将被设置为一个脚本标签。
\x3cscript\x3e%20alert(\x27pwnd\x27)%20\x3c/script\x3e
==>
<script>%20alert('pwnd')%20</script>
当在JavaScript中将用户提供的值赋给变量时, 要使用JavaScript字符串而不仅仅是HTML编码,记住这一点是很重要的。也就是要使用@Ajax.JavaScriptStringEncode方法对用户输入进行编码。下面是使用了这个方法的相同代码,这样就可以有效地避免XSS攻击。
<script type="text/javascript">
$(function(){
var message = 'Hello @Ajax.JavaScriptStringEncode(ViewBag.Username)';
$("#message").html(message).show("slow");
});
</script>
$(function(){
var message = 'Hello @Ajax.JavaScriptStringEncode(ViewBag.Username)';
$("#message").html(message).show("slow");
});
</script>