依然是先获取到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 = 0; 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 == 0) { 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验证后,
1.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" : "张三" } ]