• 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/
      

  • 相关阅读:
    matplotlib数据可视化之柱形图
    xpath排坑记
    Leetcode 100. 相同的树
    Leetcode 173. 二叉搜索树迭代器
    Leetcode 199. 二叉树的右视图
    Leetcode 102. 二叉树的层次遍历
    Leetcode 96. 不同的二叉搜索树
    Leetcode 700. 二叉搜索树中的搜索
    Leetcode 2. Add Two Numbers
    Leetcode 235. Lowest Common Ancestor of a Binary Search Tree
  • 原文地址:https://www.cnblogs.com/daizhj/p/1454300.html
Copyright © 2020-2023  润新知