热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

.NETCoreWebApi中如何实现多态数据绑定实例代码

这篇文章主要给大家介绍了关于.NETCoreWebApi中如何实现多态数据绑定的相关资料,文中通过示例代码介绍的非常详细,并给出来完整的实例代码,需要的朋友可以参考借鉴,下面来一起学习学习吧

什么是.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 Add([FromBody]List 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 : 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, 其代码如下

public class PersonJsonConverter : JsonCreationConverter
{
 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
{
 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&#63;.InputFormatterSelected(formatter, formatterContext);
   break;
  }
  else
  {
    logger&#63;.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 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 &#63;&#63; 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就完成了多态数据绑定的功能。

附源代码

总结

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


推荐阅读
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 单点登录原理及实现方案详解
    本文详细介绍了单点登录的原理及实现方案,其中包括共享Session的方式,以及基于Redis的Session共享方案。同时,还分享了作者在应用环境中所遇到的问题和经验,希望对读者有所帮助。 ... [详细]
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • 本文介绍了一种处理AJAX操作授权过期的全局方式,以解决Asp.net MVC中Session过期异常的问题。同时还介绍了基于WebImage的图片上传工具类。详细内容请参考链接:https://www.cnblogs.com/starluck/p/8284949.html ... [详细]
  • 在Android中解析Gson解析json数据是很方便快捷的,可以直接将json数据解析成java对象或者集合。使用Gson解析json成对象时,默认将json里对应字段的值解析到java对象里对应字段的属性里面。然而,当我们自己定义的java对象里的属性名与json里的字段名不一样时,我们可以使用@SerializedName注解来将对象里的属性跟json里字段对应值匹配起来。本文介绍了使用@SerializedName注解解析json数据的方法,并给出了具体的使用示例。 ... [详细]
  • 本文介绍了Sencha Touch的学习使用心得,主要包括搭建项目框架的过程。作者强调了使用MVC模式的重要性,并提供了一个干净的引用示例。文章还介绍了Index.html页面的作用,以及如何通过链接样式表来改变全局风格。 ... [详细]
  • Asp.net Mvc Framework 七 (Filter及其执行顺序) 的应用示例
    本文介绍了在Asp.net Mvc中应用Filter功能进行登录判断、用户权限控制、输出缓存、防盗链、防蜘蛛、本地化设置等操作的示例,并解释了Filter的执行顺序。通过示例代码,详细说明了如何使用Filter来实现这些功能。 ... [详细]
  • 本文介绍了ASP.NET Core MVC的入门及基础使用教程,根据微软的文档学习,建议阅读英文文档以便更好理解,微软的工具化使用方便且开发速度快。通过vs2017新建项目,可以创建一个基础的ASP.NET网站,也可以实现动态网站开发。ASP.NET MVC框架及其工具简化了开发过程,包括建立业务的数据模型和控制器等步骤。 ... [详细]
  • express工程中的json调用方法
    本文介绍了在express工程中如何调用json数据,包括建立app.js文件、创建数据接口以及获取全部数据和typeid为1的数据的方法。 ... [详细]
  • Django + Ansible 主机管理(有源码)
    本文给大家介绍如何利用DjangoAnsible进行Web项目管理。Django介绍一个可以使Web开发工作愉快并且高效的Web开发框架,能够以最小的代价构建和维护高 ... [详细]
  • Asp.Net MVC 测试应用程序
    建立一个Asp.NetMVC项目的时候,如果选择建立测试项目,那么系统会为我们建立一个项目所对应的测试项目。包含了Controller文件夹中对应的Controller单元测试文件, ... [详细]
  • postman下载安装教程
    Postman是一款强大网页接口调试工具,我们在平时开发过程中经常会使用到,一般使用最多的是postman的客户端,实际上postman在谷歌浏览器上也提供了插件,可以不必要安装客 ... [详细]
  • SAP接口编程PyRFC 调用 BAPI_FIXEDASSET_CREATE1创建固定资产
    本篇演示通过PyRFC调用BAPI_FIXEDASSET_CREATE1在SAP系统中创建固定资产,再一次体验一下Python与其它语言相比的简洁性。首先简单说明B ... [详细]
  • postman 根据接口返回值设置全局变量 ... [详细]
  • 使用Postman调试API遇到“400 Bad Request”问题
    问题今日使用Postman调试一个临时接手的API,参照调用程序代码填充好请求头、请求体参数(post),Postman客 ... [详细]
author-avatar
极御云安全
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有