ASP.NET Web API Model-ModelBinder

简介:

ASP.NET Web API Model-ModelBinder

前言

本篇中会为大家介绍在ASP.NET Web APIModelBinder的绑定原理以及涉及到的一些对象模型,还有简单的Model绑定示例,在前面的篇幅中讲解了Model元数据、ValueProvider的模块,然后还有本篇的Model绑定的模块这些会结合到后面篇幅中的ParameterBinder模块中来使用,也就是说在ASP.NET Web API框架中绑定的方式有两种实现,都是通过ParameterBinder来对参数进行绑定,而在ParameterBinder中的实现则会有两种方式,今天就给大家单独的说明一下Model绑定,把它看成一个单独的功能模块就行了。

Model-ModelBinder

不瞎扯了,直接进入主题,首先我们来看IModelBinder接口类型的定义,所有的ModelBinder功能模块都实现了IModelBinder接口,如示例代码1-1


IModelBinder

示例代码1-1

1
2
3
4
5
6
7
namespace  System.Web.Http.ModelBinding
{
     public  interface  IModelBinder
     {
         bool  BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext);
     }
}


在代码1-1中我们可以看到BindModel()方法中定义了两个参数而且都是上下文类型的参数,第一个上下文参数表示操作上下文,它是在控制器方法被执行之前就被创建并且其中封装了一些后续操作必要的信息以及存储请求、响应和参数绑定的结果值,这个稍后会跟大家讲解,它起到一个很重要的作用。

第二个上下文参数是绑定上下文参数,这个容易理解,意思就是对象里封装着本次要绑定对象的信息也就是Model元数据、ValueProvider之类的信息,现在不理解也没关系慢慢往后看看完就会明白的。

 

HttpActionContext

代码1-2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace  System.Web.Http.Controllers
{
     public  class  HttpActionContext
     {
         public  HttpActionContext();
         public  HttpActionContext(HttpControllerContext controllerContext, HttpActionDescriptor actionDescriptor);
 
         public  Dictionary< string object > ActionArguments {  get ; }
         public  HttpActionDescriptor ActionDescriptor {  get set ; }
         public  HttpControllerContext ControllerContext {  get set ; }
         public  ModelStateDictionary ModelState {  get ; }
         public  HttpRequestMessage Request {  get ; }
         public  HttpResponseMessage Response {  get set ; }
     }
}


代码1-2就是HttpActionContext类型的定义了,下面简单的描述一下几个属性所表示的含义,ActionArguments属性中的值是对应着控制器方法的参数列表,其中Key值就是参数名称,Value值就是参数的实际数据值了,因为Model绑定是一个递归的过程在复杂类型的子项绑定完毕后并不会对这个属性进行赋值,而是等这一个参数项全部绑定完成了才会进行赋值。

HttpActionDescriptor类型的ActionDescriptor属性,这个是HttpControllerDescriptor类型之后所见的第二个这种描述类型,后面还会有HttpParameterDescriptor类型,在这里ActionDescriptor属性中就是封装着当前所要请求的控制器方法信息,类似封装着方法的元数据信息。

ControllerContext属性就不用多说了想必大家也都知道它的作用,ModelStateDictionary类型的ModelState属性则是在Model绑定之后才会对其操作,是把参数绑定验证后的值存在这个属性当中。

 

HttpActionContextExtensions

代码1-3

1
2
3
4
5
6
7
8
9
10
11
12
namespace  System.Web.Http.Controllers
{
     // 摘要:
     //     包含 System.Web.Http.Controllers.HttpActionContext 的扩展方法。
     [EditorBrowsable(EditorBrowsableState.Never)]
     public  static  class  HttpActionContextExtensions
     {
         public  static  bool  Bind( this  HttpActionContext actionContext, ModelBindingContext bindingContext);
         public  static  bool  Bind( this  HttpActionContext actionContext, ModelBindingContext bindingContext, IEnumerable<IModelBinder> binders);
         //……
     }
}


代码1-3的所示的是包含HttpActionContext类型的扩展方法类型HttpActionContextExtensions,我们在这之中可以看到两个Bind()方法,这两个Bind()也是Model绑定至关重要的地方,因为Model绑定的递归就是在这里实现的,至于怎么实现的稍后会说。

这里的第一个Bind()方法其实就是调用第二个Bind()方法来执行的。而第二Bind()方法中IEnumerable<IModelBinder>参数则是从HttpActionContext类型中获取到当前的HttpControllerContext并且再从其中获取到当前请求的配置对象HttpConfiguration对象,最后从配置对象中的容器属性中获取ModelBinder的提供程序集合,然后根据当前ModelBindingContext中的ModelType类型使用提供程序集合来判断后获取适合类型的IModelBinder集合,从而调用第二个Bind()方法。

这样看可能还是不太理解递归的情况,大家稍安勿躁,后面慢慢讲解。

 

ModelBindingContext

代码1-4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace  System.Web.Http.ModelBinding
{
     // 摘要:
     //     提供运行模型联编程序的上下文。
     public  class  ModelBindingContext
     {
         // 摘要:
         //     初始化 System.Web.Http.ModelBinding.ModelBindingContext 类的新实例。
         public  ModelBindingContext();
         public  ModelBindingContext(ModelBindingContext bindingContext);
 
         public  bool  FallbackToEmptyPrefix {  get set ; }
         public  object  Model {  get set ; }
         public  ModelMetadata ModelMetadata {  get set ; }
         public  string  ModelName {  get set ; }
         public  ModelStateDictionary ModelState {  get set ; }
         public  Type ModelType {  get ; }
         public  IDictionary< string , ModelMetadata> PropertyMetadata {  get ; }
         public  ModelValidationNode ValidationNode {  get set ; }
         public  IValueProvider ValueProvider {  get set ; }
     }
}


代码1-4中所示的就是绑定上下文对象,首先我们看到它的重载构造函数中有个ModelBindingContext类型的参数用以表示嵌套,内部的实现是用以传递ModelState属性的状态值和ValueProvider值提供程序,至于为什么是这种结构?这个跟绑定复杂类型的时候有关,构造就如同ModelState属性对象的ModelStateDictionary类型一样,这种结构稍后会讲解。

当中的Model属性表示当前ModelBindingContext中绑定过后的Model值,然后ModelMetadataModelNameModelTypePropertyMetadata这些属性都是表示当前ModelBindingContextModel的对应值。这个当前可能是string类型,也可能是复杂类型。(复杂类型在绑定的时候会被ASP.NET Web API框架封装起来有个特定的类型,这个稍后讲解)

 

简单类型绑定器以及绑定器提供程序

 

简单类型绑定器- TypeConverterModelBinder

代码1-5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
     public  sealed  class  TypeConverterModelBinder : IModelBinder
     {
         // Methods
         public  bool  BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
         {
             object  obj2;
             ModelBindingHelper.ValidateBindingContext(bindingContext);
             ValueProviderResult result = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
             if  (result ==  null )
             {
                 return  false ;
             }
             bindingContext.ModelState.SetModelValue(bindingContext.ModelName, result);
             try
             {
                 obj2 = result.ConvertTo(bindingContext.ModelType);
             }
             catch  (Exception exception)
             {
                 if  (IsFormatException(exception))
                 {
                     string  errorMessage = ModelBinderConfig.TypeConversionErrorMessageProvider(actionContext, bindingContext.ModelMetadata, result.AttemptedValue);
                     if  (errorMessage !=  null )
                     {
                         bindingContext.ModelState.AddModelError(bindingContext.ModelName, errorMessage);
                     }
                 }
                 else
                 {
                     bindingContext.ModelState.AddModelError(bindingContext.ModelName, exception);
                 }
                 return  false ;
             }
             ModelBindingHelper.ReplaceEmptyStringWithNull(bindingContext.ModelMetadata,  ref  obj2);
             bindingContext.Model = obj2;
             return  true ;
         }
     }


在代码1-5中,我们看到TypeConverterModelBinder类型实现了IModelBinder接口,并且在BindModel()方法中直接就是使用绑定上下文中的ValueProvider根据绑定上下文中的ModelName属性,ModelName就是我们前面ValueProvider篇幅中讲解到的前缀值和属性值的合并。而后会将获取到的结果值进行类型判断,如果不能正确的转换成string类型则会提示各种异常,当然了这种异常不会报出来,只是添加到了当前绑定上下文的ModelState属性中,如果可以转换成功则会对当前绑定上下文的Model值进行赋值。

 

简单类型绑定器提供程序- TypeConverterModelBinderProvider

代码1-6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
     public  sealed  class  TypeConverterModelBinderProvider : ModelBinderProvider
     {
         // Methods
         public  override  IModelBinder GetBinder(HttpConfiguration configuration, Type modelType)
         {
             if  (modelType ==  null )
             {
                 throw  Error.ArgumentNull( "modelType" );
             }
             if  (!TypeHelper.HasStringConverter(modelType))
             {
                 return  null ;
             }
             return  new  TypeConverterModelBinder();
         }
     }


代码1-6中所示TypeConverterModelBinderProvider类型则为简单类型绑定器的提供程序,并且继承自ModelBinderProvider类型,讲到这里了我才发现我把这个类型忘记说明了,不过没关系,大家自行看一下就好了,ModelBinderProvider就是一个抽象类,然后定义了一个抽象的行为。

TypeConverterModelBinderProvider类型的实现中,我们可以清楚的看到如果参数modelType可以成功的转换成string类型则会返回TypeConverterModelBinder类型的实例,不然则返回null

 

 

复杂类型绑定器(封装器)以及复杂类型绑定器(封装器)提供程序

 

复杂类型封装对象-ComplexModelDto

代码1-7

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace  System.Web.Http.ModelBinding.Binders
{
     // 摘要:
     //     表示一个复杂模型的数据传输对象 (DTO)。
     public  class  ComplexModelDto
     {
         public  ComplexModelDto(ModelMetadata modelMetadata, IEnumerable<ModelMetadata> propertyMetadata);
 
         public  ModelMetadata ModelMetadata {  get ; }
         public  Collection<ModelMetadata> PropertyMetadata {  get ; }
         public  IDictionary<ModelMetadata, ComplexModelDtoResult> Results {  get ; }
     }
}


大家也看到了代码1-7中的注释部分,表示一个复杂模型(Model)的数据传输对象,实际就是对复杂类型的重新封装,封装的方式大家也看到了都是以Model元数据的方式,这个类型我就不多说了。对于Model元数据不太清楚的朋友建议去把前面的篇幅看一下。

 

复杂类型封装器-MutableObjectModelBinder

 

对于MutableObjectModelBinder类型中的实现代码我就不贴了太多了,在这些理论基础都讲完之后后面的篇幅中会有代码示例的。

这里我就用文字来描述一下MutableObjectModelBinder类型所要实现的功能,为什么叫MutableObjectModelBinder为复杂类型封装器呢?因为MutableObjectModelBinder类型它不干绑定的事情,在它执行的时候就一定可以判定当前绑定上下文的Model是一个复杂类型,这个时候MutableObjectModelBinder会根据当前绑定上下文中的Metadata下的Properties属性获取到当前Model下属性的Model元数据列表,并且根据每一项的Model元数据进行筛选,筛选的条件依赖于应用在Model属性上的特性,也就是HttpBindingBehaviorAttribute类型,对于这个类型看完这些之后自己去琢磨吧。

在获取到Model下对应属性的Model元数据集合后,然后创建ComplexModelDto对象实例,并且新建一个绑定上下文把新建的ComplexModelDto对象实例作为ModelModelType这些相关属性,然后最后会调用actionContext.Bind(context);,也就是代码1-3中的HttpActionContext扩展方法进入Model绑定中的递归。

 

复杂类型封装器提供程序- MutableObjectModelBinderProvider

代码1-8

1
2
3
4
5
6
7
8
9
10
11
12
     public  sealed  class  MutableObjectModelBinderProvider : ModelBinderProvider
     {
         // Methods
         public  override  IModelBinder GetBinder(HttpConfiguration configuration, Type modelType)
         {
             if  (!MutableObjectModelBinder.CanBindType(modelType))
             {
                 return  null ;
             }
             return  new  MutableObjectModelBinder();
         }
     }


代码1-8中可以看到在根据类型判断的时候是调用的MutableObjectModelBinder中的静态方法CanBindType(),在CanBindType()方法实现中判断类型不能为ComplexModelDto类型和string类型的,string类型的好理解,因为是属于TypeConverterModelBinder类型来绑定的,ComplexModelDto类型是为了防止框架的处理进入一个死循环,这个看到最后大家就会明白的。

 

 

复杂类型绑定器- ComplexModelDtoModelBinder

代码1-9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
     public  sealed  class  ComplexModelDtoModelBinder : IModelBinder
     {
         // Methods
         public  bool  BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
         {
             ModelBindingHelper.ValidateBindingContext(bindingContext,  typeof (ComplexModelDto),  false );
             ComplexModelDto model = (ComplexModelDto)bindingContext.Model;
             foreach  (ModelMetadata metadata  in  model.PropertyMetadata)
             {
                 ModelBindingContext context =  new  ModelBindingContext(bindingContext)
                 {
                     ModelMetadata = metadata,
                     ModelName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName, metadata.PropertyName)
                 };
                 if  (actionContext.Bind(context))
                 {
                     model.Results[metadata] =  new  ComplexModelDtoResult(context.Model, context.ValidationNode);
                 }
             }
             return  true ;
         }
      }


看这代码1-9中所示类型的名字不用说也是用来对ComplexModelDto对象进行处理的,可以在代码实现中看出来,先把绑定上下文中的Model获取出来转换成ComplexModelDto实例对象,然后遍历其属性PropertyMetadata,根据其每一项的Model元数据创建一个绑定上下文,然后调用actionContext.Bind(context)方法,这也是Model绑定递归的过程之一。这种情况会出现在初始Model类型是复杂类型并且其属性中也有复杂类型。

 

复杂类型绑定器提供程序- ComplexModelDtoModelBinderProvider

 

代码1-10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
     public  override  IModelBinder GetBinder(HttpConfiguration configuration, Type modelType)
     {
         if  (modelType ==  null )
         {
             throw  Error.ArgumentNull( "modelType" );
         }
         if  (!(modelType ==  this .ModelType))
         {
             return  null ;
         }
         if  ( this .SuppressPrefixCheck)
         {
             return  this ._modelBinderFactory();
         }
         return  new  SimpleModelBinder( this );
      }


代码1-10并不是ComplexModelDtoModelBinderProvider类型中本身的实现,而是其本质的实现是SimpleModelBinderProvider类型来完成的,分为检查Model的前缀和不检查两种。这个自行看一下就知道了。

 

我们看下整体的结构图。

1

wKiom1QayEnSqZ0HAAFLFlblcFs621.jpg

当然了还有其他的ModelBinder类型这里就不一一讲解了。最后我们看一下模拟复杂绑定的示意图。

2

wKioL1QayHfiIjorAANhslNAj_g965.jpg

这里的Product是一个复杂类型,其中有三个string类型的属性,执行的顺序为红、蓝、黄。这里要说明的就是除了红色部分进入HttpActionContextExtensions之后不会再次递归,其他蓝色和***部分均有可能,只要碰到有复杂类型。

大概的说明一下流程代码部分在示例篇章会贴出来,首先在Product类型进行绑定的时候会先获取到Product的类型,然后根据当前框架中注册的一系列ModelBinder提供程序进行筛选获取到可以对复杂类型进行绑定的ModelBinder对象,在上图中也就是MutableObjectModelBinder类型,在MutableObjectModelBinder类型处理中会将Product类型的所有属性Model元数据进行封装,封装为ComplexModelDto对象实例,然后MutableObjectModelBinder类型会生成一个ModelBindingContext1对象实例,调用HttpActionContext类型的扩展方法类型HttpActionContextExtensions中的方法Bind()进行Model绑定,在Bind()方法中会重复红色线条流程部分,意思就是说会根据ModelBindingContext1对象实例中的Metadata属性获取到Model类型,刚才我们也说过了Model类型被封装为ComplexModelDto类型了,而后根据这个类型进行筛选获取到ComplexModelDtoModelBinderProvider提供程序,随之生成ComplexModelDtoModelBinder实例,在ComplexModelDtoModelBinder执行Model绑定的处理过程中,会遍历ComplexModelDto类型实例中的每一项属性元数据并且生成对应的ModelBindingContext,在上图也就是ModelBindingContext2以及在ModelBindingContext2执行绑定操作后的ModelBindingContext3。在ModelBindingContext2生成完毕之后会再次的调用HttpActionContext类型的扩展方法类型HttpActionContextExtensions中的方法Bind()进行Model绑定,因为Product中的属性都是string类型所以不存在复杂类型,按照上图中的顺序大家可以看出,如果是复杂类型则会重新执行到红色线条的起始部分。因为这个时候是string类型所以筛选出的提供程序类型为TypeConverterModelBinderProvider,从而生成TypeConverterModelBinder实例为之绑定。






     本文转自jinyuan0829 51CTO博客,原文链接:http://blog.51cto.com/jinyuan/1554989 ,如需转载请自行联系原作者



相关文章
|
1月前
|
JSON API 数据库
解释如何在 Python 中实现 Web 服务(RESTful API)。
解释如何在 Python 中实现 Web 服务(RESTful API)。
23 0
|
2月前
|
IDE Java API
使用Java Web技术构建RESTful API的实践指南
使用Java Web技术构建RESTful API的实践指南
|
2月前
|
存储 开发框架 NoSQL
ASP.NET WEB——项目中Cookie与Session的用法
ASP.NET WEB——项目中Cookie与Session的用法
29 0
|
2月前
|
开发框架 前端开发 .NET
ASP.NET WEB——项目创建与文件上传操作
ASP.NET WEB——项目创建与文件上传操作
46 0
|
1月前
|
XML JSON API
通过Flask框架创建灵活的、可扩展的Web Restful API服务
通过Flask框架创建灵活的、可扩展的Web Restful API服务
|
1月前
|
缓存 监控 API
Python Web框架FastAPI——一个比Flask和Tornada更高性能的API框架
Python Web框架FastAPI——一个比Flask和Tornada更高性能的API框架
57 0
|
1月前
|
JSON API 数据格式
构建高效Python Web应用:Flask框架与RESTful API设计实践
【2月更文挑战第17天】在现代Web开发中,轻量级框架与RESTful API设计成为了提升应用性能和可维护性的关键。本文将深入探讨如何使用Python的Flask框架来构建高效的Web服务,并通过具体实例分析RESTful API的设计原则及其实现过程。我们将从基本的应用架构出发,逐步介绍如何利用Flask的灵活性进行模块化开发,并结合请求处理、数据验证以及安全性考虑,打造出一个既符合标准又易于扩展的Web应用。
628 4
|
1月前
|
存储 PHP 数据库
使用Net2FTP轻松打造免费的Web文件管理器并公网远程访问
使用Net2FTP轻松打造免费的Web文件管理器并公网远程访问
|
2月前
|
前端开发 JavaScript API
前端秘法番外篇----学完Web API,前端才能算真正的入门
前端秘法番外篇----学完Web API,前端才能算真正的入门
|
2月前
|
API 网络架构
解释 RESTful API,以及如何使用它构建 web 应用程序。
解释 RESTful API,以及如何使用它构建 web 应用程序。
87 0