原文:ASP.NET Core 2.2 十九. 你扔过来个json,我怎么接

  前文说道了Action的激活,这里有个关键的操作就是Action参数的映射与模型绑定,这里即涉及到简单的string、int等类型,也包含Json等复杂类型,本文详细分享一下这一过程。(ASP.NET Core 系列目录

一、概述

  当客户端发出一个请求的时候,参数可能存在于URL中也可能是在请求的Body中,而参数类型也大不相同,可能是简单类型的参数,如字符串、整数或浮点数,也可能是复杂类型的参数,比如常见的Json、XML等,这些事怎么与目标Action的参数关联在一起并赋值的呢?

  故事依然是发生在通过路由确定了被请求的Action之后,invoker的创建与执行阶段(详见Action的执行)。

invoker的创建阶段,创建处理方法,并根据目标Action的actionDescriptor获取到它的所有参数,分析各个参数的类型确定对应参数的绑定方法,

invoker的执行阶段,调用处理方法,遍历参数逐一进行赋值。

为了方便描述,创建一个测试Action如下,它有两个参数,下文以此为例进行描述。:

        public JsonResult Test([FromBody]User user,string note = "FlyLolo")
{
return new JsonResult(user.Code + "|" + user.Name );
}

二、准备阶段

1. 创建绑定方法

 当收到请求后,由路由系统确定了被访问的目标Action是我们定义的Test方法, 这时进入invoker的创建阶段,前文说过它有一个关键属性cacheEntry是由多个对象组装而成(发生在ControllerActionInvokerCache的GetCachedResult方法中),其中一个是propertyBinderFactory:

var propertyBinderFactory = ControllerBinderDelegateProvider.CreateBinderDelegate(_parameterBinder,_modelBinderFactory,_modelMetadataProvider,actionDescriptor,_mvcOptions);

看一下CreateBinderDelegate这个方法:

public static ControllerBinderDelegate CreateBinderDelegate(ParameterBinder parameterBinder,IModelBinderFactory modelBinderFactory,
IModelMetadataProvider modelMetadataProvider, ControllerActionDescriptor actionDescriptor, MvcOptions mvcOptions)
{
//各种验证 略 var parameterBindingInfo = GetParameterBindingInfo(modelBinderFactory, modelMetadataProvider, actionDescriptor, mvcOptions);
var propertyBindingInfo = GetPropertyBindingInfo(modelBinderFactory, modelMetadataProvider, actionDescriptor);
if (parameterBindingInfo == null && propertyBindingInfo == null)
{
return null;
}
return Bind; async Task Bind(ControllerContext controllerContext, object controller, Dictionary<string, object> arguments)
{
//后文详细描述
}
}

前文说过,invoker的创建阶段就是创建一些关键对象和一些用于执行的方法,而propertyBinderFactory 就是众多方法之中的一个,前文介绍它是一个用于参数绑定的Task,而没有详细说明,现在可以知道它被定义为一个名为Bind的Task,最终作为invoker的一部分等待被执行进行参数绑定。

2. 为每个参数匹配Binder

上面的CreateBinderDelegate方法创建了两个对象parameterBindingInfo 和propertyBindingInfo ,顾名思义,一个用于参数一个用于属性。看一下parameterBindingInfo 的创建:

private static BinderItem[] GetParameterBindingInfo(IModelBinderFactory modelBinderFactory,IModelMetadataProvider modelMetadataProvider,ControllerActionDescriptor actionDescriptor, MvcOptions mvcOptions)
{
var parameters = actionDescriptor.Parameters;
if (parameters.Count == )
{
return null;
}
var parameterBindingInfo = new BinderItem[parameters.Count];
for (var i = ; i < parameters.Count; i++)
{
var parameter = parameters[i];
          //略。。。
var binder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext
{
BindingInfo = parameter.BindingInfo,
Metadata = metadata,
CacheToken = parameter,
}); parameterBindingInfo[i] = new BinderItem(binder, metadata);
} return parameterBindingInfo;
}

可以看到parameterBindingInfo 本质是一个BinderItem[]

        private readonly struct BinderItem
{
public BinderItem(IModelBinder modelBinder, ModelMetadata modelMetadata)
{
ModelBinder = modelBinder;
ModelMetadata = modelMetadata;
} public IModelBinder ModelBinder { get; } public ModelMetadata ModelMetadata { get; }
}

通过遍历目标Action的所有参数actionDescriptor.Parameters,根据参数逐一匹配一个对应定的处理对象BinderItem。

如本例,会匹配到两个Binder:

参数 user   ===>  {Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinder}

参数 note  ===>   {Microsoft.AspNetCore.Mvc.ModelBinding.Binders.SimpleTypeModelBinder}

这是如何匹配的呢,系统定义了一系列provider,如下图

                图一

会遍历他们分别与当前参数做匹配:

            for (var i = ; i < _providers.Length; i++)
{
var provider = _providers[i];
result = provider.GetBinder(providerContext);
if (result != null)
{
break;
}
}

同样以这两个Binder为例看一下,BodyModelBinderProvider

        public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
} if (context.BindingInfo.BindingSource != null &&
context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Body))
{
if (_formatters.Count == )
{
throw new InvalidOperationException(Resources.FormatInputFormattersAreRequired(
typeof(MvcOptions).FullName,
nameof(MvcOptions.InputFormatters),
typeof(IInputFormatter).FullName));
} return new BodyModelBinder(_formatters, _readerFactory, _loggerFactory, _options);
} return null;
}

BodyModelBinder的主要判断依据是BindingSource.Body  也就是user参数我们设置了[FromBody]。

同理SimpleTypeModelBinder的判断依据是 if (!context.Metadata.IsComplexType) 。

找到对应的provider后,则会由该provider来new 一个 ModelBinder返回,也就有了上文的BodyModelBinder和SimpleTypeModelBinder。

小结:至此前期准备工作已经完成,这里创建了三个重要的对象:

1. Task Bind() ,用于绑定的方法,并被封装到了invoker内的CacheEntry中。

2. parameterBindingInfo :本质是一个BinderItem[],其中的BinderItem数量与Action的参数数量相同。

3. propertyBindingInfo:类似parameterBindingInfo, 用于属性绑定,下面详细介绍。

                  图二

三、执行阶段

  从上一节的小结可以猜到,执行阶段就是调用Bind方法,利用创建的parameterBindingInfo和propertyBindingInfo将请求发送来的参数处理后赋值给Action对应的参数。

  同样,这个阶段发生在invoker(即ControllerActionInvoker)的InvokeAsync()阶段,当调用到它的Next方法的时候,首先第一步State为ActionBegin的时候就会调用BindArgumentsAsync()方法,如下

        private Task Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
{
switch (next)
{
case State.ActionBegin:
{
              //略。。。
_arguments = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); var task = BindArgumentsAsync();
}

而BindArgumentsAsync()方法会调用上一节创建的_cacheEntry.ControllerBinderDelegate,也就是Task Bind() 方法

        private Task BindArgumentsAsync()
{
// 略。。。

return _cacheEntry.ControllerBinderDelegate(_controllerContext, _instance, _arguments);
}

上一节略了,现在详细看一下这个方法,

            async Task Bind(ControllerContext controllerContext, object controller, Dictionary<string, object> arguments)
{
var valueProvider = await CompositeValueProvider.CreateAsync(controllerContext);
var parameters = actionDescriptor.Parameters; for (var i = ; i < parameters.Count; i++) //遍历参数集和,逐一处理
{
var parameter = parameters[i];
var bindingInfo = parameterBindingInfo[i];
var modelMetadata = bindingInfo.ModelMetadata; if (!modelMetadata.IsBindingAllowed)
{
continue;
} var result = await parameterBinder.BindModelAsync(
controllerContext,
bindingInfo.ModelBinder,
valueProvider,
parameter,
modelMetadata,
value: null); if (result.IsModelSet)
{
arguments[parameter.Name] = result.Model;
}
} var properties = actionDescriptor.BoundProperties;
for (var i = ; i < properties.Count; i++)
//略
}

  主体就是两个for循环,分别用于处理参数和属性,依然是以参数处理为例说明。

  依然是先获取到Action所有的参数,然后进入for循环进行遍历,通过parameterBindingInfo[i]获取到参数对应的BinderItem,这些都准备好后调用parameterBinder.BindModelAsync()方法进行参数处理和赋值。注意这里传入了 bindingInfo.ModelBinder ,在parameterBinder中会调用传入的modelBinder的BindModelAsync方法

modelBinder.BindModelAsync(modelBindingContext);

而这个modelBinder是根据参数匹配的,也就是到现在已经将被处理对象交给了上文的BodyModelBinder、SimpleTypeModelBinder等具体的ModelBinder了。

以BodyModelBinder为例:

        public async Task BindModelAsync(ModelBindingContext bindingContext)
{
//略。。。

var formatterContext = new InputFormatterContext(httpContext,modelBindingKey,bindingContext.ModelState, bindingContext.ModelMetadata, _readerFactory, allowEmptyInputInModelBinding);
var formatter = (IInputFormatter)null;
for (var i = ; i < _formatters.Count; i++)
{
if (_formatters[i].CanRead(formatterContext))
{
formatter = _formatters[i];
_logger?.InputFormatterSelected(formatter, formatterContext);
break;
}
else
{
_logger?.InputFormatterRejected(_formatters[i], formatterContext);
}
}

var result = await formatter.ReadAsync(formatterContext);
            //略。。。
       }

部分代码已省略,剩余部分可以看到,这里像上文匹配provider一样,会遍历一个名为_formatters的集和,通过子项的CanRead方法来确定是否可以处理这样的formatterContext。若可以,则调用该formatter的ReadAsync()方法进行处理。这个_formatters集和默认有两个Formatter, Microsoft.AspNetCore.Mvc.Formatters.JsonPatchInputFormatter} 和  Microsoft.AspNetCore.Mvc.Formatters.JsonInputFormatter , JsonPatchInputFormatter的判断逻辑是这样的

            if (!typeof(IJsonPatchDocument).GetTypeInfo().IsAssignableFrom(modelTypeInfo) ||
!modelTypeInfo.IsGenericType)
{
return false;
}

  它会判断请求的类型是否为IJsonPatchDocument,JsonPatch见本文后面的备注,回到本例,我们经常情况遇到的还是用JsonInputFormatter,此处它会被匹配到。它继承自TextInputFormatter , TextInputFormatter 又继承自 InputFormatter,JsonInputFormatter未重写CanRead方法,采用InputFormatter的CanRead方法。

        public virtual bool CanRead(InputFormatterContext context)
{
if (SupportedMediaTypes.Count == )
{
var message = Resources.FormatFormatter_NoMediaTypes(GetType().FullName, nameof(SupportedMediaTypes));
throw new InvalidOperationException(message);
} if (!CanReadType(context.ModelType))
{
return false;
} var contentType = context.HttpContext.Request.ContentType;
if (string.IsNullOrEmpty(contentType))
{
return false;
}
return IsSubsetOfAnySupportedContentType(contentType);
}

  例如要求ContentType不能为空。本例参数为 [FromBody]User user ,并标识了 content-type: application/json ,通过CanRead验证后,

public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context,Encoding encoding)
{
//略。。。。using (var streamReader = context.ReaderFactory(request.Body, encoding))
{
using (var jsonReader = new JsonTextReader(streamReader))
{
jsonReader.ArrayPool = _charPool;
jsonReader.CloseInput = false;
            //略。。var type = context.ModelType;
var jsonSerializer = CreateJsonSerializer();
jsonSerializer.Error += ErrorHandler;
object model;
try
{
model = jsonSerializer.Deserialize(jsonReader, type);
}             //略。。。
}
}
}

  可以看到此处就是将收到的请求的内容Deserialize,获取到一个model返回。此处的jsonSerializer是 Newtonsoft.Json.JsonSerializer ,系统默认采用的json处理组件是Newtonsoft。model返回后,被赋值给对应的参数,至此赋值完毕。

小结:本阶段的工作是获取请求参数的值并赋值给Action的对应参数的过程。由于参数不同,会分配到一些不同的处理方法中处理。例如本例涉及到的provider(图一)、不同的ModelBinder(BodyModelBinder和SimpleTypeModelBinder)、不同的Formatter等等,实际项目中还会遇到其他的类型,这里不再赘述。

而文中有两个需要单独说明的,在后面的小节里说一下。

四、propertyBindingInfo

上文提到了但没有介绍,它主要用于处理Controller的属性的赋值,例如:

    public class FlyLoloController : Controller
{
[ModelBinder]
public string Key { get; set; }

有一个属性Key被标记为[ModelBinder],它会在Action被请求的时候,像给参数赋值一样赋值,处理方式也类似,不再描述。

五、JsonPatch

上文中提到了JsonPatchInputFormatter,简要说一下JsonPatch,可以理解为操作json的文档,比如上文的User类是这样的:

    public class User
{
public string Code { get; set; }
public string Name { get; set; }
//other ...
}

  现在我只想修改它的Name属性,默认情况下我仍然会需要提交这样的json

{"Code":"001","Name":"张三", .........}

  这不科学,从省流量的角度来说也觉得太多了,用JsonPatch可以这样写

[
{ "op" : "replace", "path" : "/Name", "value" : "张三" }
]

  

ASP.NET Core 2.2 十九. 你扔过来个json,我怎么接的更多相关文章

  1. ASP.NET Core 2.2 十九. Action参数的映射与模型绑定

    前文说道了Action的激活,这里有个关键的操作就是Action参数的映射与模型绑定,这里即涉及到简单的string.int等类型,也包含Json等复杂类型,本文详细分享一下这一过程.(ASP.NET ...

  2. ASP.NET Core 2.2 : 十六.扒一扒新的Endpoint路由方案 try.dot.net 的正确使用姿势 .Net NPOI 根据excel模板导出excel、直接生成excel .Net NPOI 上传excel文件、提交后台获取excel里的数据

    ASP.NET Core 2.2 : 十六.扒一扒新的Endpoint路由方案   ASP.NET Core 从2.2版本开始,采用了一个新的名为Endpoint的路由方案,与原来的方案在使用上差别不 ...

  3. ExpandoObject与DynamicObject的使用 RabbitMQ与.net core(一)安装 RabbitMQ与.net core(二)Producer与Exchange ASP.NET Core 2.1 : 十五.图解路由(2.1 or earler) .NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了

    ExpandoObject与DynamicObject的使用   using ImpromptuInterface; using System; using System.Dynamic; names ...

  4. ASP.NET Core 2.2 : 十六.扒一扒2.2版更新的新路由方案

    原文:ASP.NET Core 2.2 : 十六.扒一扒2.2版更新的新路由方案 ASP.NET Core 从2.2版本开始,采用了一个新的名为Endpoint的路由方案,与原来的方案在使用上差别不大 ...

  5. 学习ASP.NET Core Blazor编程系列九——服务器端校验

    学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 学习ASP.NET Core Blazor编程系 ...

  6. 学习ASP.NET Core Razor 编程系列九——增加查询功能

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  7. ASP.NET Core 2.2 十八.各种Filter的内部处理机制及执行顺序

    ASP.NET core 的Filter是系统中经常用到的,本文详细分享一下各种Filter定义.执行的内部机制以及执行顺序.(ASP.NET Core 系列目录) 一. 概述 ASP.NET Cor ...

  8. ASP.NET Core 2.2 : 十六.扒一扒新的Endpoint路由方案

    ASP.NET Core 从2.2版本开始,采用了一个新的名为Endpoint的路由方案,与原来的方案在使用上差别不大,但从内部运行方式上来说,差别还是很大的.上一篇详细介绍了原版路由方案的运行机制, ...

  9. ASP.NET Core 2.1 : 十五.图解路由(2.1 or earler)

    本文通过一张图来看一下路由的配置以及请求处理的机制.(ASP.NET Core 系列目录) 一.概述 路由主要有两个主要功能: 将请求的URL与已定义的路由进行匹配,找到该URL对应的处理程序并传入该 ...

随机推荐

  1. 在Ubuntu14.04中配置mysql远程连接教程

    上一篇文章,小编带大家学会了在Ubuntu14.04中安装MySQL,没有来得及上课的小伙伴们可以戳这篇文章:如何在Ubuntu14.04中安装mysql,今天给大家分享一下,如何简单的配置MySQL ...

  2. React开发实时聊天招聘工具 -第五章 需求分析

    Axios的使用 axios.get('/data') .then(res=>{ if(res.status==200) this.setState(data:res.data) })

  3. JavaScript笔记(6)

    一.Date Date实例用来处理日期和时间.Date对象基于1970年1月1日(格林威治时间)世界标准时间起经过的毫秒数.常用:new Date();new Date(value);new Date ...

  4. 对比《动手学深度学习》 PDF代码+《神经网络与深度学习 》PDF

    随着AlphaGo与李世石大战的落幕,人工智能成为话题焦点.AlphaGo背后的工作原理"深度学习"也跳入大众的视野.什么是深度学习,什么是神经网络,为何一段程序在精密的围棋大赛中 ...

  5. 【RHEL7/CentOS7网络配置】

    目录 网卡配置文件 查网卡信息 测试网络是否正常 使用 nmtui 命令配置网络 使用 nm-connection-editor 工具配置网络 修改回6.x版本的网卡名 Rhel/CentOS网络配置 ...

  6. Mark Sweep GC

    目录 标记清除算法 标记阶段 深度优先于广度优先 清除阶段 分配 First-fit.Best-fit.Worst-fit三种分配策略 合并 优点 实现简单 与保守式GC算法兼容 缺点 碎片化 分配速 ...

  7. cache基本结构

    下图为direct mapped     set associative        fully associative图示 direct mapped,相当于set number为1 fully ...

  8. bzoj1084【SCOI2005】最大子矩阵

    1084: [SCOI2005]最大子矩阵 Time Limit: 10 Sec  Memory Limit: 162 MB Submit: 1946  Solved: 970 [Submit][id ...

  9. Template template parameter(模板參数) example

    /********************************************************************************* Copyright (C), 19 ...

  10. Java Swing设置主窗体位置居中方法

    01.第一种方法 int windowWidth = frame.getWidth(); //获得窗体宽  int windowHeight = frame.getHeight(); //获得窗体高 ...