溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

.NET Core WebApi中實(shí)現(xiàn)多態(tài)數(shù)據(jù)綁定的方法

發(fā)布時(shí)間:2021-02-08 15:34:42 來源:億速云 閱讀:268 作者:小新 欄目:開發(fā)技術(shù)

小編給大家分享一下.NET Core WebApi中實(shí)現(xiàn)多態(tài)數(shù)據(jù)綁定的方法,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

什么是.NET Core?

隨著2014年 Xamarin和微軟發(fā)起.NET基金會(huì),微軟在2014年11月份 開放.NET框架源代碼。在.NET開源基金會(huì)的統(tǒng)一規(guī)劃下誕生了.NET Core 。也就是說.NET Core Framework是參考.NET Framework重新開發(fā)的.NET實(shí)現(xiàn),Mono是.NET Framework的一個(gè)開源的、跨平臺(tái)的實(shí)現(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é)綁定請(qǐng)求參數(shù), 通常情況下大部分的數(shù)據(jù)綁定都能在默認(rèn)的數(shù)據(jù)綁定器(Binder)中正常的進(jìn)行,但是也會(huì)出現(xiàn)少數(shù)不支持的情況,例如多態(tài)數(shù)據(jù)綁定。所謂的多態(tài)數(shù)據(jù)綁定(polymorphic data binding),即請(qǐng)求參數(shù)是子類對(duì)象的Json字符串, 而action中定義的是父類類型的變量,默認(rèn)情況下ASP.NET Core WebApi是不支持多態(tài)數(shù)據(jù)綁定的,會(huì)造成數(shù)據(jù)丟失。

以下圖為例

.NET Core WebApi中實(shí)現(xiàn)多態(tài)數(shù)據(jù)綁定的方法

Person類是一個(gè)父類,Doctor類和Student類是Person類的派生類。Doctor類中持有的HospitalName屬性,Student中持有的SchoolName屬性。

接下來我們創(chuàng)建一個(gè)Web Api項(xiàng)目并添加一個(gè)PeopleController。

在PeopleController中我們添加一個(gè)Add api,并將請(qǐng)求數(shù)據(jù)直接返回,以便查看效果。

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

這里我們使用Postman請(qǐng)求這個(gè)api, 請(qǐng)求的Content-Type是application/json, 請(qǐng)求的Body內(nèi)容如下。

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

請(qǐng)求的返回內(nèi)容

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

返回結(jié)果和我們希望得到的結(jié)果不太一樣,Student持有的SchoolName屬性和Doctor持有的HospitalName屬性都丟失了。

現(xiàn)在我們啟動(dòng)項(xiàng)目調(diào)試模式,重新使用Postman請(qǐng)求一次,得到的結(jié)果如下

.NET Core WebApi中實(shí)現(xiàn)多態(tài)數(shù)據(jù)綁定的方法

People集合中存放3個(gè)People類型的對(duì)象, 沒有出現(xiàn)我們期望的Student類型對(duì)象和Doctor類型對(duì)象,這說明.NET Core WebApi默認(rèn)是不支持多態(tài)數(shù)據(jù)綁定的,如果使用父類類型變量來接收數(shù)據(jù),Data Binding只會(huì)實(shí)例化父類對(duì)象,而非一個(gè)派生類對(duì)象, 從而導(dǎo)致屬性丟失。

自定義JsonConverter來實(shí)現(xiàn)多態(tài)數(shù)據(jù)綁定

JsonConverter是Json.NET中的一個(gè)類,主要負(fù)責(zé)Json對(duì)象的序列化和反序列化。

首先我們創(chuàng)建一個(gè)泛型類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();
 }
}

其中,我們加入了一個(gè)抽象方法Create,這個(gè)方法會(huì)負(fù)責(zé)根據(jù)Json字符串的內(nèi)容,返回一個(gè)泛型類型對(duì)象,這里既可以返回一個(gè)當(dāng)前泛型類型的對(duì)象,也可以返回一個(gè)當(dāng)前泛型類型派生類的對(duì)象。JObject是Json.NET中的Json字符串讀取器,負(fù)責(zé)讀取Json字符串中屬性的值。

另外我們還復(fù)寫了ReadJson方法,在ReadJson中我們會(huì)先調(diào)用Create方法獲取一個(gè)當(dāng)前泛型類對(duì)象或者當(dāng)前泛型類的派生類對(duì)象(Json.NET中默認(rèn)的KeyValuePairConverter會(huì)直接實(shí)例化當(dāng)前參數(shù)類型對(duì)象,這也就是默認(rèn)不支持多態(tài)數(shù)據(jù)綁定的主要原因),serializer.Popluate方法的作用是將Json字符串的內(nèi)容映射到目標(biāo)對(duì)象(當(dāng)前泛型類對(duì)象或者當(dāng)前泛型類的派生類對(duì)象)的對(duì)應(yīng)屬性。

這里由于我們只需要讀取Json, 所以WriteJson的方法我們不需要實(shí)現(xiàn),CanWrite屬性我們也強(qiáng)制返回了False。

第二步,我們創(chuàng)建一個(gè)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();
 }
 }
}

在這個(gè)類中我們復(fù)寫了Create方法,這里我們使用JObject來獲取Json字符串中擁有的屬性。

  • 如果字符串中包含schoolName屬性,就返回一個(gè)新的Student對(duì)象

  • 如果字符串中包含hospitalName屬性,就返回一個(gè)新的Doctor對(duì)象

  • 否則,返回一個(gè)新Person對(duì)象

最后一步,我們?cè)赑erson類中使用特性標(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)試模式啟動(dòng)程序, 然后使用Postman請(qǐng)求當(dāng)前api

 .NET Core WebApi中實(shí)現(xiàn)多態(tài)數(shù)據(jù)綁定的方法

我們會(huì)發(fā)現(xiàn),people集合中已經(jīng)正確綁定了的派生子類類型對(duì)象,最終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ù)綁定成功。

刨根問底

為什么添加了一個(gè)PersonJsonConverter類,多態(tài)綁定就實(shí)現(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)一個(gè)api參數(shù)被標(biāo)記為[FromBody]時(shí),BodyModelBinderProvider會(huì)實(shí)例化一個(gè)BodyModelBinder對(duì)象來處理這個(gè)參數(shù)并嘗試進(jìn)行數(shù)據(jù)綁定。

BodyModelBinder類中有一個(gè)BindModelAsync方法,從名字的字面意思上我們很清楚的知道這個(gè)方法就是用來綁定數(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);
 }
}

在這個(gè)方法中它會(huì)嘗試尋找一個(gè)匹配的IInputFormatter對(duì)象來綁定數(shù)據(jù),由于這時(shí)候請(qǐng)求的Content-Type是application/json, 所以這里會(huì)使用JsonInputFormatter對(duì)象來進(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會(huì)調(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方法會(huì)嘗試根據(jù)當(dāng)前請(qǐng)求參數(shù)的類型,去查找并實(shí)例化一個(gè)合適的JsonConverter。 如果查找到匹配的Converter, 就使用該Converter進(jìn)行實(shí)際的反序列化數(shù)據(jù)綁定操作。在當(dāng)前例子中由于api的參數(shù)類型是Person,所以它會(huì)匹配到PersonJsonConverter, 這就是為什么我們通過添加PersonJsonConverter就完成了多態(tài)數(shù)據(jù)綁定的功能。

以上是“.NET Core WebApi中實(shí)現(xiàn)多態(tài)數(shù)據(jù)綁定的方法”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI