边看边译《asp.net mvc 4 in action》(一)
最近正在看一本书《asp.net mvc 4 in action》,写的不错,想着一边看一边翻译出来,这样可以一句一句仔细看,以加深理解。
第三章 视图基础
视图是asp.net mvc应用程序的主要部分——它们提供了一种清晰的方式来分割表现层和逻辑层。在上一章,我们简略地看到我们的留言板程序用Razor模版引擎编写的一些简单视图,在章节的最后,我们还看到如何在一个程序里用布局方式来使所有页面都拥有一个一致的外观感受。
在这一章,我们会更加深入的探讨视图——我们将检验asp.net mvc如何呈现视图并且传递数据到视图的各种选项。最后,我们将揭示最初在asp.net mvc 2中介绍的模版功能。为了展示这些功能,我们将给留言板程序增加一个编辑页面。
3.1 视图介绍
视图的职责非常简单。它们存在的意义就是利用传递给它的模型把内容展现出来。因为控制器及其相关的服务已经执行完了所有的业务逻辑,并把结果打包成了一个模型对象,视图只需要知道如何使用模型并把它转换成HTML。
虽然这种关注点分离减少了传统asp.net程序所有的令人烦恼的大部分职责,但是视图仍然需要仔细慎重的设计,以确保视图不会变的复杂和难以维护。
在介绍传递数据到视图的不同方式之前,先让我们考察一下mvc框架是如何决定视图被呈现的。
3.1.1 选择要呈现的视图
在第2章你已经看见通过在控制器的动作里调用View方法来呈现视图。以下是GuestbookController里Create动作:
Public ActionResult Create()
{
Return View();
}
在这里,Views/Guestbook/Create.cshtml视图文件被呈现出来。但是mvc框架是如何知道呈现这个特定的视图而不是其他的视图(如Index.cshtml)呢?
调用View方法返回的ViewResult对象知道如何呈现特定的视图。当不带参数调用这个方法时,框架推断出要呈现的视图名称应该和动作(Create)的名称一致。在以后的mvc管道中,框架的ControllerActionInvoker类执行ViewResult,告诉它呈现视图。这时候,框架请求ViewEngineCollection定位适当的视图来呈现。(正如你在第2章看到过的,视图引擎默认是在Views/<controller name>目录和Views/Shared目录里查找视图)。
视图引擎
不同的视图引擎用不同的格式呈现视图。默认的,asp.net mvc有两个视图引擎——RazorViewEngine和WebFormViewEngine。Razor引擎用Razor格式呈现视图(或者是.cshtml文件或者是.vbhtml文件),而Web Form引擎是用来支持旧的Web Form视图(.aspx和.ascx文件)。以前的asp.net mvc默认只包括Web Form引擎。
为什么asp.net mvc 3要增加一个新的引擎?从asp.net 1.0版开始,Web Form允许代码和标记符共同存在于aspx页面里。然而,通常的开发实践不鼓励把原生的c#逻辑代码放到aspx文件里。相反,开发者努力不所有逻辑代码放在code-behind里。贯穿所有asp.net版本的进步是在aspx文件里包括更好的数据绑定和其他更多的是针对开发的。
在各种mvc框架里,鼓励和要求直接使用标记符编写视图。因为aspx视图引擎不是为此目的设计的,所以asp.net团队决定建立一个完整的新的带有code-focused模版方法的视图引擎。结果,一个更智能的分析引擎出现了,不用开发者明确说明就能非常容易的计算出代码在哪里结束,标记符在哪里开始。
加入其它的视图引擎也是可能的,因此你可以使用第三方的格式呈现视图。在第10章里我们使用流行的开源Spark视图引擎来呈现视图。
3.1.2 重新定义视图名称
如果愿意,你可以重新定义动作名称和视图名称相同的约定。例如,视图名称是New.cshtm而不是Create.cshtml,你调用View方法的第二个重载,它接受一个视图名称:
Return View(“New”);
作为选择,如果视图与相同名称的控制器不在同一个子目录下,你可以指定相对于应用程序的路径:
Return View(“~/Views/SomeotherDirectory/New.cshtml”);
视图只呈现本身不是很有用——通常我们想给视图传递一些数据来起一些作用。接下来的部分,我们会看到为此目的一些不同的方法。
3.2 传递数据到视图
在留言板应用程序里,我们已经看到一种传递GuestbookEntry对象集合到视图的方法。在这部分,我们会看到三种不同的方法,分别是用ViewDataDictionary,ViewBag和强类型视图。
3.2.1 ViewDataDictionary
用来传递模型信息到视图的主要对象是ViewDataDictionary类。就像其他mvc框架,asp.net mvc暴露一个字典使控制器活动能传递任何数量的模型对象和信息给视图。随着字典对象,我们能传递适当呈现视图需要的大量的数据。
例如,让我们看看如何扩展留言板页面,以便任何人都能查看留言板,但只有当前登录的用户能编辑留言板记录。为了在留言板记录详细页面显示留言信息,我们直接把GuestbookEntry对象传递到视图,下面是GuestbookEntry对象:
public class GuestbookEntry
{
public int Id { get; set; }
public string Name { get; set; }
public string Message { get; set; }
public DateTime DateAdded { get; set; }
}
虽然GuestbookEntry类有所有要显示的信息,但是不包括当前登陆者或是否显示编辑链接的任何信息。我们需要给视图比GuestbookEntry对象更多的信息。我们可以使用ViewDataDictionary来提供额外的信息,如下显示:
清单3.1 控制器的Show活动
public ViewResult Show(int id)
{
var entry = _db.Entries.Find(id);
bool hasPermission = User.Identity.Name ==entry.Name;
ViewData["hasPermission"] =hasPermission;
return View(entry);
}
在控制器基类里,我们可以利用在ViewData属性里要传递到视图的ViewDataDictionary对象。我们检查当前用户的名称,比较它和留言记录里所示的Name属性,把比较的结果放进ViewData的hasPermission键里。接着,我们用View方法创建一个ViewResult对象,设置ViewData的Model属性为GuestbookEntry对象。
在视图上,我们从ViewData里取出hasPermission信息,用它来隐藏编辑链接。
清单3.2 用ViewData信息隐藏一个链接
<p>
@{
bool hasPermission =(bool) ViewData["hasPermission"];
}
@if (hasPermission)
{
@Html.ActionLink("Edit","Edit",new {id = Model.Id})
}
@Html.ActionLink("Back toEntries", "Index")
</p>
在视图里,我们从ViewData里提取hasPermission信息。接着,我们基于hasPermission变量有条件的显示编辑链接。最后,我们给用户显示一个回到留言板记录列表页面的链接。最后呈现的页面见图3.1.
图3.1 留言板条目详细页
虽然ViewDataDictionary非常方便(你可以在里边存任何东西),但在句法上并不是很好——你不得不执行类型转换在从字典里获取数据时。Asp.net mvc包括一个在ViewData里动态存储数据的变通的方法——ViewBag。
3.2.2 ViewBag
就像ViewDataDictionary,viewBag提供一种从控制器传递数据到视图的方法,但是ViewBag利用了c# 4里边动态语言的特点。取代在字典里用字符串键存储数据,你可以在控制器里的动态ViewBag属性上简单地设置它:
ViewBag.HasPermission = hasPermission;
ViewBag的属性在视图里也是有效的,于是取代从ViewData里获取数据并转换成布尔类型,我们可以直接访问ViewBag:
<p>
@if (ViewBag.HasPermission)
{
@Html.ActionLink("Edit","Edit", new {id = Model.Id})
}
@Html.ActionLink("Back to Entries", "Index")
</p>
虽然ViewData和ViewBag都提供了极大地方便,但也带来了成本。这些技术既对代码重构不友好,也不能在编译时发现你偶尔键入动态属性名称时的错误。另外,Visual Studio对动态特性或ViewData没有智能提示(虽然第三方工具提供这些如JetBrains ReSharper)。
此外,你不容易把元数据附加到动态属性上。Asp.net mvc使用特性(attributes)附加元数据给特定类型(例如,在System.ComponentModel.DataAnnotations名字空间里的验证特性能被用于标记一个域是必须的,或者一个域的最大长度)。这些特性不能与动态ViewBag属性一起使用。
作为一个选择方案,可以使用强类型视图,强类型视图表示一个视图使用了一个特定已知的强类型类。用这种方式,你仍然可以利用智能提示和Visual Studio的反射工具,还能受益于使用特性驱动的元数据。我们将在下一部分看到这些是如何工作的。