Agenda
前言 MVC 的Model-Binding建立複雜物件(牽扯到複雜模型綁定.)
這篇會跟大家介紹MVC 是如何把達成這個複雜的動作
我有做一個可以針對於Asp.net MVC Debugger 的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.
IModelBinder(DefaultModelBinder) DefaultModelBinder將Http請求傳來資料轉換為強型別物件,DefaultModelBinder是如何取得使用Model資料呢?
實現IValueProvider來處理。
ModelBinders IModelBinder.BindModel方法使用兩個參數
1 public object BindModel (ControllerContext controllerContext, ModelBindingContext bindingContext )
ControllerContext:Controller資訊,
ModelBindingContext:當前參數綁定資訊
BindModel方法機於Http請求傳送資料進行Model綁定(對於Action方法使用參數),其中ModelBindingContext參數會提供綁定使用的重要物件成員.
關於ModelBindingContext建立我們會在後續部分進行的單獨介紹.
在IModelBinder.BindModel方法中主要透過兩個重要internal方法.
BindComplexModel:複雜參數綁定
BindSimpleModel:簡單參數綁定
下圖可以表示SimpleModel和ComplexModel
ComplexModel一個人可擁有多個房子,所以Person類別擁有HouseCollection引用. 取得使用ModelBinder機制。
取得ModelBinder會依照下面順序
參數掛有ModelBinderAttribute標籤並將BinderType屬性指向一個繼承IModelBinder型別.
參數掛有繼承CustomModelBinderAttribute類型
透過ModelBinderProviderCollection(預設MVC 沒有提供ModelBinderProvider)
預設DefaultModelBinder
下面兩個使用ModelBinder都是DefaultModelBinder,但一個是使用第一點,另一個使用第四點.
1 2 3 public ActionResult HttpModules (Person p )public ActionResult HttpModules ([ModelBinder(typeof (DefaultModelBinder ))]Person p)
在Global.cs可透過ModelBinders.Binders.Add方法註冊綁定類型.
如下面程式碼.
1 ModelBinders.Binders.Add(typeof (Arg),new FooModelBinder());
ModelBinderDictionary 一般參數透過DefaultModelBinder來幫我們完成參數綁定.
但有些特別的資料需要透過ModelBinderDictionary取得使用ModelBinder,例如上傳檔案,我們可以使用HttpPostedFileBase來取得檔案資訊流.
那是因為在ModelBinderDictionary有註冊一個HttpPostedFileBaseModelBinder來幫我們做解析.
1 2 3 4 5 6 7 8 9 10 11 private static ModelBinderDictionary CreateDefaultBinderDictionary (){ ModelBinderDictionary binders = new ModelBinderDictionary() { { typeof (HttpPostedFileBase), new HttpPostedFileBaseModelBinder() }, { typeof (byte []), new ByteArrayModelBinder() }, { typeof (Binary), new LinqBinaryModelBinder() }, { typeof (CancellationToken), new CancellationTokenModelBinder() } }; return binders; }
IValueProvider 提供參數填值 IValueProvider介面有一個重要方法GetValue會返回ValueProviderResult物件對於ValueProvider參數封裝
1 ValueProviderResult GetValue (string key )
ValueProvider工廠集合(ValueProviderFactories) 在ControllerBase類別中有一個屬性ValueProvider設定參數填值動作
1 2 3 4 5 6 7 8 9 10 11 12 public IValueProvider ValueProvider{ get { if (_valueProvider == null ) { _valueProvider = ValueProviderFactories.Factories.GetValueProvider(ControllerContext); } return _valueProvider; } set { _valueProvider = value ; } }
Http 傳送參數可能又多種模式(Post Form,Query String,Ajax….)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static class ValueProviderFactories { private static readonly ValueProviderFactoryCollection _factories = new ValueProviderFactoryCollection() { new ChildActionValueProviderFactory(), new FormValueProviderFactory(), new JsonValueProviderFactory(), new RouteDataValueProviderFactory(), new QueryStringValueProviderFactory(), new HttpFileCollectionValueProviderFactory(), }; public static ValueProviderFactoryCollection Factories { get { return _factories; } } }
ChildActionValueProviderFactory:取得另一個呼叫@Html.Action傳來Model 資料
FormValueProviderFactory:取得HTTP POST送來的資料
JsonValueProviderFactory:取得JSON資料(Content-Type = application/json)
RouteDataValueProviderFactory:取得從網址路徑取得到路由參數值
QueryStringValueProviderFactory:取得從Http請求的Query String資料
HttpFileCollectionValueProviderFactory:取得檔案上傳功能傳來檔案
如果此次請求匹配到多個ValueProvider機制會怎處理?
會按照上面ProviderFactory設定順序來排執行優先順序來填值
ValueProviderFactory MVC 利用工廠模式透過ValueProviderFactory實現的工廠來IValueProvider填值提供者物件.
JsonValueProviderFactory 在ValueProviderFactoryIValueProvider GetValueProvider
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 public sealed class JsonValueProviderFactory : ValueProviderFactory { private static void AddToBackingStore (EntryLimitedDictionary backingStore, string prefix, object value ) { IDictionary<string , object > d = value as IDictionary<string , object >; if (d != null ) { foreach (KeyValuePair<string , object > entry in d) { AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value); } return ; } IList l = value as IList; if (l != null ) { for (int i = 0 ; i < l.Count; i++) { AddToBackingStore(backingStore, MakeArrayKey(prefix, i), l[i]); } return ; } backingStore.Add(prefix, value ); } private static object GetDeserializedObject (ControllerContext controllerContext ) { if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json" , StringComparison.OrdinalIgnoreCase)) { return null ; } StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream); string bodyText = reader.ReadToEnd(); if (String.IsNullOrEmpty(bodyText)) { return null ; } JavaScriptSerializer serializer = new JavaScriptSerializer(); object jsonData = serializer.DeserializeObject(bodyText); return jsonData; } public override IValueProvider GetValueProvider (ControllerContext controllerContext ) { if (controllerContext == null ) { throw new ArgumentNullException("controllerContext" ); } object jsonData = GetDeserializedObject(controllerContext); if (jsonData == null ) { return null ; } Dictionary<string , object > backingStore = new Dictionary<string , object >(StringComparer.OrdinalIgnoreCase); EntryLimitedDictionary backingStoreWrapper = new EntryLimitedDictionary(backingStore); AddToBackingStore(backingStoreWrapper, String.Empty, jsonData); return new DictionaryValueProvider<object >(backingStore, CultureInfo.CurrentCulture); } }
取得IValueProvider 透過ValueProviderFactory返回相對應的IValueProvider物件.
下面介紹幾個實現ValueProvider物件
NameValueCollectionValueProvider NameValueCollectionValueProvider可從NameValueCollection集合取得參數.
因為Request.Form和Request.QueryString都是NameValueCollection類型集合.
這個方法很巧妙利用一個共同參數類型簽章來達成多態轉折點
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public virtual NameValueCollection Form{ get { } } public virtual NameValueCollection QueryString{ get { } }
Http 傳值到Server 有許多方式,這裡介紹MVC 利用哪個ValueProvider 將Form跟QueryString填值到物件上,很巧妙使用NameValueCollectionValueProvider建構子參數NameValueCollection決定是要使用Form或QueryString填充值到參數.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public sealed class FormValueProvider : NameValueCollectionValueProvider { public FormValueProvider (ControllerContext controllerContext ) : this (controllerContext, new UnvalidatedRequestValuesWrapper(controllerContext.HttpContext.Request.Unvalidated )) { } internal FormValueProvider (ControllerContext controllerContext, IUnvalidatedRequestValues unvalidatedValues ) : base (controllerContext.HttpContext.Request.Form, unvalidatedValues.Form, CultureInfo.CurrentCulture ) { } } public sealed class QueryStringValueProvider : NameValueCollectionValueProvider { public QueryStringValueProvider (ControllerContext controllerContext ) : this (controllerContext, new UnvalidatedRequestValuesWrapper(controllerContext.HttpContext.Request.Unvalidated )) { } internal QueryStringValueProvider (ControllerContext controllerContext, IUnvalidatedRequestValues unvalidatedValues ) : base (controllerContext.HttpContext.Request.QueryString, unvalidatedValues.QueryString, CultureInfo.InvariantCulture ) { } }
實現IValueProvider物件主要會依靠GetValue方法取得ValueProviderResult.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 [Serializable ] public class ValueProviderResult { private static readonly CultureInfo _staticCulture = CultureInfo.InvariantCulture; private CultureInfo _instanceCulture; protected ValueProviderResult () { } public ValueProviderResult (object rawValue, string attemptedValue, CultureInfo culture ) { RawValue = rawValue; AttemptedValue = attemptedValue; Culture = culture; } public string AttemptedValue { get ; protected set ; } public CultureInfo Culture { get { if (_instanceCulture == null ) { _instanceCulture = _staticCulture; } return _instanceCulture; } protected set { _instanceCulture = value ; } } public object RawValue { get ; protected set ; } public object ConvertTo (Type type ) { return ConvertTo(type, null ); } public virtual object ConvertTo (Type type, CultureInfo culture ) { } }
ValueProviderResult對於ValueProvider物件做封裝,一般存放Http參數擁有兩個只讀屬性
RawValue表示物件值
AttemptedValue主要用於顯示
ValueProviderResult提供兩個ConvertTo重載方法實現向指定目標類型轉換。
某些類型格式化依賴於相應的語言文化(比如時間、日期和貨幣等),這個語言文化通過Culture屬性來達成.
最終會呼叫一個UnwrapPossibleArrayType方法來建立物件
小結: 在ControllerActionInvoker.GetParameterValue取得參數方法,ModelBing動作有兩個重要的屬性
IValueProvider:提供如何填值
IModelBinder:建立物件(綁定關聯) 預設使用DefaultModelBinder類別.
目前分享的IValueProvider和IModelBinder UML類別關聯圖如下
下篇會介紹ModelBind模型綁定重點邏輯,有分簡單參數綁定和複雜參數綁定
BindComplexModel
BindSimpleModel
__此文作者__:Daniel Shih(石頭) __此文地址__: https://isdaniel.github.io/ithelp-day18/ __版權聲明__:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW 許可協議。轉載請註明出處!