.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 = ; 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就完成了多态数据绑定的功能。
.NET Core WebApi中实现多态数据绑定的更多相关文章
- ASP.NET Core WebAPI中的分析工具MiniProfiler
介绍 作为一个开发人员,你知道如何分析自己开发的Api性能么? 在Visual Studio和Azure中, 我们可以使用Application Insight来监控项目.除此之外我们还可以使用一个免 ...
- 关于修改.net core webapi中null默认返回的状态码。
在asp .net core webapi中,http请求的响应数据如果是null的话,我们知道状态码会返回204,即NoContent,为什么会出现这种情况呢? 因为在返回响应数据的时候,nul ...
- Asp.Net Core WebApi中接入Swagger组件(初级)
开发WebApi时通常需要为调用我们Api的客户端提供说明文档.Swagger便是为此而存在的,能够提供在线调用.调试的功能和API文档界面. 环境介绍:Asp.Net Core WebApi + S ...
- ASP.NET Core WebAPI中使用JWT Bearer认证和授权
目录 为什么是 JWT Bearer 什么是 JWT JWT 的优缺点 在 WebAPI 中使用 JWT 认证 刷新 Token 使用授权 简单授权 基于固定角色的授权 基于策略的授权 自定义策略授权 ...
- .NET Core:在ASP.NET Core WebApi中使用Cookie
一.Cookie的作用 Cookie通常用来存储有关用户信息的一条数据,可以用来标识登录用户,Cookie存储在客户端的浏览器上.在大多数浏览器中,每个Cookie都存储为一个小文件.Cookie表示 ...
- ASP.NET Core WebApi中使用FluentValidation验证数据模型
原文链接:Common features in ASP.NET Core 2.1 WebApi: Validation 作者:Anthony Giretti 译者:Lamond Lu 介绍 验证用户输 ...
- 【WebAPI No.5】Core WebAPI中的自定义格式化
介绍 Web API为JSON和XML提供媒体类型格式化程序.框架默认将这些格式化程序插入管道中.客户端可以在HTTP请求的Accept标头中请求JSON或XML. 格式化数据这个东西,其实没有什么最 ...
- ASP.NET Core WebApi中简单像素转换跟踪实现
像素跟踪虽然是最早用于跟踪营销转换的方法,但它仍然被广泛使用,像Facebook这样的大公司仍然将其视为跟踪网页转换的方法之一. 由于它的简单性,通过像素方法的跟踪转换仍然被广泛使用.它不需要任何复杂 ...
- Asp.Net Core WebAPI中启用XML格式数据支持
因为XML是一种非常常用的数据格式,所以Asp.Net core提供了非常便利的方式来添加对XML格式的支持 只需要在IOC注册Controller服务的后面跟上.AddXmlDataContract ...
随机推荐
- Game 23
Polycarp plays "Game 23". Initially he has a number nn and his goal is to transform it to ...
- CentOS7上Docker简单安装及nginx部署
安装 如果原来安装过docker,先把原来的删掉,再安装(如果是首次安装docker忽略第一步,直接在第二步看起) 1.1先查看下已经安装了那些docker yum list installed | ...
- lvm快照备份数据库(Mysql5.7)
备份的目的 能够防止由于机械故障以及人为误操作带来的数据丢失,例如将数据库文件保存在了其它地方. 备份的分类 以操作过程中服务的可用性分: 冷备份:cold backup mysql服务关闭,mysq ...
- 用kattle将数据从SQLserver中导入到vertica中
今天简单的学习了一下ETL工具kattle了,只是简单的上手,不过这也已经够我去做POC了. 首先大体介绍一下kattle,Kettle是一款国外开源的ETL工具,纯java编写,可以在Window. ...
- R语言S3类的理解与构建
R语言类 R语言的类有S3类和S4类,S3类用的比较广,创建简单粗糙但是灵活,而S4类比较精细,具有跟C++一样严格的结构.这里我们主要讲S3类. S3类的结构 S3类内部是一个list,append ...
- 你不知道的JS之作用域和闭包(四)(声明)提升
原文:你不知道的js系列 先有鸡还是先有蛋? 如下代码: a = 2; var a; console.log( a ); 很多开发者可能会认为结果会输出 undefined,因为 var a 在 a ...
- mysql 5.6.43免安装版安装教程
1.第一步,下载 Mysql官方网站:http://www.mysql.com/,数据库下载地址:http://www.mysql.com/downloads/.个人比较喜欢用免安装版,使用起来方便 ...
- Hadoop 集群安装(主节点安装)
1.下载安装包及测试文档 切换目录到/tmp view plain copy cd /tmp 下载Hadoop安装包 view plain copy wget http://192.168.1.100 ...
- Java实现递增数组的二分查找
package com.algorithm; import java.util.ArrayList;import java.util.List; /** * 类功能描述: * * @author Ba ...
- Batch入门教程丨第一章:部署与Hello World!(下)
在上期分享的内容中,我们已经掌握了基础理论知识,今天我们将继续了解和学习与Windows Batch有关的知识和编程方法,如何编写和运行Windows Batch程序,脚本语言的入门方式等,从而能够更 ...