西北狼

-- 学而时习之,不亦乐乎!
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

MVC学习之ViewData和ViewModel

Posted on 2009-05-14 13:48  西北老狼  阅读(1671)  评论(2编辑  收藏  举报
ViewData和ViewModel
现在我们将进一步扩展DinnersController,实现丰富表单编辑功能。这里我们讨论2种方法,用来将数据从Controller传递到View:ViewData 和 ViewModel。
 
从Controller传递数据到View视图模板
MVC模式一个典型的特征是严格的功能隔离。Model模型、Controller控制器和View视图各自定义了作用和职责,且相互之间以定义好的方式进行沟通。这有助于提升测试性和代码重用。
当Controller决定呈现HTML响应给客户端是,它负责显式传递给View模板所有需要的数据。View模板从不执行任何数据查询或应用程序逻辑 – 仅仅负责呈现Model或Controller传递过来的数据。
目前,DinnersController控制器传递给View模板的Model模型数据非常简单和直接 – Index
() 方法是Dinner对象列表,Details()、Edit()、Create() 和Delete() 方法则是传递一个Dinner对象。当增加更多UI 特性时,我们经常需要传递更多数据,在视图模板中展示HTML响应。例如,我们需要改变Edit和Create视图中Country 字段(从HTML文本框到下拉列表框)。我们将生成一个动态的、支持的国家列表,而不是在视图模板中硬编码的下拉列表框。我们需要从Controller同时传递Dinner对象和支持的国家列表给View模板。下面看看通过2种方式来实现。
 
使用ViewData 字典
Controller基类公开了一个ViewData 字典属性,用来从Controllers传递额外的数据给Views视图。
例如,为了实现将Edit视图中Country国家的文本框改为下拉列表框,我们更新Edit() Action方法,传入一个SelectList对象(除了Dinner对象外),该对象将作为Country下拉列表框的Model类。
        //
        // GET: /Dinners/Edit/2
        public ActionResult Edit(int id)
        {
            Dinner dinner = dinnerRepository.GetDinner(id);
            ViewData["Countries"] = new SelectList(PhoneValidator.Countries, dinner.Country);
 
            return View(dinner);
        }
上述SelectList 构造函数接收2个参数,第一个是国家列表,添加下拉列表,第二个是当前选择的值。
下面更新Edit.aspx 视图模板,使用Html.DropDownList() 辅助方法代码Html.TextBox() 辅助方法:
<%= Html.DropDownList("Country", ViewData["Countries"] as SelectList)%>
上述Html.DropDownList() 辅助方法接收2个参数,第一个是输出的HTML表单元素的名称,第二个是通过ViewData字典传入的SelectList模型类,必备那个使用C#的关键字as转换dictionary为SelectList。
现在我们在浏览器中访问/Dinners/Edit/2,发现Edit视图模板中Country国家文本框已经更新为下拉列表框了。
 
 
因为我们也会从HTTP POST Edit方法中呈现Edit视图模板(当有错误时,否则会进入Details视图模板),因此我们也需要更新HTTP POST Edit方法,在发生错误进入Edit视图模板时,添加SelectList到ViewData中,代码如下:
        // GET: /Dinners/Edit/2
        public ActionResult Edit(int id)
        {
            Dinner dinner = dinnerRepository.GetDinner(id);
            ViewData["Countries"] = new SelectList(PhoneValidator.Countries, dinner.Country);
 
            return View(dinner);
        }
 
        //
        // POST: /Dinners/Edit/2
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Edit(int id, FormCollection formValues)
        {
            Dinner dinner = dinnerRepository.GetDinner(id);
 
            try
            {
                UpdateModel(dinner);
                dinnerRepository.Save();
                return RedirectToAction("Details", new { id = dinner.DinnerID });
            }
            catch
            {
                ModelState.AddRuleViolations(dinner.GetRuleViolations());
                ViewData["countries"] = new SelectList(PhoneValidator.Countries, dinner.Country);
 
                return View(dinner);
            }
        }
现在DinnersController控制器中Edit方法完全支持下拉列表框了。
 
使用ViewModel模式
ViewData字典方法的优点是非常快和容易实现。部分开发人员不喜欢使用基于字符串的字典(string-based dictionaries),因为一些输入错误会导致错误,但是不能在编译期间发现。在使用View视图模板中使用强类型时,非强类型的ViewData字典也需要使用as操作符或类型转换。
另一个可选的方法是ViewModel模式。当时有这一模式时,我们需要针对特定的View创建强类型的类,公开View模板需要的动态参数值或内容。Controller类接着填充和传递这些类给View模板去使用。这样可以实现类型安全、编译期间检查和编辑器智能提示等等。
例如,针对Dinner的Edit视图,我们创建一个DinnerFormViewModel类,公开了2个强类型的属性:Dinner对象和SelectList模型类(用来填充国家下拉列表框)。
public class DinnerFormViewModel {
// Properties
public Dinner Dinner { get; private set; }
public SelectList Countries { get; private set; }
// Constructor
public DinnerFormViewModel(Dinner dinner) {
Dinner = dinner;
Countries = new SelectList(PhoneValidator.AllCountries,
dinner.Country);
}
}
 
接着,我们更新Edit() Action方法,使用从repository检索到的Dinner对象创建DinnerFormViewModel对象,并传递给视图模板:
//
// GET: /Dinners/Edit/5
public ActionResult Edit(int id) {
Dinner dinner = dinnerRepository.GetDinner(id);
return View(new DinnerFormViewModel(dinner));
}
 
接下来,更新视图模板,在Edit.aspx页面文件中,更改顶部的inherits属性,从
Inherits="System.Web.Mvc.ViewPage<NerdDinner.Models.Dinner>
更改为
Inherits="System.Web.Mvc.ViewPage<NerdDinner.Controllers.DinnerFormViewModel>
 
一旦完成上述操作后,View模板中Model属性的智能提示将更新为传入的DinnerFormViewModel对象模型:
 


 
下面我们需要更新视图中的代码。对于表单中的HTML元素的名称不需要更新,仍旧保持为Title、Country等等,我们需要更新HTML辅助方法,使用DinnerFormViewModel类来获取属性值。
<p>
<label for="Title">Dinner Title:</label>
<%= Html.TextBox("Title", Model.Dinner.Title) %>
<%= Html.ValidationMessage("Title", "*") %>
</p>
<p>
<label for="Country">Country:</label>
<%= Html.DropDownList("Country", Model.Countries) %>
<%= Html.ValidationMessage("Country", "*") %>
</p>
 
同样地,我们也需要更新Edit Post方法,在产生错误时,使用DinnerFormViewModel类传递给视图模板:
//
// POST: /Dinners/Edit/5
[
AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection collection) {
Dinner dinner = dinnerRepository.GetDinner(id);
try {
UpdateModel(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id=dinner.DinnerID });
}
catch {
ModelState.AddModelErrors(dinner.GetRuleViolations());
return View(new DinnerFormViewModel(dinner));
}
}
 
我们也更新Create() Action方法,重用相同的DinnerFormViewModel类,在View中实现Country下拉列表框。下面是HTTP-GET的实现代码:
//
// GET: /Dinners/Create
public ActionResult Create() {
Dinner dinner = new Dinner() {
EventDate = DateTime.Now.AddDays(7)
};
return View(new DinnerFormViewModel(dinner));
}
 
下面是HTTP-POST Create方法的实现代码:
//
// POST: /Dinners/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Dinner dinner) {
if (ModelState.IsValid) {
try {
dinner.HostedBy = "SomeUser";
dinnerRepository.Add(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id=dinner.DinnerID });
}
catch {
ModelState.AddModelErrors(dinner.GetRuleViolations());
}
}
return View(new DinnerFormViewModel(dinner));
}
现在Edit和Create视图都支持通过下列列表框来选择国家了。
 

 
 
定制ViewModel类(Custom-shaped ViewModel Classes
在上面的实现方案中,DinnerFormViewModel类直接公开了2个公有属性:Dinner 模型对象和SelectList模型属性。这一方法适合于View模板中HTML用户界面元素和业务Model对象比较接近的场景。
如果不符合这一情况,可以考虑创建定制的ViewModel类,根据视图的使用情况创建优化的对象模型 – 该对象模型可能完全不同于底层的业务模型对象(Domain Model Object)。例如,该ViewModel类有可能公开不同的属性或者从多个Model对象中汇总的属性。
定制的ViewModel类不仅可用来从Controller传递数据到View去呈现,而且可用来处理从表单提交回来给Controller的action方法的数据。针对后一种情况,你可以让Action方法根据表单提交回来的数据更新ViewModel对象,接着使用ViewModel实例来映射或者获取时间的业务模型对象(Domain Model Object)。
定制ViewModel类提供了很好的灵活性,在任何时候,你发现View模板中的呈现代码或Action方法中表单提交代码越来越开始复杂时,你可以考虑使用定制的ViewModel了。通常,这意味着业务模型对象和View视图中的用户界面元素不一致,一个中介的定制ViewModel类就可以发挥作用了。