[Web API] Web API 2 深入系列(7) Model繫結(下)

Never、C發表於2016-10-28

目錄

  1. ModelBinder

  2. ModelBinderProvider

  3. 不同型別的Model繫結

    • 簡單型別
    • 複雜型別
    • 其他型別

ModelBinder

ModelBinder是Model繫結的核心.

public interface IModelBinder
{
    //繫結Model方法,返回繫結是否成功
    bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext);
}

ModelBindingContext

public class ModelBindingContext
{
    //資料來源
    public IValueProvider ValueProvider { get; set; }
    //最終建立的物件 繫結過程就是建立Model
    public object Model { get; set; }
    //引數名稱
    public string ModelName { get; set; }
    //引數型別
    public Type ModelType { get; }

    //引數後設資料
    public ModelMetadata ModelMetadata { get; set; }
    //屬性後設資料
    public IDictionary<string, ModelMetadata> PropertyMetadata { get; }

    //儲存繫結結果(包括錯誤資訊,ValueProviderResult),該值引用自ApiController的ModelState
    public ModelStateDictionary ModelState { get; set; }
    
    public bool FallbackToEmptyPrefix { get; set; }
}

當ModelBinder特性的Name(為null),FallbackToEmptyPrefix為True.
FallbackToEmptyPrefix為False時,則必須資料來源有字首才能繫結上.

Web API定義了一系列的ModelBinder,這裡先介紹

public class CompositeModelBinder : IModelBinder
{
    //IModelBinder 集合
    public CompositeModelBinder(params IModelBinder[] binders)
    //使用內部ModelBinder進行繫結Model(預設遍歷一次,如果FallbackToEmptyPrefix為True,則會有2次遍歷機會)
    public virtual bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
}

ModelBinderProvider

Web API透過ModelBinderProvider建立ModelBinder

public abstract class ModelBinderProvider
{
    public abstract IModelBinder GetBinder(HttpConfiguration configuration, Type modelType);
}

Web API中預設註冊了一系列的ModelBinderProvider

    this.SetMultiple<ModelBinderProvider>(new ModelBinderProvider[8]
    {
        (ModelBinderProvider) new TypeConverterModelBinderProvider(),
        (ModelBinderProvider) new TypeMatchModelBinderProvider(),
        (ModelBinderProvider) new KeyValuePairModelBinderProvider(),
        (ModelBinderProvider) new ComplexModelDtoModelBinderProvider(),
        (ModelBinderProvider) new ArrayModelBinderProvider(),
        (ModelBinderProvider) new DictionaryModelBinderProvider(),
        (ModelBinderProvider) new CollectionModelBinderProvider(),
        (ModelBinderProvider) new MutableObjectModelBinderProvider()
    });

對應的我們先介紹一下

//同樣都是組裝一批ModelBinderProvider
public sealed class CompositeModelBinderProvider : ModelBinderProvider
{
    public CompositeModelBinderProvider(IEnumerable<ModelBinderProvider> providers)
    public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType)
}

ModelBinderAttribute除了透過Name設定FallbackToEmptyPrefix,還有個更重要的屬性BinderType

public class ModelBinderAttribute : ParameterBindingAttribute
{
    public string Name { get; set; }
    //用來指定ModelBinder 或 ModelBinderProvider
    public Type BinderType { get; set; }
}

不同型別的Model繫結

不同的資料型別,有不同的資料結構.
所以針對他們的繫結機制也是不同的.

簡單型別

TypeConverterModelBinder

public sealed class TypeConverterModelBinder : IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
        model = valueProviderResult.ConvertTo(bindingContext.ModelType);
        bindingContext.Model = model;
        return true;
    }
}

複雜型別

在介紹複雜型別的Bind前,先介紹一下下面2個型別.

ComplexModelDto包含複雜型別的後設資料和繫結結果

public class ComplexModelDto
{
    public ComplexModelDto(ModelMetadata modelMetadata, IEnumerable<ModelMetadata> propertyMetadata)

    public ModelMetadata ModelMetadata { get; private set; }
    public Collection<ModelMetadata> PropertyMetadata { get; private set; }
    //key 為屬性的後設資料,value 為繫結結果
    public IDictionary<ModelMetadata, ComplexModelDtoResult> Results { get; private set; }
}

ComplexModelDtoResult

public sealed class ComplexModelDtoResult
{
    //繫結結果值
    public object Model { get; private set; }
    //驗證資訊
    public ModelValidationNode ValidationNode { get; private set; }
}

複雜型別的繫結由 MutableObjectModelBinder 和 ComplexModelDtoModelBinder共同完成.

而上面2個型別,實際就是用來在2個ModelBinder間傳遞的物件

public class MutableObjectModelBinder : IModelBinder
{
    public virtual bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        //型別篩選
        if (!bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName) || !MutableObjectModelBinder.CanBindType(bindingContext.ModelType))
            return false;
        //1. 建立空物件
        bindingContext.ModelMetadata.Model = Activator.CreateInstance(bindingContext.ModelType);
        //2. 建立ComplexModelDto物件
        ComplexModelDto complexModelDto = new ComplexModelDto(bindingContext.ModelMetadata,bindingContext.PropertyMetadata);
        //3. 進入繫結流程
        this.ProcessDto(actionContext, bindingContext, complexModelDto);
        return true;
    }

    internal void ProcessDto(HttpActionContext actionContext, ModelBindingContext bindingContext, ComplexModelDto dto)
    {
        //建立子ModelBindingContext
        var subContext = new ModelBindingContext(bindingContext)
        {
            ModelName = bindingContext.ModelName,
            ModelMetadata = GetMetadata(typeof(ComplexModelDto))
        };
        //呼叫ComplexModelDtoModelBinder 對ComplexModelDto進行繫結
        actionContext.Bind(subContext);

        //對複雜型別的屬性進行繫結
        foreach (var result in (IEnumerable<KeyValuePair<ModelMetadata, ComplexModelDtoResult>>) dto.Results)
        {
            ModelMetadata key = result.Key;
            ComplexModelDtoResult dtoResult = result.Value;
            if (dtoResult != null)
            {
                var property = bindingContext.ModelType.GetProperty(key.PropertyName);
                this.SetProperty(actionContext, bindingContext, key, dtoResult,property);
            }
        }
    }
}

ComplexModelDtoModelBinder則將所有屬性再透過排程其他的ModelBinder進行繫結.並將結果儲存在ComplexModelDto的Results屬性中.

其他型別

Web API還提供了其他常用的資料型別 ModelBinder

CollectionModelBinder來實現 集合 型別(指的集合是實現了IEnumerable介面的型別)

ArrayModelBinder來實現 陣列 型別

DictionaryModelBinder來實現 字典 型別

透過對應的ModelBinderProvider可以知道對應的ModelBinder 能實現哪種型別的繫結

備註

相關文章