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 ,如需转载请自行联系原作者



相关文章
|
21天前
|
存储 消息中间件 前端开发
Web2py框架下的神秘力量:如何轻松集成第三方API,让你的应用不再孤单!
【8月更文挑战第31天】在开发现代Web应用时,常需集成第三方服务如支付网关、数据存储等。本文将指导你使用Web2py框架无缝接入第三方API。通过实例演示从注册获取API密钥、创建控制器、发送HTTP请求到处理响应的全过程。利用`requests`库与Web2py的内置功能,轻松实现API交互。文章详细介绍了如何编写RESTful控制器,处理API请求及响应,确保数据安全传输。通过本教程,你将学会如何高效整合第三方服务,拓展应用功能。欢迎留言交流心得与建议。
30 1
|
30天前
|
监控 前端开发 Serverless
现代化 Web 应用构建问题之观测站点的PV、UV和API异常等指标如何解决
现代化 Web 应用构建问题之观测站点的PV、UV和API异常等指标如何解决
29 2
|
21天前
|
API C# 开发框架
WPF与Web服务集成大揭秘:手把手教你调用RESTful API,客户端与服务器端优劣对比全解析!
【8月更文挑战第31天】在现代软件开发中,WPF 和 Web 服务各具特色。WPF 以其出色的界面展示能力受到欢迎,而 Web 服务则凭借跨平台和易维护性在互联网应用中占有一席之地。本文探讨了 WPF 如何通过 HttpClient 类调用 RESTful API,并展示了基于 ASP.NET Core 的 Web 服务如何实现同样的功能。通过对比分析,揭示了两者各自的优缺点:WPF 客户端直接处理数据,减轻服务器负担,但需处理网络异常;Web 服务则能利用服务器端功能如缓存和权限验证,但可能增加服务器负载。希望本文能帮助开发者根据具体需求选择合适的技术方案。
56 0
|
21天前
|
API 网络安全 数据库
Web2py框架如何颠覆传统的RESTful API开发?掌握这些技巧,让你的开发效率飞跃!
【8月更文挑战第31天】Web2py是一款全栈Python Web框架,适用于快速开发复杂交互的Web应用。本文将介绍如何使用Web2py创建RESTful API,包括设置新控制器、定义RESTful路由、处理数据库交互、确保API安全性、编写文档与使用Swagger、测试API以及部署时的注意事项。Web2py的高度抽象和易用性使其成为实现RESTful API的理想选择,帮助开发者专注于业务逻辑而非技术细节。
21 0
|
28天前
|
移动开发 数据挖掘 API
HTML5 中 Web Workers API 的用法
【8月更文挑战第24天】
34 0
|
28天前
|
API
【Azure API 管理】在 Azure API 管理中使用 OAuth 2.0 授权和 Azure AD 保护 Web API 后端,在请求中携带Token访问后报401的错误
【Azure API 管理】在 Azure API 管理中使用 OAuth 2.0 授权和 Azure AD 保护 Web API 后端,在请求中携带Token访问后报401的错误
|
1月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
31 0
|
1月前
|
存储 开发框架 .NET
ASP.NET Web Api 使用 EF 6,DateTime 字段如何取数据库服务器当前时间
ASP.NET Web Api 使用 EF 6,DateTime 字段如何取数据库服务器当前时间
|
1月前
|
开发框架 .NET API
如何在 ASP.NET Core Web Api 项目中应用 NLog 写日志?
如何在 ASP.NET Core Web Api 项目中应用 NLog 写日志?
|
Web App开发 JavaScript 前端开发
ASP.NET MVC Web API 学习笔记---第一个Web API程序
1. Web API简单说明 近来很多大型的平台都公开了Web API。比如百度地图 Web API,做过地图相关的人都熟悉。公开服务这种方式可以使它易于与各种各样的设备和客户端平台集成功能,以及通过在浏览器中使用 JavaScript来创建更丰富的HTML体验。
1136 0