.NET Core WebApi中如何實現(xiàn)多態(tài)數(shù)據(jù)綁定實例代碼
什么是.NET Core?
隨著2014年 Xamarin和微軟發(fā)起.NET基金會,微軟在2014年11月份 開放.NET框架源代碼。在.NET開源基金會的統(tǒng)一規(guī)劃下誕生了.NET Core 。也就是說.NET Core Framework是參考.NET Framework重新開發(fā)的.NET實現(xiàn),Mono是.NET Framework的一個開源的、跨平臺的實現(xiàn)。
本文主要介紹了關(guān)于.NET Core WebApi多態(tài)數(shù)據(jù)綁定的相關(guān)內(nèi)容,分享出來供大家參考學(xué)習(xí),下面話不多說了,來一起看看詳細(xì)的介紹吧
什么是多態(tài)數(shù)據(jù)綁定?
我們都知道在ASP.NET Core WebApi中數(shù)據(jù)綁定機(jī)制(Data Binding)負(fù)責(zé)綁定請求參數(shù), 通常情況下大部分的數(shù)據(jù)綁定都能在默認(rèn)的數(shù)據(jù)綁定器(Binder)中正常的進(jìn)行,但是也會出現(xiàn)少數(shù)不支持的情況,例如多態(tài)數(shù)據(jù)綁定。所謂的多態(tài)數(shù)據(jù)綁定(polymorphic data binding),即請求參數(shù)是子類對象的Json字符串, 而action中定義的是父類類型的變量,默認(rèn)情況下ASP.NET Core WebApi是不支持多態(tài)數(shù)據(jù)綁定的,會造成數(shù)據(jù)丟失。
以下圖為例

Person類是一個父類,Doctor類和Student類是Person類的派生類。Doctor類中持有的HospitalName屬性,Student中持有的SchoolName屬性。
接下來我們創(chuàng)建一個Web Api項目并添加一個PeopleController。
在PeopleController中我們添加一個Add api,并將請求數(shù)據(jù)直接返回,以便查看效果。
[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內(nèi)容如下。
[{
firstName: 'Mike',
lastName: 'Li'
}, {
firstName: 'Stephie',
lastName: 'Wang',
schoolName: 'No.15 Middle School'
}, {
firstName: 'Jacky',
lastName: 'Chen',
hospitalName: 'Center Hospital'
}]
請求的返回內(nèi)容
[
{
"FirstName": "Mike",
"LastName": "Li"
},
{
"FirstName": "Stephie",
"LastName": "Wang"
},
{
"FirstName": "Jacky",
"LastName": "Chen"
}
]
返回結(jié)果和我們希望得到的結(jié)果不太一樣,Student持有的SchoolName屬性和Doctor持有的HospitalName屬性都丟失了。
現(xiàn)在我們啟動項目調(diào)試模式,重新使用Postman請求一次,得到的結(jié)果如下

People集合中存放3個People類型的對象, 沒有出現(xiàn)我們期望的Student類型對象和Doctor類型對象,這說明.NET Core WebApi默認(rèn)是不支持多態(tài)數(shù)據(jù)綁定的,如果使用父類類型變量來接收數(shù)據(jù),Data Binding只會實例化父類對象,而非一個派生類對象, 從而導(dǎo)致屬性丟失。
自定義JsonConverter來實現(xiàn)多態(tài)數(shù)據(jù)綁定
JsonConverter是Json.NET中的一個類,主要負(fù)責(zé)Json對象的序列化和反序列化。
首先我們創(chuàng)建一個泛型類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,這個方法會負(fù)責(zé)根據(jù)Json字符串的內(nèi)容,返回一個泛型類型對象,這里既可以返回一個當(dāng)前泛型類型的對象,也可以返回一個當(dāng)前泛型類型派生類的對象。JObject是Json.NET中的Json字符串讀取器,負(fù)責(zé)讀取Json字符串中屬性的值。
另外我們還復(fù)寫了ReadJson方法,在ReadJson中我們會先調(diào)用Create方法獲取一個當(dāng)前泛型類對象或者當(dāng)前泛型類的派生類對象(Json.NET中默認(rèn)的KeyValuePairConverter會直接實例化當(dāng)前參數(shù)類型對象,這也就是默認(rèn)不支持多態(tài)數(shù)據(jù)綁定的主要原因),serializer.Popluate方法的作用是將Json字符串的內(nèi)容映射到目標(biāo)對象(當(dāng)前泛型類對象或者當(dāng)前泛型類的派生類對象)的對應(yīng)屬性。
這里由于我們只需要讀取Json, 所以WriteJson的方法我們不需要實現(xiàn),CanWrite屬性我們也強(qiáng)制返回了False。
第二步,我們創(chuàng)建一個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();
}
}
}
在這個類中我們復(fù)寫了Create方法,這里我們使用JObject來獲取Json字符串中擁有的屬性。
- 如果字符串中包含schoolName屬性,就返回一個新的Student對象
- 如果字符串中包含hospitalName屬性,就返回一個新的Doctor對象
- 否則,返回一個新Person對象
最后一步,我們在Person類中使用特性標(biāo)注Person類使用PersonJsonConverter來進(jìn)行轉(zhuǎn)換Json序列化和反序列化。
[JsonConverter(typeof(PersonJsonConverter))]
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
現(xiàn)在我們重新使用調(diào)試模式啟動程序, 然后使用Postman請求當(dāng)前api

我們會發(fā)現(xiàn),people集合中已經(jīng)正確綁定了的派生子類類型對象,最終Postman上我們得到以下響應(yīng)結(jié)果
[
{
"FirstName": "Mike",
"LastName": "Li"
},
{
"SchoolName": "No.15 Middle School",
"FirstName": "Stephie",
"LastName": "Wang"
},
{
"HospitalName": "Center Hospital",
"FirstName": "Jacky",
"LastName": "Chen"
}
]
至此多態(tài)數(shù)據(jù)綁定成功。
刨根問底
為什么添加了一個PersonJsonConverter類,多態(tài)綁定就實現(xiàn)了呢?
讓我們來一起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方法設(shè)置了默認(rèn)數(shù)據(jù)綁定使用Provider列表。
當(dāng)一個api參數(shù)被標(biāo)記為[FromBody]時,BodyModelBinderProvider會實例化一個BodyModelBinder對象來處理這個參數(shù)并嘗試進(jìn)行數(shù)據(jù)綁定。
BodyModelBinder類中有一個BindModelAsync方法,從名字的字面意思上我們很清楚的知道這個方法就是用來綁定數(shù)據(jù)的。
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對象來綁定數(shù)據(jù),由于這時候請求的Content-Type是application/json, 所以這里會使用JsonInputFormatter對象來進(jìn)行數(shù)據(jù)綁定。
下面我們看一下JsonInputFormatter類的部分關(guān)鍵代碼
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方法負(fù)責(zé)數(shù)據(jù)綁定, 在該方法中使用了Json.NET的JsonSerializer類的Deserialize方法來進(jìn)行反序列化, 這說明Mvc Core的底層是直接使用Json.NET來操作Json的。
JsonSerializer類的部分關(guān)鍵代碼
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會調(diào)用JsonSerializerInternalReader類的Deserialize方法將Json字符串內(nèi)容反序列化。
最終我們看一下JsonSerializerInternalReader中的部分關(guān)鍵代碼
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方法會嘗試根據(jù)當(dāng)前請求參數(shù)的類型,去查找并實例化一個合適的JsonConverter。 如果查找到匹配的Converter, 就使用該Converter進(jìn)行實際的反序列化數(shù)據(jù)綁定操作。在當(dāng)前例子中由于api的參數(shù)類型是Person,所以它會匹配到PersonJsonConverter, 這就是為什么我們通過添加PersonJsonConverter就完成了多態(tài)數(shù)據(jù)綁定的功能。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
- jQuery向webApi提交post json數(shù)據(jù)
- ASP.Net WebAPI與Ajax進(jìn)行跨域數(shù)據(jù)交互時Cookies數(shù)據(jù)的傳遞
- ASP.NET WebAPI連接數(shù)據(jù)庫的方法
- 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 服務(wù)端配置跨域的實例
- .Net Core2.1 WebAPI新增Swagger插件詳解
相關(guān)文章
AntDesign Pro + .NET Core 實現(xiàn)基于JWT的登錄認(rèn)證功能
這篇文章主要介紹了AntDesign Pro + .NET Core 實現(xiàn)基于JWT的登錄認(rèn)證功能,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03
關(guān)閉子頁面刷新父頁面中部分控件數(shù)據(jù)的方法
關(guān)閉子頁面刷新父頁面中部分控件數(shù)據(jù),具體的實現(xiàn)代碼如下,感興趣的朋友可以參考下哈2013-05-05
ASP.NET MVC中jQuery與angularjs混合應(yīng)用傳參并綁定數(shù)據(jù)
這篇文章主要介紹了ASP.NET MVC中jQuery與angularjs混合應(yīng)用傳參并綁定數(shù)據(jù),需要的朋友可以參考下2017-06-06
asp.net core razor自定義taghelper的方法
這篇文章主要介紹了asp.net core razor自定義taghelper的方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2019-09-09

