ASP.NET Web API Model-ModelBinder

ASP.NET Web API Model-ModelBinder

前言

本篇中會爲大家介紹在ASP.NET Web APIModelBinder的綁定原理以及涉及到的一些對象模型,還有簡單的Model綁定示例,在前面的篇幅中講解了Model元數據、ValueProvider的模塊,然後還有本篇的Model綁定的模塊這些會結合到後面篇幅中的ParameterBinder模塊中來使用,也就是說在ASP.NET Web API框架中綁定的方式有兩種實現,都是通過ParameterBinder來對參數進行綁定,而在ParameterBinder中的實現則會有兩種方式,今天就給大家單獨的說明一下Model綁定,把它看成一個單獨的功能模塊就行了。

Model-ModelBinder

不瞎扯了,直接進入主題,首先我們來看IModelBinder接口類型的定義,所有的ModelBinder功能模塊都實現了IModelBinder接口,如示例代碼1-1


IModelBinder

示例代碼1-1

namespace System.Web.Http.ModelBinding
{
    public interface IModelBinder
    {
        bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext);
    }
}


在代碼1-1中我們可以看到BindModel()方法中定義了兩個參數而且都是上下文類型的參數,第一個上下文參數表示操作上下文,它是在控制器方法被執行之前就被創建並且其中封裝了一些後續操作必要的信息以及存儲請求、響應和參數綁定的結果值,這個稍後會跟大家講解,它起到一個很重要的作用。

第二個上下文參數是綁定上下文參數,這個容易理解,意思就是對象裏封裝着本次要綁定對象的信息也就是Model元數據、ValueProvider之類的信息,現在不理解也沒關係慢慢往後看看完就會明白的。

 

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屬性中的值是對應着控制器方法的參數列表,其中Key值就是參數名稱,Value值就是參數的實際數據值了,因爲Model綁定是一個遞歸的過程在複雜類型的子項綁定完畢後並不會對這個屬性進行賦值,而是等這一個參數項全部綁定完成了纔會進行賦值。

HttpActionDescriptor類型的ActionDescriptor屬性,這個是HttpControllerDescriptor類型之後所見的第二個這種描述類型,後面還會有HttpParameterDescriptor類型,在這裏ActionDescriptor屬性中就是封裝着當前所要請求的控制器方法信息,類似封裝着方法的元數據信息。

ControllerContext屬性就不用多說了想必大家也都知道它的作用,ModelStateDictionary類型的ModelState屬性則是在Model綁定之後纔會對其操作,是把參數綁定驗證後的值存在這個屬性當中。

 

HttpActionContextExtensions

代碼1-3

namespace System.Web.Http.Controllers
{
    // 摘要:
    //     包含 System.Web.Http.Controllers.HttpActionContext 的擴展方法。
    [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類型的擴展方法類型HttpActionContextExtensions,我們在這之中可以看到兩個Bind()方法,這兩個Bind()也是Model綁定至關重要的地方,因爲Model綁定的遞歸就是在這裏實現的,至於怎麼實現的稍後會說。

這裏的第一個Bind()方法其實就是調用第二個Bind()方法來執行的。而第二Bind()方法中IEnumerable<IModelBinder>參數則是從HttpActionContext類型中獲取到當前的HttpControllerContext並且再從其中獲取到當前請求的配置對象HttpConfiguration對象,最後從配置對象中的容器屬性中獲取ModelBinder的提供程序集合,然後根據當前ModelBindingContext中的ModelType類型使用提供程序集合來判斷後獲取適合類型的IModelBinder集合,從而調用第二個Bind()方法。

這樣看可能還是不太理解遞歸的情況,大家稍安勿躁,後面慢慢講解。

 

ModelBindingContext

代碼1-4

namespace System.Web.Http.ModelBinding
{
    // 摘要:
    //     提供運行模型聯編程序的上下文。
    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中所示的就是綁定上下文對象,首先我們看到它的重載構造函數中有個ModelBindingContext類型的參數用以表示嵌套,內部的實現是用以傳遞ModelState屬性的狀態值和ValueProvider值提供程序,至於爲什麼是這種結構?這個跟綁定複雜類型的時候有關,構造就如同ModelState屬性對象的ModelStateDictionary類型一樣,這種結構稍後會講解。

當中的Model屬性表示當前ModelBindingContext中綁定過後的Model值,然後ModelMetadataModelNameModelTypePropertyMetadata這些屬性都是表示當前ModelBindingContextModel的對應值。這個當前可能是string類型,也可能是複雜類型。(複雜類型在綁定的時候會被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類型實現了IModelBinder接口,並且在BindModel()方法中直接就是使用綁定上下文中的ValueProvider根據綁定上下文中的ModelName屬性,ModelName就是我們前面ValueProvider篇幅中講解到的前綴值和屬性值的合併。而後會將獲取到的結果值進行類型判斷,如果不能正確的轉換成string類型則會提示各種異常,當然了這種異常不會報出來,只是添加到了當前綁定上下文的ModelState屬性中,如果可以轉換成功則會對當前綁定上下文的Model值進行賦值。

 

簡單類型綁定器提供程序- 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類型,講到這裏了我才發現我把這個類型忘記說明了,不過沒關係,大家自行看一下就好了,ModelBinderProvider就是一個抽象類,然後定義了一個抽象的行爲。

TypeConverterModelBinderProvider類型的實現中,我們可以清楚的看到如果參數modelType可以成功的轉換成string類型則會返回TypeConverterModelBinder類型的實例,不然則返回null

 

 

複雜類型綁定器(封裝器)以及複雜類型綁定器(封裝器)提供程序

 

複雜類型封裝對象-ComplexModelDto

代碼1-7

namespace System.Web.Http.ModelBinding.Binders
{
    // 摘要:
    //     表示一個複雜模型的數據傳輸對象 (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中的註釋部分,表示一個複雜模型(Model)的數據傳輸對象,實際就是對複雜類型的重新封裝,封裝的方式大家也看到了都是以Model元數據的方式,這個類型我就不多說了。對於Model元數據不太清楚的朋友建議去把前面的篇幅看一下。

 

複雜類型封裝器-MutableObjectModelBinder

 

對於MutableObjectModelBinder類型中的實現代碼我就不貼了太多了,在這些理論基礎都講完之後後面的篇幅中會有代碼示例的。

這裏我就用文字來描述一下MutableObjectModelBinder類型所要實現的功能,爲什麼叫MutableObjectModelBinder爲複雜類型封裝器呢?因爲MutableObjectModelBinder類型它不幹綁定的事情,在它執行的時候就一定可以判定當前綁定上下文的Model是一個複雜類型,這個時候MutableObjectModelBinder會根據當前綁定上下文中的Metadata下的Properties屬性獲取到當前Model下屬性的Model元數據列表,並且根據每一項的Model元數據進行篩選,篩選的條件依賴於應用在Model屬性上的特性,也就是HttpBindingBehaviorAttribute類型,對於這個類型看完這些之後自己去琢磨吧。

在獲取到Model下對應屬性的Model元數據集合後,然後創建ComplexModelDto對象實例,並且新建一個綁定上下文把新建的ComplexModelDto對象實例作爲ModelModelType這些相關屬性,然後最後會調用actionContext.Bind(context);,也就是代碼1-3中的HttpActionContext擴展方法進入Model綁定中的遞歸。

 

複雜類型封裝器提供程序- 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中可以看到在根據類型判斷的時候是調用的MutableObjectModelBinder中的靜態方法CanBindType(),在CanBindType()方法實現中判斷類型不能爲ComplexModelDto類型和string類型的,string類型的好理解,因爲是屬於TypeConverterModelBinder類型來綁定的,ComplexModelDto類型是爲了防止框架的處理進入一個死循環,這個看到最後大家就會明白的。

 

 

複雜類型綁定器- 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對象進行處理的,可以在代碼實現中看出來,先把綁定上下文中的Model獲取出來轉換成ComplexModelDto實例對象,然後遍歷其屬性PropertyMetadata,根據其每一項的Model元數據創建一個綁定上下文,然後調用actionContext.Bind(context)方法,這也是Model綁定遞歸的過程之一。這種情況會出現在初始Model類型是複雜類型並且其屬性中也有複雜類型。

 

複雜類型綁定器提供程序- 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類型中本身的實現,而是其本質的實現是SimpleModelBinderProvider類型來完成的,分爲檢查Model的前綴和不檢查兩種。這個自行看一下就知道了。

 

我們看下整體的結構圖。

1

wKiom1QayEnSqZ0HAAFLFlblcFs621.jpg

當然了還有其他的ModelBinder類型這裏就不一一講解了。最後我們看一下模擬複雜綁定的示意圖。

2

wKioL1QayHfiIjorAANhslNAj_g965.jpg

這裏的Product是一個複雜類型,其中有三個string類型的屬性,執行的順序爲紅、藍、黃。這裏要說明的就是除了紅色部分進入HttpActionContextExtensions之後不會再次遞歸,其他藍色和***部分均有可能,只要碰到有複雜類型。

大概的說明一下流程代碼部分在示例篇章會貼出來,首先在Product類型進行綁定的時候會先獲取到Product的類型,然後根據當前框架中註冊的一系列ModelBinder提供程序進行篩選獲取到可以對複雜類型進行綁定的ModelBinder對象,在上圖中也就是MutableObjectModelBinder類型,在MutableObjectModelBinder類型處理中會將Product類型的所有屬性Model元數據進行封裝,封裝爲ComplexModelDto對象實例,然後MutableObjectModelBinder類型會生成一個ModelBindingContext1對象實例,調用HttpActionContext類型的擴展方法類型HttpActionContextExtensions中的方法Bind()進行Model綁定,在Bind()方法中會重複紅色線條流程部分,意思就是說會根據ModelBindingContext1對象實例中的Metadata屬性獲取到Model類型,剛纔我們也說過了Model類型被封裝爲ComplexModelDto類型了,而後根據這個類型進行篩選獲取到ComplexModelDtoModelBinderProvider提供程序,隨之生成ComplexModelDtoModelBinder實例,在ComplexModelDtoModelBinder執行Model綁定的處理過程中,會遍歷ComplexModelDto類型實例中的每一項屬性元數據並且生成對應的ModelBindingContext,在上圖也就是ModelBindingContext2以及在ModelBindingContext2執行綁定操作後的ModelBindingContext3。在ModelBindingContext2生成完畢之後會再次的調用HttpActionContext類型的擴展方法類型HttpActionContextExtensions中的方法Bind()進行Model綁定,因爲Product中的屬性都是string類型所以不存在複雜類型,按照上圖中的順序大家可以看出,如果是複雜類型則會重新執行到紅色線條的起始部分。因爲這個時候是string類型所以篩選出的提供程序類型爲TypeConverterModelBinderProvider,從而生成TypeConverterModelBinder實例爲之綁定。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章