使用强类型的ViewData好处有许多,比如说在IDE中就会有更好的支持,比如代码提示。同时在View与Controller之间有更严谨的“约定”。在Suteki.Shop项目中作者对强类型的ViewPage引入是通过MvcContrib实现的,下面就是其ViewPage<T>代码(Suteki.Shop\Views\ViewPage.cs): public class ViewPage < T > : MvcContrib.FluentHtml.ModelViewPage < T > where T : class { public ViewPage() : base ( new LowercaseFirstCharacterOfNameBehaviour()) {} } public class ViewUserControl < T > : MvcContrib.FluentHtml.ModelViewUserControl < T > where T : class { public ViewUserControl() : base ( new LowercaseFirstCharacterOfNameBehaviour()) {} } 可以看出ViewPage和ViewUserControl只是对MvcContrib中ModelViewPage,ModelViewUserControl的继承,代码很简单,没什么太多可说的。 强类型的ViewData使用形如:ViewPage<TViewData>,我们可以通过打开一个View看一下,比如“编辑用户信息”时的视图头声明部分:
<% @ Page Title = "" Language = " C# " Inherits = " Suteki.Shop.ViewPage<ShopViewData> " 其中的ShopViewData就是TViewData。在Suteki.Shop中作者使用ShopViewData对Model中大部分类作了相应的属性和数据绑定的统一封装,感觉ShopViewData就是Model的集合体或者是“缩影”,这样的好处就我看来主要是在View中进行强类型ViewData绑定时统一参数,这里感觉有偷懒之嫌。不过因此造成其自身视图数据的“庸肿”,其内部有太多的属性,还是就是绑定传递时的效率可能也会存在一些问题(只是猜测,未测试过,呵呵)。
好了,下面就开始正文。
首先我们要看一下Suteki.Common\ViewData文件夹下面的几个类,包括:
IErrorViewData,IMessageViewData,ViewDataBase等,其类图如下:
从图中看出,ViewDataBase是其体系“核心”, 其实现了 IMessageViewData, IErrorViewData这两个接口。其实体代码如下: public abstract class ViewDataBase : IMessageViewData, IErrorViewData { public string Message { get ; set ; } public string ErrorMessage { get ; set ; } public ViewDataBase WithErrorMessage( string errorMessage) { this .ErrorMessage = errorMessage; return this ; } public ViewDataBase WithMessage( string message) { this .Message = message; return this ; } } 该抽象类的属性Message,ErrorMessage分别实现了IMessageViewData和IErrorViewData的接口属性。主要 用于显示临时操作信息(比如“成功添加用户”,“成功编辑用户”等)。其所提供的两个方法“WithErrorMessage” 和“WithMessage”只是对相应属性的简单绑定而已。
有了ViewDataBase之后,下面就来看一下其子类实现了,下面是相应类图:
正如前面所介绍的那样,子类中最“重要”的当属“ShopViewData”,其包括了基本所有Model中的类型,并将它们以“属性”的方法提供出来以便于前台View使用,同时ShopViewData还提供了与其属性相关的绑定方法(均以“With...”开头),下面就是其代码。 Code
为了便于使用,Suteki.Shop还以静态属性的方式进行了封闭,最终以ShopView这个类开放出来提供给Action和View使用,其实现代码如下: /// <summary> /// So you can write /// ShopView.Data.WithProducts(myProducts); /// </summary> public class ShopView { public static ShopViewData Data { get { return new ShopViewData(); } } } 下面以“编辑用户”这个Action来看一下其使用方法:
[AcceptVerbs(HttpVerbs.Post), UnitOfWork] public ActionResult Edit([DataBind] User user, string password) { if ( ! string .IsNullOrEmpty(password)) { user.Password = userService.HashPassword(password); } try { user.Validate(); } catch (ValidationException validationException) { validationException.CopyToModelState(ModelState, " user " ); return View( " Edit " , EditViewData.WithUser(user)); } return View( " Edit " , EditViewData.WithUser(user).WithMessage( " Changes have been saved " )); } 注意其中的EditViewData属性就是初始化一个ShopViewData实例并调用该实例的WithRoles()方法来完成对用户规则的获取。然后在"Edit"这个Action的返回语句中继续绑定其他信息,如当前编辑的用户信息“user”,以及操作提示信息“Changes have been saved”。 这样就可以在View中对ShopViewData进行显示操作了。这里要说明的是在View中对Message的显示是通过下面这一行完成的: <% = Html.MessageBox(ViewData.Model) %> 而这个方法是对HtmlHelper这个MVC类的扩展方法,其方法定义如下:
public static string MessageBox( this HtmlHelper htmlHelper, IMessageViewData messageViewData) { if (messageViewData.Message == null ) return string .Empty; HtmlTextWriter writer = new HtmlTextWriter( new StringWriter()); writer.AddAttribute( " class " , " message " ); writer.RenderBeginTag(HtmlTextWriterTag.Div); writer.Write(messageViewData.Message); writer.RenderEndTag(); return writer.InnerWriter.ToString(); } 大家看到了其传入的参数是IMessageViewData类型,而传入的是“ShopViewData”类型,如下图所示: 而看过上面内容的话,就可以通过其类图中实现的方法看出这个继承实现链表: ShopViewData ==> ViewDataBase == > IMessageViewData 所以扩展文法直接就完成了这种“向上转型”操作。
除了“编辑用户”这种在Action中直接绑定Message字段属性的方式,Suteki.Shop还提供了Filter方式的“操作信息”绑定,比如
CopyMessageFromTempDataToViewData(Suteki.Shop\Filters),其代码如下:
public class CopyMessageFromTempDataToViewData : ActionFilterAttribute { public override void OnActionExecuted(ActionExecutedContext filterContext) { var result = filterContext.Result as ViewResult; if (result != null && filterContext.Controller.TempData.ContainsKey( " message " )) { var model = result.ViewData.Model as ShopViewData; if (model != null && string .IsNullOrEmpty(model.Message)) { model.Message = filterContext.Controller.TempData[ " message " ] as string ; } } } } 大家请注意,上面的filterContext.Controller类型是ControllerBase(详细说明参见我之前写的这篇文章),其提供了Message属性来实现临时数据TempData["message"]的获取来绑定工作,代码如下:
[Rescue( " Default " ), Authenticate, CopyMessageFromTempDataToViewData] public abstract class ControllerBase : Controller, IProvidesBaseService { public string Message { get { return TempData[ " message " ] as string ; } set { TempData[ " message " ] = value; } } } 这样就可以通过CopyMessageFromTempDataToViewData这个Filter来实现将临时数据绑定到ShopViewData中的Message属性,并提供给前台View使用了。当然这是有条件的,就是上面代码中的这一行:
if (model != null && string .IsNullOrEmpty(model.Message)) 从逻辑上看,这样做应该是防止对已绑定操作信息(model.Message不为空)进行“误覆盖”吧。
好了,今天的内容就先到这里了。
本文转自 daizhenjun 51CTO博客,原文链接:http://blog.51cto.com/daizhj/160560,如需转载请自行联系原作者