zoukankan      html  css  js  c++  java
  • Asp.net MVC 示例项目"Suteki.Shop"分析之ModelBinder

          在Suteki.Shop中,作者构造了一个ModelBinder基类“DataBinder”,其本身继承自IModelBinder
    接口,并以此其类派生出其它一些子类类如ProductBinder等等。可以说除了极个别的地方之外,Data
    Binder被用于了Suteki.Shop大多数的ModelBinder绑定场景之路。

          首先看一下其类图结构:
        
             
          作为基类,DataBinder(图中左下方)实现了将HTTP请求过来的数据转换成为模型中相应的类型。
    其核心方法就是BindModel,下面做一下解释说明:

    public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
                
    object
     entity;

                
    if(declaringAttribute == null ||
     declaringAttribute.Fetch)
                {
                    entity 
    =
     FetchEntity(bindingContext, controllerContext);
                }
                
    else
     
                {
                    entity 
    =
     Activator.CreateInstance(bindingContext.ModelType);
                }        

                
    try

                {
                    validatingBinder.UpdateFrom(entity, controllerContext.HttpContext.Request.Form, bindingContext.ModelState, bindingContext.ModelName);
                }
                
    catch(ValidationException ex) 
                {
                    
    //
    Ignore validation exceptions - they are stored in ModelState. 
                    
    //The controller can access the errors by inspecting the ModelState dictionary.

                }

                
    return
     entity;
        }


         首先要说明的就是declaringAttribute,这个变量其实是DataBindAttribute类型实例,其作用
    是判断当前所请求过来的数据是用于创建新的Model信息还是获取并绑定已有的数据记录。该实例的
    实始化是交给Accept方法来实现的(注意Accept方法的声明是在IAcceptsAttribute接口中定义的):

    public void Accept(Attribute attribute)
    {
         declaringAttribute 
    =
     (DataBindAttribute) attribute;   
    }


     
          而对该方法的调用是放在了BindUsingAttribute的GetBinder()方法中,所以这里要先看一下
    BindUsingAttribute的具体实现: 

    public class BindUsingAttribute : CustomModelBinderAttribute
    {
            
    private readonly
     Type binderType;

            
    public
     BindUsingAttribute(Type binderType)
            {
                
    if(!typeof
    (IModelBinder).IsAssignableFrom(binderType))
                {
                    
    throw new InvalidOperationException("Type '{0}' does not implement IModelBinder."
    .With(binderType.Name));
                }

                
    this.binderType =
     binderType;
            }

            
    public override
     IModelBinder GetBinder()
            {
                var binder 
    =
     (IModelBinder) ServiceLocator.Current.GetInstance(binderType);

                
    if(binder is
     IAcceptsAttribute)
                {
                    ((IAcceptsAttribute)binder).Accept(
    this
    );
                }

                
    return
     binder;
            }
    }


     
          这个属性类也就是在Action中进行ModelBinder绑定时用到的,其类构造方法中通过一个类型参
    数初始化并在GetBinder()方法中使用ServiceLocator来进行最终的ModelBinder实例化构建,注意
    在该方法中有对当前binder的逻辑判断(IAcceptsAttribute),并以此来区别当前Action绑定的Model
    Binder是否实现了IAcceptsAttribute接口。如果没实现或declaringAttribute.Fetch为true时,就
    会在之前的DataBinder类方法“BindModel”中调用“FetchEntity()”方法,FetchEntity会从提交
    的数据中的主键(PrimaryKey)中检索数据并返回该对象实例,代码如下:
     

    private object FetchEntity(ModelBindingContext bindingContext, ControllerContext controllerContext)
    {
            
    object
     entity;
            var primaryKey 
    = bindingContext.ModelType.GetPrimaryKey();//从Shop.dbml中查找相应的主键信息

            string name = bindingContext.ModelName + "." + primaryKey.Name;//拼接出要获取的主键名称

            
    string rawKeyValue = controllerContext.HttpContext.Request.Form[name];

            
    if (string
    .IsNullOrEmpty(rawKeyValue))
            {
                
    throw new InvalidOperationException("Could not find a value named '{0}'"
    .With(name));
            }

            
    int key =
     Convert.ToInt32(rawKeyValue);
            var repository 
    =
     resolver.GetRepository(bindingContext.ModelType);
            entity 
    =
     repository.GetById(key);
            
    return
     entity;
    }

     

         注意上面代码中的这两行:   

        var repository = resolver.GetRepository(bindingContext.ModelType);
        entity 
    = repository.GetById(key);


         其通过bindingContext.ModelType方法获取指定的Model类型的Repository实例(包括该Model类型
    的CRUD方法),并使用接口方法GetById()来获取最终的Model对象。

        下面就是IRepository的接口定义(当然其也定义了泛型接口,后面会提到)。 

    public interface IRepository
     {
         
    object GetById(int
     id);
         IQueryable GetAll();
         
    void InsertOnSubmit(object
     entity);
         
    void DeleteOnSubmit(object
     entity);
         [Obsolete(
    "Units of Work should be managed externally to the Repository."
    )]
         
    void
     SubmitChanges();
     } 

     
         看到这里,我感觉用这种方式设计还是很讨巧的,因为只要每个Model都实现了相应的IRepository
    接口操作类,就可以通过上面两行代码进行调用,这也同时让 FetchEntity获取了最大的稳定性,基本
    上可以做到与具体的Model类解藕,呵呵。

         正如前面所说DataBinder本身就是个基类,其实现了一些默认设置和功能,并以此来简化子类的实
    现代码。

         下面接着看一下其子类的实现,这里以ProductBinder(Suteki.Shop\Binders\ProductBinder.cs)
    为例进行说明,ProductBinder主要实现商的信息更新,包括商品的Size, 图片等等,其实现代码如下:
        

    public class ProductBinder : DataBinder
    {
            
    readonly IRepository<Product>
     repository;
            
    readonly
     IHttpFileService httpFileService;
            
    readonly IOrderableService<ProductImage>
     orderableService;
            
    readonly
     ISizeService sizeService;

            public ProductBinder(IValidatingBinder validatingBinder, IRepositoryResolver resolver, IRepository<Product> repository, IHttpFileService httpFileService, IOrderableService<ProductImage> orderableService, ISizeService sizeService)

     

                : base(validatingBinder, resolver)
            {
                
    this.repository =
     repository;
                
    this.httpFileService =
     httpFileService;
                
    this.orderableService =
     orderableService;
                
    this.sizeService =
     sizeService;
            }

            
    public override object
     BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
            {
                var product 
    = base.BindModel(controllerContext, bindingContext) as
     Product;

                
    if(product != null
    )
                {
                    UpdateImages(product, controllerContext.HttpContext.Request);
                    CheckForDuplicateNames(product, bindingContext);
                    UpdateSizes(controllerContext.HttpContext.Request.Form, product);
                }

                
    return
     product;
            }

            
    void
     UpdateSizes(NameValueCollection form, Product product)
            {
                sizeService.WithValues(form).Update(product);
            }

            
    void
     UpdateImages(Product product, HttpRequestBase request)
            {
                var images 
    =
     httpFileService.GetUploadedImages(request);
                var position 
    =
     orderableService.NextPosition;
                
    foreach (var image in
     images)
                {
                    product.ProductImages.Add(
    new
     ProductImage
                    {
                        Image 
    =
     image,
                        Position 
    =
     position
                    });
                    position
    ++
    ;
                }
            }

            
    void
     CheckForDuplicateNames(Product product, ModelBindingContext bindingContext)
            {
                
    if (!string
    .IsNullOrEmpty(product.Name))
                {
                    
    bool productWithNameAlreadyExists =

                        repository.GetAll().Any(x 
    => x.ProductId != product.ProductId && x.Name == product.Name);

                    
    if
     (productWithNameAlreadyExists)
                    {
                        
    string key = bindingContext.ModelName + ".ProductId"
    ;

                        bindingContext.ModelState.AddModelError(key, "Product names must be unique and there is 

                                    already a product called '{0}'".With(product.Name));

                    }

                }
            }
    }

     

          ProductBinder的构造方法主要是用于进行单元测试,所以就不多做说明了。值得注意的是其
    重写了BindModel方法,这也是我们使用继承方法的精妙所在,可以根据自己的需求“覆盖”已有
    的基类方法,以此实现自己的业务逻辑。当然如果将来ProductBinder下面有子类继承的话,同时
    又要定制自己的BindModel内容时,也可以如法泡制。

         另外就是在上面的CheckForDuplicateNames方法中对于成员 repository的使用,该成员的
    如下:
        

    IRepository<Product>  repository;

        
         其IRepository<>泛型接口的实现目的与IRepository相同,也是为了隔离“变化的需求”,同
    时提高了可扩展性。

        
        下面就是IRepository<>泛型接口的声明:

    public interface IRepository<T> where T : class
    {
        T GetById(
    int id);
        IQueryable
    <T>
     GetAll();
        
    void
     InsertOnSubmit(T entity);
        
    void
     DeleteOnSubmit(T entity);
        [Obsolete(
    "Units of Work should be managed externally to the Repository."
    )]
        
    void
     SubmitChanges();
    }


         除了从DataBinder上继承之外,Suteki.Shop中还有两个与其继承层次相同的ModelBinder,
    们是CurrentBasketBinder(购物车),OrderBinder(定单),而这两个Binder与之前所说的
    DataBinder”的一个区别就是其均未实现IAcceptsAttribute接口。换句话说当使用BindUsing-
    Attribute绑定到Action时,其均不会运行BindUsingAttribute类中GetBinder()方法的“IAcce-
    ptsAttribute.Accept(this)语句”。

        下面是其类图:


         今天的内容看着有点复杂,但其实与前几天所说的Controller,Filter的实现方式都有些相似,
    就是对于MVC所提供的类都是先做一个其类,然后在基类的基础上写新入自己想要的功能代码,这
    种方式应该说是一种常识或是基本功了,好处大家也都能分析的出来,呵呵。


         好了,今天的内容就先到这里了。   
       
         原文链接:
    http://www.cnblogs.com/daizhj/archive/2009/05/20/1454300.html

         作者: daizhj,代震军,LaoD

         Tags: mvc

         网址: http://daizhj.cnblogs.com/
      

  • 相关阅读:
    IE里ActiveXObject的一些实践
    JS兼容性问题汇总
    js:apply/call
    mark 一些近来用过的js库
    (转)时序分析基本概念
    (转载) 使用TimeQuest时序分析器
    (转)Altera对应的时序概念
    有序符号表(二叉树实现,JAVA,算法(四))
    有序符号表(数组实现,JAVA,算法(四),二分法)
    无序符号表(链表实现,JAVA,算法(四))
  • 原文地址:https://www.cnblogs.com/daizhj/p/1454300.html
Copyright © 2011-2022 走看看