您好,登錄后才能下訂單哦!
小編給大家分享一下.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就完成了多態數據綁定的功能。
以上是“.NET Core WebApi中實現多態數據綁定的方法”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。