首页 > 网络编程 > ASP.NET > 正文

.NET Core WebApi中如何实现多态数据绑定实例代码_实用技巧

2018-10-12 16:22:12

什么是.NET Core?

随着2014年 Xamarin和微软发起.NET基金会,微软在2014年11月份 开放.NET框架源代码。在.NET开源基金会的统一规划下诞生了.NET Core 。也就是说.NET Core Framework是参考.NET Framework重新开发的.NET实现,Mono是.NET Framework的一个开源的、跨平台的实现。

本文主要介绍了关于.NET Core WebApi多态数据绑定的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧

什么是多态数据绑定?

我们都知道在ASP.NET Core WebApi中数据绑定机制(Data Binding)负责绑定请求参数, 通常情况下大部分的数据绑定都能在默认的数据绑定器(Binder)中正常的进行,但是也会出现少数不支持的情况,例如多态数据绑定。所谓的多态数据绑定(polymorphic data binding),即请求参数是子类对象的Json字符串, 而action中定义的是父类类型的变量,默认情况下ASP.NET Core WebApi是不支持多态数据绑定的,会造成数据丢失。

以下图为例

Person类是一个父类,Doctor类和Student类是Person类的派生类。Doctor类中持有的HospitalName属性,Student中持有的SchoolName属性。

接下我们创建一个Web Api项目并添加一个PeopleController。

在PeopleController中我们添加一个Add api,并将请求数据直接返回,以便查看效果。

[Route("api/people")]public class PeopleController : Controller{ [HttpPost] [Route("")] public List<Person> Add([FromBody]List<Person> people) { return people; }}

这里我们使用Postman请求这个api, 请求的Content-Type是application/json, 请求的Body内容如下。

[{ firstName: 'Mike', lastName: 'Li'}, { firstName: 'Stephie', lastName: 'Wang', schoolName: 'No.15 Middle School'}, { firstName: 'Jacky', lastName: 'Chen', hospitalName: 'Center Hospital'}]

请求的返回内容

[ { "FirstName": "Mike", "LastName": "Li" }, { "FirstName": "Stephie", "LastName": "Wang" }, { "FirstName": "Jacky", "LastName": "Chen" }]

返回结果和我们希望得到的结果不太一样,Student持有的SchoolName属性和Doctor持有的HospitalName属性都丢失了。

现在我们启动项目调试模式,重新使用Postman请求一次,得到的结果如下

People集合中存放3个People类型的对象, 没有出现我们期望的Student类型对象和Doctor类型对象,这说明.NET Core WebApi默认是不支持多态数据绑定的,如果使用父类类型变量来接收数据,Data Binding只会实例化父类对象,而非一个派生类对象, 从而导致属性丢失。

自定义JsonConverter来实现多态数据绑定

JsonConverter是Json.NET中的一个类,主要负责Json对象的序列化和反序列化。

首先我们创建一个泛型类JsonCreationConverter,并继承了JsonConverter类,代码如下:

public abstract class JsonCreationConverter<T> : JsonConverter{ public override bool CanWrite { get {  return false; } } protected abstract T Create(Type objectType, JObject jObject); public override bool CanConvert(Type objectType) { return typeof(T).IsAssignableFrom(objectType); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader == null) throw new ArgumentNullException("reader"); if (serializer == null) throw new ArgumentNullException("serializer"); if (reader.TokenType == JsonToken.Null)  return null; JObject jObject = JObject.Load(reader); T target = Create(objectType, jObject); serializer.Populate(jObject.CreateReader(), target); return target; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); }}

其中,我们加入了一个抽象方法Create,这个方法会负责根据Json字符串的内容,返回一个泛型类型对象,这里既可以返回一个当前泛型类型的对象,也可以返回一个当前泛型类型派生类的对象。JObject是Json.NET中的Json字符串读取器,负责读取Json字符串中属性的值。

另外我们还复写了ReadJson方法,在ReadJson中我们会先调用Create方法获取一个当前泛型类对象或者当前泛型类的派生类对象(Json.NET中默认的KeyValuePairConverter会直接实例化当前参数类型对象,这也就是默认不支持多态数据绑定的主要原因),serializer.Popluate方法的作用是将Json字符串的内容映射到目标对象(当前泛型类对象或者当前泛型类的派生类对象)的对应属性。

这里由于我们只需要读取Json, 所以WriteJson的方法我们不需要实现,CanWrite属性我们也强制返回了False。

第二步,我们创建一个PersonJsonConverter类,它继承了JsonCreationConverter<Person>, 其代码如下

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类的部分关键代码

public object Deserialize(JsonReader reader, Type objectType){ return DeserializeInternal(reader, objectType);}internal virtual object DeserializeInternal(JsonReader reader, Type objectType){ …… JsonSerializerInternalReader serializerReader = new JsonSerializerInternalReader(this); object value = serializerReader.Deserialize(traceJsonReader ?? reader, objectType, CheckAdditionalContent); …… return value;}

JsonSerializer会调用JsonSerializerInternalReader类的Deserialize方法将Json字符串内容反序列化。

最终我们看一下JsonSerializerInternalReader中的部分关键代码

public object Deserialize(JsonReader reader, Type objectType, bool checkAdditionalContent){ … JsonConverter converter = GetConverter(contract, null, null, null); if (reader.TokenType == JsonToken.None && !reader.ReadForType(contract, converter != null)) {  ......  object deserializedValue;  if (converter != null && converter.CanRead)  {   deserializedValue = DeserializeConvertable(converter, reader, objectType, null);  }  else  {   deserializedValue = CreateValueInternal(reader, objectType, contract, null, null, null, null);  }  }}

JsonSerializerInternalReader类里面的Deserialize方法会尝试根据当前请求参数的类型,去查找并实例化一个合适的JsonConverter。 如果查找到匹配的Converter, 就使用该Converter进行实际的反序列化数据绑定操作。在当前例子中由于api的参数类型是Person,所以它会匹配到PersonJsonConverter, 这就是为什么我们通过添加PersonJsonConverter就完成了多态数据绑定的功能。

附源代码

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

  • 相关标签:ASP.NET
  • 本文发布HTML5中文学习网 ,转载请注明出处,感谢您!
  • 相关文章


  • 曝网友假装外国人写投诉信 ofo秒退押金并回函致歉
  • 苹果市值缩水逾2000亿美元 遭多家投行下调目标价
  • Asp.net Core与类库读取配置文件信息的方法_实用技巧
  • asp.net在Repeater嵌套的Repeater中使用复选框详解_实用技巧
  • 利用IIS调试ASP.NET网站程序的完整步骤_实用技巧
  • Asp.Net Core轻松学习系列之配置文件_实用技巧
  • ASP.NET 页生命周期概述(小结)_实用技巧
  • 详解ASP.NET Core WebApi 返回统一格式参数_实用技巧
  • 2018年网络流行语有哪些?2018年十大网络流行语盘点
  • 华为首席财务官孟晚舟被暂扣 深圳市政府要求加方立即放人!
  • 独孤九贱(4)_PHP视频教程

    江湖传言:PHP是世界上最好的编程语言。真的是这样吗?这个梗究竟是从哪来的?学会本课程,你就会明白了。 PHP中文网出品的PHP入门系统教学视频,完全从初学者的角度出发,绝不玩虚的,一切以实用、有用...

    独孤九贱(5)_ThinkPHP5视频教程

    ThinkPHP是国内最流行的中文PHP开发框架,也是您Web项目的最佳选择。《php.cn独孤九贱(5)-ThinkPHP5视频教程》课程以ThinkPHP5最新版本为例,从最基本的框架常识开始,将...

    独孤九贱(1)_HTML5视频教程

    《php.cn原创html5视频教程》课程特色:php中文网原创幽默段子系列课程,以恶搞,段子为主题风格的php视频教程!轻松的教学风格,简短的教学模式,让同学们在不知不觉中,学会了HTML知识。 ...

    ThinkPHP5实战之[教学管理系统]

    本套教程,以一个真实的学校教学管理系统为案例,手把手教会您如何在一张白纸上,从零开始,一步一步的用ThinkPHP5框架快速开发出一个商业项目。

    PHP入门视频教程之一周学会PHP

    所有计算机语言的学习都要从基础开始,《PHP入门视频教程之一周学会PHP》不仅是PHP的基础部分更主要的是PHP语言的核心技术,是学习PHP必须掌握的内容,任何PHP项目的实现都离不开这部分的内容,通...

    作者信息

    kevin

    永远在学习的路上!

    相关教程

  • javascript初级视频教程 javascript初级视频教程
  • jquery 基础视频教程 jquery 基础视频教程
  • javascript三级联动视频教程 javascript三级联动视频教程
  • 独孤九贱(3)_JavaScript视频教程 独孤九贱(3)_JavaScript视频教程
  • 独孤九贱(6)_jQuery视频教程 独孤九贱(6)_jQuery视频教程
  • 热门教程