您好,登錄后才能下訂單哦!
本篇中會為大家介紹在ASP.NET Web API中ModelBinder的綁定原理以及涉及到的一些對象模型,還有簡單的Model綁定示例,在前面的篇幅中講解了Model元數(shù)據(jù)、ValueProvider的模塊,然后還有本篇的Model綁定的模塊這些會結(jié)合到后面篇幅中的ParameterBinder模塊中來使用,也就是說在ASP.NET Web API框架中綁定的方式有兩種實現(xiàn),都是通過ParameterBinder來對參數(shù)進(jìn)行綁定,而在ParameterBinder中的實現(xiàn)則會有兩種方式,今天就給大家單獨的說明一下Model綁定,把它看成一個單獨的功能模塊就行了。
不瞎扯了,直接進(jìn)入主題,首先我們來看IModelBinder接口類型的定義,所有的ModelBinder功能模塊都實現(xiàn)了IModelBinder接口,如示例代碼1-1
IModelBinder
示例代碼1-1
namespace System.Web.Http.ModelBinding { public interface IModelBinder { bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext); } }
在代碼1-1中我們可以看到BindModel()方法中定義了兩個參數(shù)而且都是上下文類型的參數(shù),第一個上下文參數(shù)表示操作上下文,它是在控制器方法被執(zhí)行之前就被創(chuàng)建并且其中封裝了一些后續(xù)操作必要的信息以及存儲請求、響應(yīng)和參數(shù)綁定的結(jié)果值,這個稍后會跟大家講解,它起到一個很重要的作用。
第二個上下文參數(shù)是綁定上下文參數(shù),這個容易理解,意思就是對象里封裝著本次要綁定對象的信息也就是Model元數(shù)據(jù)、ValueProvider之類的信息,現(xiàn)在不理解也沒關(guān)系慢慢往后看看完就會明白的。
HttpActionContext
代碼1-2
namespace System.Web.Http.Controllers { public class HttpActionContext { public HttpActionContext(); public HttpActionContext(HttpControllerContext controllerContext, HttpActionDescriptor actionDescriptor); public Dictionary<string, object> ActionArguments { get; } public HttpActionDescriptor ActionDescriptor { get; set; } public HttpControllerContext ControllerContext { get; set; } public ModelStateDictionary ModelState { get; } public HttpRequestMessage Request { get; } public HttpResponseMessage Response { get; set; } } }
代碼1-2就是HttpActionContext類型的定義了,下面簡單的描述一下幾個屬性所表示的含義,ActionArguments屬性中的值是對應(yīng)著控制器方法的參數(shù)列表,其中Key值就是參數(shù)名稱,Value值就是參數(shù)的實際數(shù)據(jù)值了,因為Model綁定是一個遞歸的過程在復(fù)雜類型的子項綁定完畢后并不會對這個屬性進(jìn)行賦值,而是等這一個參數(shù)項全部綁定完成了才會進(jìn)行賦值。
HttpActionDescriptor類型的ActionDescriptor屬性,這個是HttpControllerDescriptor類型之后所見的第二個這種描述類型,后面還會有HttpParameterDescriptor類型,在這里ActionDescriptor屬性中就是封裝著當(dāng)前所要請求的控制器方法信息,類似封裝著方法的元數(shù)據(jù)信息。
ControllerContext屬性就不用多說了想必大家也都知道它的作用,ModelStateDictionary類型的ModelState屬性則是在Model綁定之后才會對其操作,是把參數(shù)綁定驗證后的值存在這個屬性當(dāng)中。
HttpActionContextExtensions
代碼1-3
namespace System.Web.Http.Controllers { // 摘要: // 包含 System.Web.Http.Controllers.HttpActionContext 的擴(kuò)展方法。 [EditorBrowsable(EditorBrowsableState.Never)] public static class HttpActionContextExtensions { public static bool Bind(this HttpActionContext actionContext, ModelBindingContext bindingContext); public static bool Bind(this HttpActionContext actionContext, ModelBindingContext bindingContext, IEnumerable<IModelBinder> binders); //…… } }
代碼1-3的所示的是包含HttpActionContext類型的擴(kuò)展方法類型HttpActionContextExtensions,我們在這之中可以看到兩個Bind()方法,這兩個Bind()也是Model綁定至關(guān)重要的地方,因為Model綁定的遞歸就是在這里實現(xiàn)的,至于怎么實現(xiàn)的稍后會說。
這里的第一個Bind()方法其實就是調(diào)用第二個Bind()方法來執(zhí)行的。而第二Bind()方法中IEnumerable<IModelBinder>參數(shù)則是從HttpActionContext類型中獲取到當(dāng)前的HttpControllerContext并且再從其中獲取到當(dāng)前請求的配置對象HttpConfiguration對象,最后從配置對象中的容器屬性中獲取ModelBinder的提供程序集合,然后根據(jù)當(dāng)前ModelBindingContext中的ModelType類型使用提供程序集合來判斷后獲取適合類型的IModelBinder集合,從而調(diào)用第二個Bind()方法。
這樣看可能還是不太理解遞歸的情況,大家稍安勿躁,后面慢慢講解。
ModelBindingContext
代碼1-4
namespace System.Web.Http.ModelBinding { // 摘要: // 提供運行模型聯(lián)編程序的上下文。 public class ModelBindingContext { // 摘要: // 初始化 System.Web.Http.ModelBinding.ModelBindingContext 類的新實例。 public ModelBindingContext(); public ModelBindingContext(ModelBindingContext bindingContext); public bool FallbackToEmptyPrefix { get; set; } public object Model { get; set; } public ModelMetadata ModelMetadata { get; set; } public string ModelName { get; set; } public ModelStateDictionary ModelState { get; set; } public Type ModelType { get; } public IDictionary<string, ModelMetadata> PropertyMetadata { get; } public ModelValidationNode ValidationNode { get; set; } public IValueProvider ValueProvider { get; set; } } }
代碼1-4中所示的就是綁定上下文對象,首先我們看到它的重載構(gòu)造函數(shù)中有個ModelBindingContext類型的參數(shù)用以表示嵌套,內(nèi)部的實現(xiàn)是用以傳遞ModelState屬性的狀態(tài)值和ValueProvider值提供程序,至于為什么是這種結(jié)構(gòu)?這個跟綁定復(fù)雜類型的時候有關(guān),構(gòu)造就如同ModelState屬性對象的ModelStateDictionary類型一樣,這種結(jié)構(gòu)稍后會講解。
當(dāng)中的Model屬性表示當(dāng)前ModelBindingContext中綁定過后的Model值,然后ModelMetadata、ModelName、ModelType、PropertyMetadata這些屬性都是表示當(dāng)前ModelBindingContext中Model的對應(yīng)值。這個”當(dāng)前”可能是string類型,也可能是復(fù)雜類型。(復(fù)雜類型在綁定的時候會被ASP.NET Web API框架封裝起來有個特定的類型,這個稍后講解)
簡單類型綁定器以及綁定器提供程序
簡單類型綁定器- TypeConverterModelBinder
代碼1-5
public sealed class TypeConverterModelBinder : IModelBinder { // Methods public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) { object obj2; ModelBindingHelper.ValidateBindingContext(bindingContext); ValueProviderResult result = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); if (result == null) { return false; } bindingContext.ModelState.SetModelValue(bindingContext.ModelName, result); try { obj2 = result.ConvertTo(bindingContext.ModelType); } catch (Exception exception) { if (IsFormatException(exception)) { string errorMessage = ModelBinderConfig.TypeConversionErrorMessageProvider(actionContext, bindingContext.ModelMetadata, result.AttemptedValue); if (errorMessage != null) { bindingContext.ModelState.AddModelError(bindingContext.ModelName, errorMessage); } } else { bindingContext.ModelState.AddModelError(bindingContext.ModelName, exception); } return false; } ModelBindingHelper.ReplaceEmptyStringWithNull(bindingContext.ModelMetadata, ref obj2); bindingContext.Model = obj2; return true; } }
在代碼1-5中,我們看到TypeConverterModelBinder類型實現(xiàn)了IModelBinder接口,并且在BindModel()方法中直接就是使用綁定上下文中的ValueProvider根據(jù)綁定上下文中的ModelName屬性,ModelName就是我們前面ValueProvider篇幅中講解到的前綴值和屬性值的合并。而后會將獲取到的結(jié)果值進(jìn)行類型判斷,如果不能正確的轉(zhuǎn)換成string類型則會提示各種異常,當(dāng)然了這種異常不會報出來,只是添加到了當(dāng)前綁定上下文的ModelState屬性中,如果可以轉(zhuǎn)換成功則會對當(dāng)前綁定上下文的Model值進(jìn)行賦值。
簡單類型綁定器提供程序- TypeConverterModelBinderProvider
代碼1-6
public sealed class TypeConverterModelBinderProvider : ModelBinderProvider { // Methods public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType) { if (modelType == null) { throw Error.ArgumentNull("modelType"); } if (!TypeHelper.HasStringConverter(modelType)) { return null; } return new TypeConverterModelBinder(); } }
代碼1-6中所示TypeConverterModelBinderProvider類型則為簡單類型綁定器的提供程序,并且繼承自ModelBinderProvider類型,講到這里了我才發(fā)現(xiàn)我把這個類型忘記說明了,不過沒關(guān)系,大家自行看一下就好了,ModelBinderProvider就是一個抽象類,然后定義了一個抽象的行為。
在TypeConverterModelBinderProvider類型的實現(xiàn)中,我們可以清楚的看到如果參數(shù)modelType可以成功的轉(zhuǎn)換成string類型則會返回TypeConverterModelBinder類型的實例,不然則返回null。
復(fù)雜類型綁定器(封裝器)以及復(fù)雜類型綁定器(封裝器)提供程序
復(fù)雜類型封裝對象-ComplexModelDto
代碼1-7
namespace System.Web.Http.ModelBinding.Binders { // 摘要: // 表示一個復(fù)雜模型的數(shù)據(jù)傳輸對象 (DTO)。 public class ComplexModelDto { public ComplexModelDto(ModelMetadata modelMetadata, IEnumerable<ModelMetadata> propertyMetadata); public ModelMetadata ModelMetadata { get; } public Collection<ModelMetadata> PropertyMetadata { get; } public IDictionary<ModelMetadata, ComplexModelDtoResult> Results { get; } } }
大家也看到了代碼1-7中的注釋部分,表示一個復(fù)雜模型(Model)的數(shù)據(jù)傳輸對象,實際就是對復(fù)雜類型的重新封裝,封裝的方式大家也看到了都是以Model元數(shù)據(jù)的方式,這個類型我就不多說了。對于Model元數(shù)據(jù)不太清楚的朋友建議去把前面的篇幅看一下。
復(fù)雜類型封裝器-MutableObjectModelBinder
對于MutableObjectModelBinder類型中的實現(xiàn)代碼我就不貼了太多了,在這些理論基礎(chǔ)都講完之后后面的篇幅中會有代碼示例的。
這里我就用文字來描述一下MutableObjectModelBinder類型所要實現(xiàn)的功能,為什么叫MutableObjectModelBinder為復(fù)雜類型封裝器呢?因為MutableObjectModelBinder類型它不干綁定的事情,在它執(zhí)行的時候就一定可以判定當(dāng)前綁定上下文的Model是一個復(fù)雜類型,這個時候MutableObjectModelBinder會根據(jù)當(dāng)前綁定上下文中的Metadata下的Properties屬性獲取到當(dāng)前Model下屬性的Model元數(shù)據(jù)列表,并且根據(jù)每一項的Model元數(shù)據(jù)進(jìn)行篩選,篩選的條件依賴于應(yīng)用在Model屬性上的特性,也就是HttpBindingBehaviorAttribute類型,對于這個類型看完這些之后自己去琢磨吧。
在獲取到Model下對應(yīng)屬性的Model元數(shù)據(jù)集合后,然后創(chuàng)建ComplexModelDto對象實例,并且新建一個綁定上下文把新建的ComplexModelDto對象實例作為Model、ModelType這些相關(guān)屬性,然后最后會調(diào)用actionContext.Bind(context);,也就是代碼1-3中的HttpActionContext擴(kuò)展方法進(jìn)入Model綁定中的遞歸。
復(fù)雜類型封裝器提供程序- MutableObjectModelBinderProvider
代碼1-8
public sealed class MutableObjectModelBinderProvider : ModelBinderProvider { // Methods public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType) { if (!MutableObjectModelBinder.CanBindType(modelType)) { return null; } return new MutableObjectModelBinder(); } }
代碼1-8中可以看到在根據(jù)類型判斷的時候是調(diào)用的MutableObjectModelBinder中的靜態(tài)方法CanBindType(),在CanBindType()方法實現(xiàn)中判斷類型不能為ComplexModelDto類型和string類型的,string類型的好理解,因為是屬于TypeConverterModelBinder類型來綁定的,ComplexModelDto類型是為了防止框架的處理進(jìn)入一個死循環(huán),這個看到最后大家就會明白的。
復(fù)雜類型綁定器- ComplexModelDtoModelBinder
代碼1-9
public sealed class ComplexModelDtoModelBinder : IModelBinder { // Methods public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) { ModelBindingHelper.ValidateBindingContext(bindingContext, typeof(ComplexModelDto), false); ComplexModelDto model = (ComplexModelDto)bindingContext.Model; foreach (ModelMetadata metadata in model.PropertyMetadata) { ModelBindingContext context = new ModelBindingContext(bindingContext) { ModelMetadata = metadata, ModelName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName, metadata.PropertyName) }; if (actionContext.Bind(context)) { model.Results[metadata] = new ComplexModelDtoResult(context.Model, context.ValidationNode); } } return true; } }
看這代碼1-9中所示類型的名字不用說也是用來對ComplexModelDto對象進(jìn)行處理的,可以在代碼實現(xiàn)中看出來,先把綁定上下文中的Model獲取出來轉(zhuǎn)換成ComplexModelDto實例對象,然后遍歷其屬性PropertyMetadata,根據(jù)其每一項的Model元數(shù)據(jù)創(chuàng)建一個綁定上下文,然后調(diào)用actionContext.Bind(context)方法,這也是Model綁定遞歸的過程之一。這種情況會出現(xiàn)在初始Model類型是復(fù)雜類型并且其屬性中也有復(fù)雜類型。
復(fù)雜類型綁定器提供程序- ComplexModelDtoModelBinderProvider
代碼1-10
public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType) { if (modelType == null) { throw Error.ArgumentNull("modelType"); } if (!(modelType == this.ModelType)) { return null; } if (this.SuppressPrefixCheck) { return this._modelBinderFactory(); } return new SimpleModelBinder(this); }
代碼1-10并不是ComplexModelDtoModelBinderProvider類型中本身的實現(xiàn),而是其本質(zhì)的實現(xiàn)是SimpleModelBinderProvider類型來完成的,分為檢查Model的前綴和不檢查兩種。這個自行看一下就知道了。
我們看下整體的結(jié)構(gòu)圖。
圖1
當(dāng)然了還有其他的ModelBinder類型這里就不一一講解了。最后我們看一下模擬復(fù)雜綁定的示意圖。
圖2
這里的Product是一個復(fù)雜類型,其中有三個string類型的屬性,執(zhí)行的順序為紅、藍(lán)、黃。這里要說明的就是除了紅色部分進(jìn)入HttpActionContextExtensions之后不會再次遞歸,其他藍(lán)色和***部分均有可能,只要碰到有復(fù)雜類型。
大概的說明一下流程代碼部分在示例篇章會貼出來,首先在Product類型進(jìn)行綁定的時候會先獲取到Product的類型,然后根據(jù)當(dāng)前框架中注冊的一系列ModelBinder提供程序進(jìn)行篩選獲取到可以對復(fù)雜類型進(jìn)行綁定的ModelBinder對象,在上圖中也就是MutableObjectModelBinder類型,在MutableObjectModelBinder類型處理中會將Product類型的所有屬性Model元數(shù)據(jù)進(jìn)行封裝,封裝為ComplexModelDto對象實例,然后MutableObjectModelBinder類型會生成一個ModelBindingContext1對象實例,調(diào)用HttpActionContext類型的擴(kuò)展方法類型HttpActionContextExtensions中的方法Bind()進(jìn)行Model綁定,在Bind()方法中會重復(fù)紅色線條流程部分,意思就是說會根據(jù)ModelBindingContext1對象實例中的Metadata屬性獲取到Model類型,剛才我們也說過了Model類型被封裝為ComplexModelDto類型了,而后根據(jù)這個類型進(jìn)行篩選獲取到ComplexModelDtoModelBinderProvider提供程序,隨之生成ComplexModelDtoModelBinder實例,在ComplexModelDtoModelBinder執(zhí)行Model綁定的處理過程中,會遍歷ComplexModelDto類型實例中的每一項屬性元數(shù)據(jù)并且生成對應(yīng)的ModelBindingContext,在上圖也就是ModelBindingContext2以及在ModelBindingContext2執(zhí)行綁定操作后的ModelBindingContext3。在ModelBindingContext2生成完畢之后會再次的調(diào)用HttpActionContext類型的擴(kuò)展方法類型HttpActionContextExtensions中的方法Bind()進(jìn)行Model綁定,因為Product中的屬性都是string類型所以不存在復(fù)雜類型,按照上圖中的順序大家可以看出,如果是復(fù)雜類型則會重新執(zhí)行到紅色線條的起始部分。因為這個時候是string類型所以篩選出的提供程序類型為TypeConverterModelBinderProvider,從而生成TypeConverterModelBinder實例為之綁定。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。