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

简介:
在Suteki.Shop中,作者构造了一个ModelBinder基类“DataBinder”,其本身继承自IModelBinder接口,并以此其类派生出其它一些子类类如ProductBinder等等。可以说除了极个别的地方之外,DataBinder被用于了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绑定的ModelBinder是否实现了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所提供的类都是先做一个其类,然后在基类的基础上写新入自己想要的功能代码,这种方式应该说是一种常识或是基本功了,好处大家也都能分析的出来,呵呵。

         好了,今天的内容就先到这里了。    


本文转自 daizhenjun 51CTO博客,原文链接:http://blog.51cto.com/daizhj/159381,如需转载请自行联系原作者
相关文章
|
设计模式 前端开发 Java
Spring MVC——项目创建和建立请求连接
MVC是一种软件架构设计模式,将应用分为模型、视图和控制器三部分。Spring MVC是基于MVC模式的Web框架,通过`@RequestMapping`等注解实现URL路由映射,支持GET和POST请求,并可传递参数。创建Spring MVC项目与Spring Boot类似,使用`@RestController`注解标记控制器类。
268 1
Spring MVC——项目创建和建立请求连接
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
443 5
|
存储 前端开发 数据可视化
在实际项目中,如何选择使用 Flux 架构或传统的 MVC 架构
在实际项目中选择使用Flux架构或传统MVC架构时,需考虑项目复杂度、团队熟悉度和性能需求。Flux适合大型、高并发应用,MVC则适用于中小型、逻辑简单的项目。
Visual Studio 快速分析 .NET Dump 文件
【11月更文挑战第10天】.NET Dump 文件是在 .NET 应用程序崩溃或出现问题时生成的,记录了应用程序的状态,包括内存对象、线程栈和模块信息。通过分析这些文件,开发人员可以定位和解决内存泄漏、死锁等问题。在 Visual Studio 中,可以通过调试工具、内存分析工具和符号加载等功能来详细分析 Dump 文件。此外,还可以使用第三方工具如 WinDbg 进行更深入的分析。
1372 1
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
274 2
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
487 7
|
存储 运维
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
532 2
|
存储 开发框架 前端开发
ASP.NET MVC 迅速集成 SignalR
ASP.NET MVC 迅速集成 SignalR
401 0
|
开发框架 缓存 .NET
【App Service】在Azure App Service中分析.NET应用程序的性能的好帮手(Review Stack Traces)
【App Service】在Azure App Service中分析.NET应用程序的性能的好帮手(Review Stack Traces)
247 0
|
Linux C# C++
【Azure App Service For Container】创建ASP.NET Core Blazor项目并打包为Linux镜像发布到Azure应用服务
【Azure App Service For Container】创建ASP.NET Core Blazor项目并打包为Linux镜像发布到Azure应用服务
255 0