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



相关文章
|
19天前
|
开发框架 前端开发 JavaScript
ASP.NET Web Pages - 教程
ASP.NET Web Pages 是一种用于创建动态网页的开发模式,采用HTML、CSS、JavaScript 和服务器脚本。本教程聚焦于Web Pages,介绍如何使用Razor语法结合服务器端代码与前端技术,以及利用WebMatrix工具进行开发。适合初学者入门ASP.NET。
|
2月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
58 4
|
2月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
159 3
|
10天前
|
Kubernetes 安全 Devops
有效抵御网络应用及API威胁,聊聊F5 BIG-IP Next Web应用防火墙
有效抵御网络应用及API威胁,聊聊F5 BIG-IP Next Web应用防火墙
36 10
有效抵御网络应用及API威胁,聊聊F5 BIG-IP Next Web应用防火墙
|
22天前
|
开发框架 .NET 程序员
驾驭Autofac,ASP.NET WebApi实现依赖注入详细步骤总结
Autofac 是一个轻量级的依赖注入框架,专门为 .NET 应用程序量身定做,它就像是你代码中的 "魔法师",用它来管理对象的生命周期,让你的代码更加模块化、易于测试和维护
驾驭Autofac,ASP.NET WebApi实现依赖注入详细步骤总结
|
19天前
|
开发框架 .NET PHP
ASP.NET Web Pages - 添加 Razor 代码
ASP.NET Web Pages 使用 Razor 标记添加服务器端代码,支持 C# 和 Visual Basic。Razor 语法简洁易学,类似于 ASP 和 PHP。例如,在网页中加入 `@DateTime.Now` 可以实时显示当前时间。
|
1月前
|
前端开发 API 开发者
Python Web开发者必看!AJAX、Fetch API实战技巧,让前后端交互如丝般顺滑!
在Web开发中,前后端的高效交互是提升用户体验的关键。本文通过一个基于Flask框架的博客系统实战案例,详细介绍了如何使用AJAX和Fetch API实现不刷新页面查看评论的功能。从后端路由设置到前端请求处理,全面展示了这两种技术的应用技巧,帮助Python Web开发者提升项目质量和开发效率。
52 1
|
1月前
|
JSON API 数据格式
如何使用Python和Flask构建一个简单的RESTful API。Flask是一个轻量级的Web框架
本文介绍了如何使用Python和Flask构建一个简单的RESTful API。Flask是一个轻量级的Web框架,适合小型项目和微服务。文章从环境准备、创建基本Flask应用、定义资源和路由、请求和响应处理、错误处理等方面进行了详细说明,并提供了示例代码。通过这些步骤,读者可以快速上手构建自己的RESTful API。
61 2
|
2月前
|
监控 负载均衡 API
Web、RESTful API 在微服务中有哪些作用?
在微服务架构中,Web 和 RESTful API 扮演着至关重要的角色。它们帮助实现服务之间的通信、数据交换和系统的可扩展性。
54 2
|
2月前
|
人工智能 搜索推荐 API
用于企业AI搜索的Bocha Web Search API,给LLM提供联网搜索能力和长文本上下文
博查Web Search API是由博查提供的企业级互联网网页搜索API接口,允许开发者通过编程访问博查搜索引擎的搜索结果和相关信息,实现在应用程序或网站中集成搜索功能。该API支持近亿级网页内容搜索,适用于各类AI应用、RAG应用和AI Agent智能体的开发,解决数据安全、价格高昂和内容合规等问题。通过注册博查开发者账户、获取API KEY并调用API,开发者可以轻松集成搜索功能。
下一篇
DataWorks