Asp.net MVC 示例项目"Suteki.Shop"分析之---ViewData

        使用强类型的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 ;
        }
}
   
         该抽象类的属性MessageErrorMessage分别实现了IMessageViewDataIErrorViewData的接口属性。主要 用于显示临时操作信息(比如“成功添加用户”,“成功编辑用户”等)。其所提供的两个方法“WithErrorMessage” 和“WithMessage”只是对相应属性的简单绑定而已。
  
        有了ViewDataBase之后,下面就来看一下其子类实现了,下面是相应类图:
    
    
        正如前面所介绍的那样,子类中最“重要”的当属“ShopViewData”,其包括了基本所有Model中的类型,并将它们以“属性”的方法提供出来以便于前台View使用,同时ShopViewData还提供了与其属性相关的绑定方法(均以“With...”开头),下面就是其代码。   
 
Code
public class ShopViewData : ViewDataBase
{
        
public Category Category { getset; }
        
public IEnumerable<Category> Categories { getset; }

        
public Product Product { getset; }
        
public IEnumerable<Product> Products { getset; }

        
public IEnumerable<Role> Roles { getset; }

        
public User User { getset; }
        
public IEnumerable<User> Users { getset; }

        
public Basket Basket { getset; }

        
public Order Order { getset; }
        
public PagedList<Order> Orders { getset; }
        
public OrderSearchCriteria OrderSearchCriteria { getset; }

        
public IEnumerable<Country> Countries { getset; }
        
public Country Country { getset; }

        
public IEnumerable<CardType> CardTypes { getset; }

        
public Postage Postage { getset; }
        
public IEnumerable<Postage> Postages { getset; }

        
public PostageResult PostageResult { getset; }

        
public Card Card { getset; }

        
public IEnumerable<StockItem> StockItems { getset; }

        
public IEnumerable<Content> Contents { getset; }

        
public IEnumerable<OrderStatus> OrderStatuses { getset; }

        
// attempt at a fluent interface

        
public ShopViewData WithCategory(Category category)
        {
            
this.Category = category;
            
return this;
        }

        
public ShopViewData WithCategories(IEnumerable<Category> categories)
        {
            
this.Categories = categories;
            
return this;
        }

        
public ShopViewData WithProduct(Product product)
        {
            
this.Product = product;
            
return this;
        }

        
public ShopViewData WithProducts(IEnumerable<Product> products)
        {
            
this.Products = products;
            
return this;
        }

        
public ShopViewData WithRoles(IEnumerable<Role> roles)
        {
            
this.Roles = roles;
            
return this;
        }

        
public ShopViewData WithUser(User user)
        {
            
this.User = user;
            
return this;
        }

        
public ShopViewData WithUsers(IEnumerable<User> users)
        {
            
this.Users = users;
            
return this;
        }

        
public ShopViewData WithBasket(Basket basket)
        {
            
this.Basket = basket;
            
return this;
        }

        
public ShopViewData WithOrders(PagedList<Order> orders)
        {
            
this.Orders = orders;
            
return this;
        }

        
public ShopViewData WithOrder(Order order)
        {
            
this.Order = order;
            
return this;
        }

        
public ShopViewData WithOrderSearchCriteria(OrderSearchCriteria orderSearchCriteria)
        {
            
this.OrderSearchCriteria = orderSearchCriteria;
            
return this;
        }

        
public ShopViewData WithCountries(IEnumerable<Country> countries)
        {
            
this.Countries = countries;
            
return this;
        }

        
public ShopViewData WithCountry(Country country)
        {
            
this.Country = country;
            
return this;
        }

        
public ShopViewData WithCardTypes(IEnumerable<CardType> cardTypes)
        {
            
this.CardTypes = cardTypes;
            
return this;
        }

        
public ShopViewData WithPostage(Postage postage)
        {
            
this.Postage = postage;
            
return this;
        }

        
public ShopViewData WithPostages(IEnumerable<Postage> postages)
        {
            
this.Postages = postages;
            
return this;
        }

        
public ShopViewData WithTotalPostage(PostageResult postageResult)
        {
            
this.PostageResult = postageResult;
            
return this;
        }

        
public ShopViewData WithCard(Card card)
        {
            
this.Card = card;
            
return this;
        }

        
public ShopViewData WithStockItems(IEnumerable<StockItem> stockItems)
        {
            
this.StockItems = stockItems;
            
return this;
        }

        
public ShopViewData WithContents(IEnumerable<Content> contents)
        {
            
this.Contents = contents;
            
return this;
        }

        
public ShopViewData WithOrderStatuses(IEnumerable<OrderStatus> orderStatuses)
        {
            
this.OrderStatuses = orderStatuses;
            
return this;
        }
}
    
         为了便于使用,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不为空)进行“误覆盖”吧。
   
   
         好了,今天的内容就先到这里了。
    
       
    
    
   
    
    
    
   

你可能感兴趣的:(mvc,职场,休闲,suteki.shop)