.NET Core WebApi中如何实现多态数据绑定实例代码
 更新时间:2018年08月26日 10:41:07   作者:LamondLu  

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

什么是.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就完成了多态数据绑定的功能。
附源代码
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对华域联盟的支持。

您可能感兴趣的文章:jQuery向webApi提交post json数据ASP.Net WebAPI与Ajax进行跨域数据交互时Cookies数据的传递ASP.NET WebAPI连接数据库的方法Asp.net core WebApi 使用Swagger生成帮助页实例ASP.NET Core 2.0 WebApi全局配置及日志实例浅谈ASP.Net Core WebApi几种版本控制对比asp.net core 2.0 webapi集成signalr(实例讲解)asp.net core webapi 服务端配置跨域的实例.Net Core2.1 WebAPI新增Swagger插件详解

webapi
多态
数据绑定

相关文章
调试ASP.NET2005/2008时,端口不正确的解决三套方案这篇文章主要介绍了调试ASP.NET2005/2008时,端口不正确的解决三套方案,小编就特别喜欢收藏这类文章,方便以后工作学习中遇到这类问题进行解决。 2015-09-09
.NET Core利用skiasharp文字头像生成方法教程(基于docker发布)这篇文章主要给大家介绍了关于.NET Core利用skiasharp文字头像生成(基于docker发布)的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。 2018-03-03
ASP.NET Core3.1 Ocelot负载均衡的实现这篇文章主要介绍了ASP.NET Core3.1 Ocelot负载均衡的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 2020-11-11
Repeater与ListView功能概述及使用介绍Repeater(foreach)用于对绑定数据源中的数据进行遍历并按格式显示,Repeater一般只用来展示数据ListView会自动生成很多模板,免去手写模板代码的麻烦,感兴趣的你可以了解下本文 2013-02-02
ASP.NET中集成百度编辑器UEditor本文给大家讲解的是如何在ASP.NET中集成百度编辑器UEditor的方法和具体的步奏,十分的详细,有需要的小伙伴可以参考下。 2015-06-06
Asp.net 无限级分类实例代码近期做的一个网站需要一个无限级下拉列表的无限级分级功能,于是就有了下面的小作品。
开始以为无限级下拉列表功能应该很简单,无非就是用一个递归算法就好啦,但是在做起来的时候却会遇到一些比较模糊的东西。 2008-07-07
详解ASP.NET Core和ASP.NET Framework共享身份验证本篇文章主要介绍了详解ASP.NET Core和ASP.NET Framework共享身份验证 ,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
2016-12-12
.net 反序题目的详细解答在各种答案,以及平时面试过程中,这道题总归会有一些非常典型的错误发生。其中给老赵的感觉也非常有意思,不知其中的“思路”是否如老赵猜测那样。 2009-06-06
asp.net button 绑定多个参数asp.net button 绑定多个参数的代码 2008-11-11
ASP.NET MVC 4 捆绑和缩小实例介绍在 ASP.NET MVC 4 中可捆绑多个 css 和 js 文件以减少HTTP请求,并对 css 和 js 文件进行压缩(缩小),这样可提高网站的加载速度 2013-06-06

最新评论

声明:本站(华域联盟www.cnhackhy.com)所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。