public class PersonJsonConverter : JsonCreationConverter<Person> { protected override Person Create(Type objectType, JObject jObject) { if (jObject == null) throw new ArgumentNullException("jObject"); if (jObject["schoolName"] != null) { return new Student(); } else if (jObject["hospitalName"] != null) { return new Doctor(); } else { return new Person(); } } }
在这个类中我们复写了Create方法,这里我们使用JObject来获取Json字符串中拥有的属性。
如果字符串中包含schoolName属性,就返回一个新的Student对象
如果字符串中包含hospitalName属性,就返回一个新的Doctor对象
否则,返回一个新Person对象
最后一步,我们在Person类中使用特性标注Person类使用PersonJsonConverter来进行转换Json序列化和反序列化。
[JsonConverter(typeof(PersonJsonConverter))] public class Person { public string FirstName { get; set; } public string LastName { get; set; } }
现在我们重新使用调试模式启动程序, 然后使用Postman请求当前api
我们会发现,people集合中已经正确绑定了的派生子类类型对象,最终Postman上我们得到以下响应结果
[ { "FirstName": "Mike", "LastName": "Li" }, { "SchoolName": "No.15 Middle School", "FirstName": "Stephie", "LastName": "Wang" }, { "HospitalName": "Center Hospital", "FirstName": "Jacky", "LastName": "Chen" } ]
至此多态数据绑定成功。
刨根问底
为什么添加了一个PersonJsonConverter类,多态绑定就实现了呢?
让我们来一起Review一下MVC Core以及Json.NET的代码。
首先我们看一下MvcCoreMvcOptionsSetup代码
public class MvcCoreMvcOptionsSetup : IConfigureOptions<MvcOptions> { private readonly IHttpRequestStreamReaderFactory _readerFactory; private readonly ILoggerFactory _loggerFactory; ...... public void Configure(MvcOptions options) { options.ModelBinderProviders.Add(new BinderTypeModelBinderProvider()); options.ModelBinderProviders.Add(new ServicesModelBinderProvider()); options.ModelBinderProviders.Add(new BodyModelBinderProvider(options.InputFormatters, _readerFactory, _loggerFactory, options)); ...... } ...... }
MvcCoreMvcOptionsSetup类中的Configure方法设置了默认数据绑定使用Provider列表。
当一个api参数被标记为[FromBody]时,BodyModelBinderProvider会实例化一个BodyModelBinder对象来处理这个参数并尝试进行数据绑定。
BodyModelBinder类中有一个BindModelAsync方法,从名字的字面意思上我们很清楚的知道这个方法就是用来绑定数据的。
public async Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } …. 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); } } …… try { var result = await formatter.ReadAsync(formatterContext); …… } catch (Exception exception) when (exception is InputFormatterException || ShouldHandleException(formatter)) { bindingContext.ModelState.AddModelError(modelBindingKey, exception, bindingContext.ModelMetadata); } }
在这个方法中它会尝试寻找一个匹配的IInputFormatter对象来绑定数据,由于这时候请求的Content-Type是application/json, 所以这里会使用JsonInputFormatter对象来进行数据绑定。
下面我们看一下JsonInputFormatter类的部分关键代码
public override async Task<InputFormatterResult> ReadRequestBodyAsync( InputFormatterContext context, Encoding encoding) { ...... using (var streamReader = context.ReaderFactory(request.Body, encoding)) { using (var jsonReader = new JsonTextReader(streamReader)) { … object model; try { model = jsonSerializer.Deserialize(jsonReader, type); } finally { jsonSerializer.Error -= ErrorHandler; ReleaseJsonSerializer(jsonSerializer); } … } } }
JsonInputFormatter类中的ReadRequestBodyAsync方法负责数据绑定, 在该方法中使用了Json.NET的JsonSerializer类的Deserialize方法来进行反序列化, 这说明Mvc Core的底层是直接使用Json.NET来操作Json的。
JsonSerializer类的部分关键代码