知识点3-4:给视图传递数据
控制器中加工处理好的数据,如何传递给视图?
一、ViewDataDictionary
用来将模型信息传递给视图的主要对象是ViewDataDictionary类,它暴露了一个字典,以使控制器动作能够把任意数目的模型对象和信息传递给视图。借助字典对象,我们可以传递需要的数据到视图。
举个栗子>>:如何扩展留言本网站,使任何人都能查看留言,但只有当前已登录用户能够编辑留言本条目。
目前显示留言本信息,我们可以把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所需的全部信息,但它并未包含当前已登录用户的任何信息,或指示视图是否应该显示Edit链接的信息。这就需要给视图提供更多数据,而不只是GuestbookEntry对象。我们可以使用ViewDataDictionary来提供这种额外的信息片段,如下所示。
public ViewResult Show(int id) { var entry = _db.Entries.Find(id); bool hasPermission = User.Identity.Name == entry.Name; ViewData["hasPermission"] = hasPermission; return View(entry); }
上例检查了当前用户名,将它与留言本条目的Name属性进行比较,并将比较结果放入用hasPermission键表示的ViewData中。接着用重载的View方法创建一个ViewResult对象,并将ViewData的Model属性设置为GuestbookEntry对象。
在视图一侧,我们将取出ViewData中的hasPermission信息,并用它隐藏Edit链接,如下所示。
<h2>Guestbook Entry</h2> <dl> <dt>Name:</dt> <dd>@Model.Name</dd> <dt>Data added:</dt> <dd>@Model.DateAdded</dd> <dt>Message:</dt> <dd>@Model.Message</dd> </dl> <p> @{ bool hasPermission = (bool)ViewData["hasPermission"]; if(hasPermission) { @Html.ActionLink("Edit","Edit", new { id=Model.Id}) } @Html.ActionLink("Back to Entries", "Index") } </p>
在这个视图中,我们从ViewData提取了hasPermission信息。接着,根据hasPermission变量,有条件地显示了Edit链接。最后,显示了一个将用户带回留言本条目列表页面的链接。显示留言本条目的最终渲染页面如下图所示。
虽然ViewDataDictionary非常灵活,但在语法上并不太好用,只要从字典接受数据,就必须执行类型转换。
二、ViewBag
与ViewDataDictionary一样,ViewBag也提供了一种方式,以便将数据从控制器传递给视图,但ViewBag利用了C#4的动态语言特性。它并非使用字符串键在一个字典中存储数据项,你可以在控制器中简单地在这个动态的ViewBag属性上设置若干属性:
ViewBag.HasPermission = hasPermission;
ViewBag属性在视图中也是可用的,因此不必从ViewData接受数据项,并将它转换成Boolean型,我们可以简化视图,直接访问ViewBag:
<p> @if(ViewBag.HasPermission) { @Html.ActionLink("Edit","Edit", new { id=Model.Id}) } @Html.ActionLink("Back to Entries", "Index") } </p>
尽管ViewData和ViewBag这两种动态方法都提供了许多灵活性,但这是有代价的。如果偶然输错了一个动态属性名,这些技术既不能友好重构,编译器也不能测出这类错误。此外,对于动态属性或ViewData,你得不到Visual Studio的智能感应。
另外,你不能方便地将元数据附加到动态属性上。模型验证规则也不能用于动态的ViewBag属性。作为一种可选办法,你可以利用强类型视图,以指示视图能够使用一个特定的已知强类型类。这样可以利用智能感应和Visual Studio的重构工具,并且也可以使用模型验证规则。
三、带有视图模型的强类型视图
在使用基于Razor的视图时,默认情况下,视图继承于两种类型:System.Web.Mvc.WebViewPage或System.Web.Mvc.WebViewPage<T>,继承于WebViewPage的视图称为弱类型视图,继承与WebViewPage<T>的视图为强类型视图。
WebViewPage<T>的定义如下所示。
除了通过Model属性提供对ViewData.Model的强类型封装之外,还对相关的视图辅助器对象AjaxHelper和HtmlHelper的强类型版本提供了访问。为了使用强类型视图,首先必须确保控制器动作适当地设置了ViewData.Model。
练习>>:将Guestbook程序中的Index视图修改为强类型视图。
在清单3.4中,为了显示列表页面,我们接受了留言本的所有条目,并将整个资料集合传递View方法,该方法封装了对ViewData.Model属性的设置。
清单3.4 将留言条目集合传递给视图
public ActionResult Index() { var mostRecentEntries = (from entry in _db.Entries orderby entry.DateAdded descending select entry).Take(20); var model = mostRecentEntries.ToList(); return View(model); }
在与这个动作对应的Index视图中,即使松散类型的WebViewPage类也能够使用这个ViewData.Model属性。但该属性却是object型的,需要对它进行转换才能有效地使用它。与此相反,我们可以通过使用@model关键字,为WebViewPage<T>基类指定模型类型。
@using Guestbook.Models @model List<GuestbookEntry> @{ ViewBag.Title = "查看留言"; } <h2>请给我留言吧</h2> <p> <a href="/Guestbook/Create">添加留言</a> </p> @foreach(var entry in Model) { <section class="contact"> <header> <b>作者:</b>@entry.Name; <b>时间:</b>@entry.DateAdded.ToLongDateString() </header> <p><b>留言内容:</b>@entry.Message</p> </section> <hr /> }
通过使用@model关键字指定模型类型,视图便是继承于WebViewPage<T>,而不是WebViewPage了,于是得到了一个强类型视图。这里还使用了@using关键字,以导入命名空间。
总结>>:从Action取得数据,在ASP.NET MVC可区分成两种方式,一种是“使用弱类型取得数据”,另一种则是“使用强类型取得数据”,两者的差别在于View页面最上方声明的方式。
如果View页面使用弱类型接收来自Controller的数据,在View页面里完全不需要有任何声明,数据可以从ViewData、ViewBag或TempData取得,在页面中也可以通过@Model属性,取得从Action传来的ViewData.Model数据模型,但@Model数据模型的类型将会是object,所以算是弱类型的传值方式。
如果View页面使用强类型方式接收来自Controller的数据,那么,必须在View页面的第一行使用@model关键字引入一个View页面专用的数据模型类型参考。使用这种方式有助于提升View的开发效率,因为可以使用Visual Studio 2012的Intellisense提示功能。