Views 视图   — By Phil Haack

 

本章内容:

  • 视图的作用
  • 如何定义视图
  • 关于强类型视图
  • 理解视图模型
  • 如何新建视图
  • 使用Razor
  • 如何定义Partial(部分)视图
  • 理解视图引擎

对于web应用来说,网站界面是非常重要的,用户往往基于它的第一印像而决定是否继续访问你的网站。在本章中,我们并不会教你怎样制作一个漂亮的视图,我们将着重演示视图的职责及它是如何在ASP.NET MVC中工作的,并教你如何使用工具创建视图以用于你的网站。

 

视图的作用/定义

 

视图的作用在于提供一个UI(用户界面)给用户。它往往与模型相关联,将模型转换成展现给用户的特定格式。从MVC3起,视图数据能够通过ViewBag直接访问。ViewBag提供了一个简便的语法来访问数据。实际上它是基于.net 4的dynamic关键字的,这允许使用属性存取类似的语法从字典中检索值。

ViewBag.Message等价于ViewBag[“Message”]。

这种语法上的区别在技术上并无区分,ViewBag只是一个语法糖,供人选择喜欢的。

值得注意的一点是,视图与ASP.NET Web窗体和PHP不同,它本身并不能直接访问。你不能用浏览器直接访问它。视图是由控制器提供数据并以呈现的。如下代码:

public class HomeController : Controller {
public ActionResult Sample() {
ViewBag.Message = “Hello World. Welcome to ASP.NET MVC!”;
return View(“Sample”);
}
}

Home控制器为ViewBag的Message属性设置了一个string并返回Sample视图(对应Sample.cshtml)。

 

public class HomeController : Controller {
public ActionResult Index() {
ViewBag.Message = “Welcome to ASP.NET MVC!”;
return View();
}
}

上例代码中并没有返回视图名称,这样默认会返回与方法名称对应的视图。(Views/Home/Index.cshtml)

如果你需要返回一个完全不同的目录中的视图。这时您可以使用波浪号语法,以提供完整的路径,如下

public ActionResult Index() {
ViewBag.Message = “Welcome to ASP.NET MVC!”;
return View(“~/Views/Example/Index.cshtml”);
}

使用波浪号语法时,你需要提供视图的文件扩展名,因为这么做实际上是绕过了视图引擎的内部机制来查找视图。

 

强类型视图

 

假设你需要写一个视图用于展示一个专辑列表,一个可行的方法就是简单地把专辑列表丢到ViewBag然后再于视图中遍历出来。

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();
}
<ul>
@foreach (Album p in (ViewBag.Albums as IEnumerable<Album>)) {
<li>@p.Title</li>
}
</ul>

注意在枚举之前你需要先将ViewBag.Albums转成Ienumerable<Album>,你也可以使用dynamic关键字来简化代码,不过这会失去智能提示的帮助。

<ul>
@foreach (dynamic p in ViewBag.Albums) {
<li>@p.Title</li>
}
</ul>

有没有一种办法既能使用dynamic使代码简洁而又能使用智能提示及编译检查呢?答案就是强类型视图。

在Controller方法中,定义模型并通过View方法的重载将其传给视图。

public ActionResult List() {
var albums = new List<Album>();
for (int i = 0; i < 10; i++) {
albums.Add(new Album {Title = “Album “ + i});
}
return View(albums);
}

接下来告诉视图@model是一个什么类型的对象,注意需要提供完整的类型名称。

@model IEnumerable<MvcApplication1.Models.Album>
<ul>
@foreach (Album p in Model) {
<li>@p.Title</li>
}
</ul>

使用using可以避免多次编写冗长的完整类型名。

@using MvcApplication1.Models
@model IEnumerable<Album>
<ul>
@foreach (Album p in Model) {
<li>@p.Title</li>
}
</ul>

还有另一种方法就是在Views目录里面的web.config里面定义,以避免每个视图都要重新定义using。

@using MvcApplication1.Models
<system.web.webPages.razor>

<pages pageBaseType=”System.Web.Mvc.WebViewPage”>
<namespaces>
<add namespace=”System.Web.Mvc” />
<add namespace=”System.Web.Mvc.Ajax” />
<add namespace=”System.Web.Mvc.Html” />
<add namespace=”System.Web.Routing” />
<add namespace=”MvcApplication1.Models” />
</namespaces>
</pages>
</system.web.webPages.razor>

 

 

视图模型

视图往往需要显示一些在模型没有的数据。你可能会想到使用ViewBag,这确实是个办法,不过如前文所述,这并不是一个最好的办法。
比较好的办法是:针对需要显示的数据写一个自定义的视图模型类。视图模型只用于提供视图所需的数据,它是专门为视图而定义的模型。

举个例子,你需要一个购物车结款页面用来显示商品列表,总金额及一条提示信息,你可以创建一个ShoppingCartSummaryViewModel的类,如下:

public class ShoppingCartViewModel {
public IEnumerable<Product> Products { get; set; }
public decimal CartTotal { get; set; }
public string Message { get; set; }
}

接着,你就能够大视图中直接使用该强类型了

@model ShoppingCartSummaryViewModel

 

Partial View(部分视图):与普通视图的区别在于它不包含html,head这些标签,与webforms里面的ascx(用户控件)作用差不多。

 

自定义T4视图模板

在创建强类型视图的时候,你可以指定一个视图脚手架来快速生成该模型的特定类型视图。这些脚手架其实是基于T4模板生成的,你可以在以下目录中找到他们:

[Visual Studio Install Directory]\Common7\IDE\ItemTemplates\[CSharp | VisualBasic]\Web\MVC 3\CodeTemplates\AddView\CSHTML\

对应我的电脑位置:D:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\ItemTemplates\CSharp\Web\MVC 3\CodeTemplates\AddView\CSHTML

你可以修改它们的内容为你想要的,也可以新建一些模板(新建后就可以在添加视图的对话框中的视图脚手架下拉框中找到)。不过修改这些文件将对你本机上的项目产生影响,所以更好的办法是将它们复制到你的项目中进行修改。

最简单的方法就是直接将 CodeTemplates文件夹复制到你的MVC项目根目录,接着将你不需要覆盖掉的模板文件删除掉。

Visual Studio会报如下错误:

Compiling transformation: The type or namespace name
‘MvcTextTemplateHost’ could not be found (are you missing a using
directive or an assembly reference?)

解决方案是选择所有T4文件然后清除他们的Custom Tool属性。

接下来使用视图脚手架的时候,就会以你项目中的T4模板为优先了。

 

Razor 视图引擎

 

Razor提供了一个简化的语法和更具表达的视图,最大限度的减少语法及不必要的字符。许多用过Razor视图的开发者都认为使用Razor编写视图非常流畅,舒服。使用Razor的视图一般以cshtml(C#)或vbhtml(VB)来后缀名。

Razor了解标记的结构,因此它让代码和标记之前的转换尽可能地顺利。看一些例子能让你更好地理解:

@{
// this is a block of code. For demonstration purposes, we’ll
// we’ll create a “model” inline.
var items = new string[] {“one”, “two”, “three”};
}
<html>
<head><title>Sample View</title></head>
<body>
<h1>Listing @items.Length items.</h1>
<ul>
@foreach(var item in items) {
<li>The item name is @item.</li>
}
</ul>
</body>
</html>

"@"标识是用于代码与标记之间相互转换的核心标记。Razor非常聪明,它会自动判断哪里是代码,哪里是标记。但这可能会让你担心在一些情况下是否会产生歧义?

@{
string rootNamespace = “MyApp”;
}
<span>@rootNamespace.Models</span>

如上面的例子我是希望它输出:

<span>MyApp.Models</span>

可实际上却报错了:提示string没有Models这个属性。这是因为Razor无法理解我们的意图,它将@rootNamespace.Models当作成代码表达式的执行了。幸运的是,你可以用括号来明确哪些是代码表达式,以达到你想要的结果。

<span>@(rootNamespace).Models</span>

有些时候你可能需要直接输出@符号(大多数情形下,Razor会自动识别邮件地址),这时你需要对@标识进行解码:

<p>
You should follow
@@haacked, @@jongalloway, @@bradwilson, @@odetocode
</p>

 

Razor表达式是经过Html 编码的。

@{
string message = “<script>alert(‘haacked!’);</script>”;
}
<span>@message</span>

上例将不会弹出脚本对话框,而是输出编码后的信息:

<span>&lt;script&gt;alert(‘haacked!’);&lt;script&gt;</span>

如果你需要直接输出Html,可以使用Html.Raw以避开html编码。

@{
string message = “<strong>This is bold!</strong>”;
}
<span>@Html.Raw(message)</span>

这种自动HTML编码能有效地防止XSS,不过对于在javascript中显示用户输入则是不足的。

<script type=”text/javascript”>
$(
function () {
var message = ‘Hello @ViewBag.Username';
$(“#message”).html(message).show(‘slow’);
});
</script>

在这个代码片段中,用户名来自Razor表达式。尽管已经经过Html编码,可是仍在存在XSS漏洞。如果用户提供了如下的用户名,那么页面就会执行该段脚本:

\x3cscript\x3e%20alert(\x27pwnd\x27)%20\x3c/script\x3e

综上所述,如果js中的变量值是由用户提供的,那么不应该使用html 编码而是使用javascript string encoding,使用@Ajax.JavaScriptStringEncode 对输入进行编码:

<script type=”text/javascript”>
$(
function () {
var message = ‘Hello @Ajax.JavaScriptStringEncode(ViewBag.Username)’;
$(“#message”).html(message).show(‘slow’);
});
</script>

 

除了代码表达式,Razor也支持在视图里面定义代码块

  @foreach(var item in stuff) {<li>The item name is @item.</li>}
@{
string s = “One line of code.”;
ViewBag.Title “Another line of code”;
}
@{Html.RenderPartial(“SomePartial”);}

 

Razor语法示例

结合Web Forms的示例对比,让你更好地理解Razor。

 

  • 代码表达式
Razor <span>@model.Message</span>
Web Forms <span><%: model.Message %></span>




Razor <span>ISBN@(isbn)</span>
Web Forms <span>ISBN<%: isbn %></span>




Razor <span>@Html.Raw(model.Message)</span>
Web Forms<span><%: Html.Raw(model.Message) %></span>or
<span><%= model.Message %></span>
  • 代码块
Razor @{ 
int x = 123;
string y = “because.”;
}
Web Forms <%
int x = 123;
string y = “because.”;
%>




Razor @foreach (var item in items) {
<span>Item @item.Name.</span>
}
Web Forms <% foreach (var item in items) { %>
<span>Item <%: item.Name %>.</span>
<% } %>




Razor @foreach (var item in items) {
<span>Item @item.Name.</span>
}
Web Forms <% foreach (var item in items) { %>
<span>Item <%: item.Name %>.</span>
<% } %>




Razor @if (showMessage) {
<text>This is plain text</text>
}
or
@if (showMessage) {
@:This is plain text.
}
Web Forms <% if (showMessage) { %>
This is plain text.
<% } %>




-转义符-
Razor My Twitter Handle is &#64;hacked
or
My Twitter Handle is @@haacked
Web Forms &lt;% expression %&gt; marks a code 
nugget.


-注释-
Razor @*
This is a multiline server side comment.
@if (showMessage) {
<h1>@ViewBag.Message</h1>
}
All of this is commented out.
*@
Web Forms <%--
This
is a multiline server side comment.
<% if (showMessage) { %>
<h1><%: ViewBag.Message %></h1>
<% } %>
All of this is commented out.
--%>

-通用方法的调用-

Razor @(Html.SomeMethod<AType>())

Web Forms <%: Html.SomeMethod<AType>() %>

 

Layouts(布局)

Razor里面的Layouts用于为视图保持一致的外观和体验。它和Web Frorms里面的母版页目的一样,不过Layouts提供了更为简单的语法及更好的灵活性。

<!DOCTYPE html>
<html>
<head><title>@ViewBag.Title</title></head>
<body>
<h1>@ViewBag.Title</h1>
<div id=”main-content”>@RenderBody()</div>
</body>
</html>

上面就是一个非常简单的Layout,@RenderBody()是一个占位符,它是一个标识,使用该Layout的视图会将自身的内容渲染在标识处。

@{
Layout = “~/Views/Shared/SiteLayout.cshtml”;
View.Title = “The Index!”;
}
<p>This is the main content!</p>

如上视图通过Layout属性来定义使用哪一个布局,当视图被渲染后,视图里面的主要内容就会被替换到Layout里面的ID为main-content的div中。最后生成的html如下:

<!DOCTYPE html>
<html>
<head><title> The Index!</title></head>
<body>
<h1> The Index!</h1>
<div id=”main-content”><p>This is the main content!</p></div>
</body>
</html>

一个布局里面可以有多个不同的section:

<!DOCTYPE html>
<html>
<head><title>@ViewBag.Title</title></head>
<body>
<h1>@ViewBag.Title</h1>
<div id=”main-content”>@RenderBody()</div>
<footer>@RenderSection(“Footer”)</footer>
</body>
</html>

默认情况下,视图必须为每个section提供展示内容,否则会抛出异常。

@{
Layout = “~/Views/Shared/SiteLayout.cshtml”;
View.Title = “The Index!”;
}
<p>This is the main content!</p>
@section Footer {
This is the <strong>footer</strong>.
}

@section标识与Layout里面的section定义相匹配。

@RenderSection方法提供了一个重载方法允许你指定该section是否是必需提供内容的。

<footer>@RenderSection(“Footer”, false)</footer>

你可以为没有定义section的视图提供默认的内容以达到更好的效果:

<footer>
@if (IsSectionDefined(“Footer”)) {
RenderSection(“Footer”);
}
else {
<span>This is the default footer.</span>
}
</footer>

 

注1:(IsSectionDefined的使用)

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title</title>
    @RenderSection("Header", false)
    @RenderSection("Footer", false)
    @if (!IsSectionDefined("Footer"))
    { 
        <span>This is the default footer.</span> 
    }
</head>
<body>
    @RenderBody()
</body>
</html>

在使用IsSectionDefined判断Section Footer是否存在前,必须先RenderSection("Footer"),否则会报如下错误:

以下各节已定义,但尚未为布局页“~/Views/Shared/_Layout.cshtml”呈现:“Footer”。

 

ViewStart

在前面的例子中,每个视图都需要的Layout属性中指定布局,这有时候显得多余并难以维护。_ViewStart.cshtml可以用于解决这个问题,在同一目录里,该文件中的代码将优于其他视图执行。该文件的效果是递归的,作用于所有子目录。

新建的MVC3项目在Views目录默认都会有一个_ViewStart.cshtml。它定义了一个默认的布局。

@{
Layout = “~/Views/Shared/_Layout.cshtml”;
}

由于它的执行顺序优于其他视图,所以视图能够指定Layout属性为别的布局以重载它。ViewStart对了采用同样的布局视图集是非常方便的。

 

定义Partial View

除了返回视图,行为方法还能通过PartialViewResult来返回部分视图。

public class HomeController : Controller {
public ActionResult Message() {
ViewBag.Message = “This is a partial view.”;
return PartialView();
}
}

在这个例子中,名称为Message.cshtml的视图将会被渲染。不过,如果Layout是在_ViewStart.cshtml里面定义的(并且没有在视图中定义Layout),那么布局将不会被渲染出来。

部分视图与普通视图相比只是少了Layout的定义:

<h2>@ViewBag.Message</h2>

这在一些Ajax的场景是非常有用的。

<div id=”result”></div>
<script type=”text/javascript”>
$(
function(){
$(‘#result’).load(‘
/home/message’);
});
</script>

你可以使用NuGet安装示例来看看。

Install-Package Wrox.ProMvc3.Views.SpecifyingViews 

安装完后执行项目并浏览以下URL:

  • /sample/index
  • /sample/index2
  • /sample/index3
  • /sample/partialviewdemo

 

视图引擎

为了更好的理解视图引擎,让我们看下ASP.NET MVC的生命周期(非常简化的)

实际上远不止这么简单,上图只是为了说明视图引擎的出场顺序-控制器方法在响应请求中返回ViewResult之后。值得注意的是,控制器本身并不呈现视图,它只是接收数据并返回ViewResult以决定显示哪一个视图,然后由ViewResult调用视图引擎来渲染视图。

posted on 2012-04-08 18:23  elycir  阅读(1041)  评论(0)    收藏  举报