閱讀565 返回首頁    go 阿裏雲 go 技術社區[雲棲]


ASP.NET MVC以ValueProvider為核心的值提供係統: DictionaryValueProvider

NameValueCollectionValueProvider采用一個NameValueCollection作為數據源,DictionnaryValueProvider的數據源類型自然就是一個Dictionnary。NameValueCollection和Dictionnary都是一個鍵值對的集合,它們之間的不同之處在NameValueCollection運行元素具有相同的Key,Dictionnary卻要求元素的Key具有唯一性。[本文已經同步到《How ASP.NET MVC Works?》中]

目錄
一、DictionaryValueProvider<TValue>
二、RouteDataValueProvider
三、HttpFileCollectionValueProvider
四、ChildActionValueProvider
五、實例演示:ChildActionValueProvider的值提供機製
六、ValueProviderCollection

DictionnaryValueProvider的類型全名為System.Web.Mvc.DictionaryValueProvider<TValue>,如下麵的代碼片斷所示,DictionaryValueProvider<TValue>實現了IEnumerableValueProvider和IValueProvider接口,構造函數接受一個IDictionary<string, TValue>對象,該對象表示作為數據源的字典。定義在DictionaryValueProvider<TValue>中所有方法的邏輯與定義在NameValueCollectionValueProvider中的同名方法並沒有本質區別。

   1: public class DictionaryValueProvider<TValue> : IEnumerableValueProvider, IValueProvider
   2: {
   3:     public DictionaryValueProvider(IDictionary<string, TValue> dictionary, CultureInfo culture);
   4:     public virtual bool ContainsPrefix(string prefix);
   5:     public virtual IDictionary<string, string> GetKeysFromPrefix(string prefix);
   6:     public virtual ValueProviderResult GetValue(string key);
   7: }

將當前路由數據作為數據源的RouteDataValueProvider繼承自DictionaryValueProvider<TValue>。如下麵的代碼片斷所示,基於當前Controller上下文構建的RouteDataValueProvider直接將表示當前路由數據的RouteData對象的Values屬性(這是一個RouteValueDictionary對象)作為數據來源。

   1: public sealed class RouteDataValueProvider : DictionaryValueProvider<object>
   2: {
   3:     public RouteDataValueProvider(ControllerContext controllerContext) : 
   4:         base(controllerContext.RouteData.Values, CultureInfo.InvariantCulture)
   5:     {
   6:     }
   7: }

我們可以通過類型為file的輸入元素進行文件的上傳,在表示HTTP請求的HttpRequestBase對象中,上傳文件通過隻讀屬性Files表示。從下麵的代碼片斷所示,該屬性類型為HttpFileCollectionBase,是一個元素類型為HttpPostedFileBase的集合。

   1: public abstract class HttpRequestBase
   2: {   
   3:     public virtual HttpFileCollectionBase Files { get; }
   4: }
   5: public abstract class HttpFileCollectionBase : NameObjectCollectionBase, ICollection, IEnumerable
   6: {   
   7:     public virtual string[] AllKeys { get; }
   8:     public override int Count { get; }
   9:     public virtual HttpPostedFileBase this[int index] { get; }
  10:     public virtual HttpPostedFileBase this[string name] { get; }
  11: }
  12: public abstract class HttpPostedFileBase
  13: {
  14:     public virtual void SaveAs(string filename);
  15:  
  16:     public virtual int ContentLength { get; }
  17:     public virtual string ContentType { get; }
  18:     public virtual string FileName { get; }
  19:     public virtual Stream InputStream { get; }
  20: }

用於處理上傳文件的Action方法通常定義類型為HttpPostedFileBase及其列表的參數來表示上傳的文件,針對HttpPostedFileBase參數的Model綁定選用的數據就來源於表示當前請求的HttpRequestBase的Files屬性,而具體參數值的提供最終通過具有如下定義的HttpFileCollectionValueProvider來實現。

   1: public sealed class HttpFileCollectionValueProvider : DictionaryValueProvider<HttpPostedFileBase[]>
   2: {    
   3:     public HttpFileCollectionValueProvider(ControllerContext controllerContext);
   4: }

如上麵的代碼所示,HttpFileCollectionValueProvider繼承自DictionaryValueProvider<TValue>,泛型參數TValue的類型為HttpPostedFileBase數組,這是因為在同一個表單中可以定義多個同名的文件輸入元素,所以在以文件元素名稱作為Key的字典中,字典元素的值自然就是一個HttpPostedFileBase的列表。

為了讓讀者對HttpFileCollectionValueProvider采用的針對上傳文件的值對象提供機製具有一個深刻的認識,我們來進行一個簡單的實例演示。在通過Visual Studio的ASP.NET MVC項目模板創建的空Web應用中創建一個具有如下定義的HomeController。該Controller類型中定義了兩個Action方法,默認的Index方法會將默認的View呈現出來,DisplayPostedFiles方法則通過創建的HttpFileCollectionValueProvider對象將上傳文件的文件名稱呈現出來。

   1: public class HomeController : Controller
   2: {
   3:     public ActionResult Index()
   4:     {
   5:         return View();
   6:     }
   7:     [HttpPost]
   8:     public void DisplayPostedFiles()
   9:     {
  10:         HttpFileCollectionValueProvider valueProvider = new HttpFileCollectionValueProvider(ControllerContext);
  11:         IEnumerable<HttpPostedFileBase> foo = (IEnumerable<HttpPostedFileBase>)valueProvider.GetValue("foo").ConvertTo(typeof(IEnumerable<HttpPostedFileBase>));
  12:         IEnumerable<HttpPostedFileBase> bar =  (IEnumerable<HttpPostedFileBase>)valueProvider.GetValue("bar").ConvertTo(typeof(IEnumerable<HttpPostedFileBase>));
  13:  
  14:         Response.Write("foo<br/>");
  15:         foreach (var file in foo)
  16:         {
  17:             Response.Write(file.FileName + "<br/>");
  18:         }
  19:  
  20:         Response.Write("<br/>bar<br/>");
  21:         foreach (var file in bar)
  22:         {
  23:             Response.Write(file.FileName + "<br/>");
  24:         }            
  25:     }
  26: }

在DisplayPostedFiles方法中,我們針對當前Controller上下文創建HttpFileCollectionValueProvider對象,然後分別將字符“foo”和“bar”作為Key得到兩個HttpPostedFileBase對象列表,並將它們的文件名打印出來。下麵的代碼表示Action方法Index對應的View。在一個針對Action方法DisplayPostedFiles的表單中我們定義了三個文件輸入元素,其中前兩個名稱為“foo”和“bar”。

   1: @{
   2:    using(Html.BeginForm("DisplayPostedFiles","Home", 
   3:        FormMethod.Post,new {enctype="multipart/form-data"}))
   4:    {
   5:        <ul>
   6:         <li>File 1: <input type="file" name="foo"/></li>
   7:         <li>File 2: <input type="file"  name="foo"/></li>
   8:         <li>File 3: <input type="file"  name="bar"/></li>
   9:        </ul>  
  10:        <input type="submit" value="提交" />       
  11:     }
  12: }

當我們運行該程序的時候,瀏覽器上會出現一個包含三個文件輸入元素和提交按鈕的頁麵。然後我們從本地選擇任意三個文件(比如text1.txt、text2.txt和text2.txt)並點擊“提交”按鈕,界麵上會出現如下所示的輸出結果。

   1: foo
   2: text1.txt
   3: text2.txt
   4:  
   5: bar
   6: text3.txt

子Action和普通意義上的Action的不同之處在於它不能用於響應來自客戶端的請求,而在某個View中被調用以生成某個部分的HTML。View中針對某個子Action方法的調用通過如下所示的HtmlHelper的擴展方法Action來實現。

   1: public static class ChildActionExtensions
   2: {
   3:     //其他成員
   4:     public static MvcHtmlString Action(this HtmlHelper htmlHelper, string actionName);
   5:     public static MvcHtmlString Action(this HtmlHelper htmlHelper, string actionName, object routeValues);
   6:     public static MvcHtmlString Action(this HtmlHelper htmlHelper, string actionName, string controllerName);
   7:     public static MvcHtmlString Action(this HtmlHelper htmlHelper, string actionName, RouteValueDictionary routeValues);
   8:     public static MvcHtmlString Action(this HtmlHelper htmlHelper, string actionName, string controllerName, object routeValues);
   9:     public static MvcHtmlString Action(this HtmlHelper htmlHelper, string actionName, string controllerName, RouteValueDictionary routeValues);  
  10: }

顧名思義,ChildActionValueProvider專門服務於針對子Action方法參數的Model綁定。如下麵的代碼片斷所示,ChildActionValueProvider依然是DictionaryValueProvider<TValue>的繼承者,不過這裏的泛型參數類型Object。那麼在作為數據源的字典中,具體的Key和Value究竟是怎樣一個對象呢?

   1: public sealed class ChildActionValueProvider : DictionaryValueProvider<object>
   2: {
   3:     public ChildActionValueProvider(ControllerContext controllerContext);
   4:     public override ValueProviderResult GetValue(string key);
   5: }

當我們創建針對指定的Controller上下文創建一個ChildActionValueProvider對象時,會,這可以從如下所示的ChildActionValueProvider的構造函數定義看出來。

   1: public sealed class ChildActionValueProvider : DictionaryValueProvider<object>
   2: {
   3:     //其他成員   
   4:     public ChildActionValueProvider(ControllerContext controllerContext) :  base(controllerContext.RouteData.Values, CultureInfo.InvariantCulture)
   5:     {
   6:     }
   7: }

但是ChildActionValueProvider的GetValue方法獲取的值卻,不然ChildActionValueProvider就和RouteDataValueProvider沒有什麼分別了。實際上,ChildActionValueProvider的GetValue方法獲取的值來源於調用HtmHelper的擴展方法Action時通過參數routeValues指定的RouteValueDictionary。

現在我們來簡單介紹一下定義在ChildActionValueProvider的GetValue方法中的對象值的提供機製。如下麵的代碼片斷所示,ChildActionValueProvider具有一個字符串類型的靜態字段。當該類型第一次被加載時,該字段被初始化成一個GUID。

   1: public sealed class ChildActionValueProvider : DictionaryValueProvider<object>
   2: {
   3:     //其他成員
   4:     private static string _childActionValuesKey = Guid.NewGuid().ToString();
   5: }

在某個View中通過HtmlHelper的擴展方法Action執行子Action方法時,如果通過參數routeValues指定的RouteValueDictionary不為空,會基於這個對象創建一個DictionaryValueProvider<TValue>對象。然後將這個對象添加到通過routeValues表示的原始的RouteValueDictionary對象中,對應的Key就是ChildActionValueProvider的靜態屬性_childActionValuesKey表示的GUID。

這個RouteValueDictionary被進一步封裝成表示請求上下文的RequestContext對象,目標子Action所在的Controller會在該請求上下文中被激活,而在Controller激活過程中表示Controller上下文的ControllerContext被創建出來,毫無疑問它包含了之前創建的RouteValueDictionary對象。而我們針對當前Controller上下文創建ChildActionValueProvider的時候指定的作為數據源的RouteValueDictionary對象就是這麼一個對象。

   1: @Html.Action("XxxChildAction", new {Foo=123, Bar = 456, Baz=789})

舉個例子,假設我們在某個View中如果如下的方式調用當前Controller的子Action方法 XxxChildAction,並指定相應的路由數據(Foo、Bar和Baz)。最終作為ChildActionValueProvider數據源的Dictionary<string,object>對象結構如下圖所示。

image

當調用ChildActionValueProvider的GetValue方法獲取指定Key的值時,實際上並不會直接根據指定的Key去獲取對應的值,而是。然後再調用該對象的GetValue根據指定的Key去獲得相應的值。

為了印證上麵介紹的關於ChildActionValueProvider的值提供機製,我們來演示一個簡單的實例。在進行演示之前有一個點需要作一下簡單說明,對於DictionaryValueProvider<TValue>對象來說,最終作為其數據源的通過私有字段_values表示的一個Dictionary<string, ValueProviderResult對象。當我們調用GetValue方法是,隻需要根據指定的Key從該字典對象中返回相應的ValueProviderResult即可。

   1: public class DictionaryValueProvider<TValue> : IEnumerableValueProvider, IValueProvider
   2: {
   3:     //其他成員
   4:     private readonly Dictionary<string, ValueProviderResult> _values;
   5: }

在通過Visual Studio的ASP.NET MVC 項目模板創建的空Web應用中定義如下一個默認的HomeController。默認的Action方法Index僅僅是將默認的View呈現出來而已,並沒有特別之處。在另一個Action方法DisplayRouteData中,我們名稱分別為Foo、Bar和Baz的三個路由數據篡改成“abc”、“ijk”和“zyz”。然後根據當前Controller上下文創建一個ChildActionValueProvider對象,並通過反射的方式獲取通過它的私有字段_values表示的Dictionary<string, ValueProviderResult對象。

   1: public class HomeController : Controller
   2: {
   3:     public ActionResult Index()
   4:     {
   5:         return View();
   6:     }
   7:  
   8:     public ActionResult DisplayRouteData()
   9:     {
  10:         ControllerContext.RouteData.Values["Foo"] = "abc";
  11:         ControllerContext.RouteData.Values["Bar"] = "ijk";
  12:         ControllerContext.RouteData.Values["Baz"] = "xyz";
  13:  
  14:         StringBuilder sb = new StringBuilder();
  15:         ChildActionValueProvider valueProvider = new ChildActionValueProvider(ControllerContext);
  16:         FieldInfo valuesField = typeof(DictionaryValueProvider<object>).GetField("_values", BindingFlags.Instance|BindingFlags.NonPublic);
  17:         Dictionary<string, ValueProviderResult> values = (Dictionary<string, ValueProviderResult>)valuesField.GetValue(valueProvider);
  18:         foreach (string key in values.Keys)
  19:         {
  20:             sb.Append(string.Format("{0}: {1}<br/>", key, values[key].RawValue));
  21:             DictionaryValueProvider<object> innerValueProvider = values[key].RawValue as DictionaryValueProvider<object>;
  22:             if (innerValueProvider == null)
  23:             {
  24:                 continue;
  25:             }
  26:             sb.Append(string.Format("&nbsp;&nbsp;&nbsp;&nbsp;{0}: {1}<br/>", "Foo", innerValueProvider.GetValue("Foo").RawValue));
  27:             sb.Append(string.Format("&nbsp;&nbsp;&nbsp;&nbsp;{0}: {1}<br/>", "Bar", innerValueProvider.GetValue("Bar").RawValue));
  28:             sb.Append(string.Format("&nbsp;&nbsp;&nbsp;&nbsp;{0}: {1}<br/>", "Baz", innerValueProvider.GetValue("Baz").RawValue));
  29:         }
  30:  
  31:         sb.Append("<br/>ChildActionValueProvider.GetValue()<br/>");
  32:         sb.Append(string.Format("{0}: {1}<br/>", "Foo", valueProvider.GetValue("Foo").RawValue));
  33:         sb.Append(string.Format("{0}: {1}<br/>", "Bar", valueProvider.GetValue("Bar").RawValue));
  34:         sb.Append(string.Format("{0}: {1}<br/>", "Baz", valueProvider.GetValue("Baz").RawValue));            
  35:  
  36:         return Content(sb.ToString());
  37:     }
  38: }

我們創建一個StringBuilder對象,並將用於輸出獲取到的Dictionary<string, ValueProviderResult>對象的Key和Value的HTML添加其中。在進行遍曆過程中,如果ValueProviderResult對象的RawValue屬性是一個DictionaryValueProvider<object>對象,則調用其GetValue方法得到Key分別為Foo、Bar和Baz的值。相應的輸出的HTML一並添加到StringBuilder中。

在程序的最後,我們直接調用ChildActionValueProvider的GetValue方法獲取針對Foo、Bar和Baz作為Key的值,並將輸出Key和Value的HTML添加到StringBuilder中。最終針對生成的HTML字符串返回一個ContentResult對象。如下所示的代碼反映Action方法Index對應的View的定義,在這裏我們直接調用HtmlHelper的擴展方法Action執行定義在HomeController的Action方法DisplayRouteData,並指定了相應的路由數據(Foo、Bar和Baz)。

   1: @Html.Action("DisplayRouteData", new { Foo = 123, Bar = 456, Baz = 789 })

運行我們的程序會在瀏覽器中得到如下的輸出結果。我們可以從中看到針對於Controller和Action名稱的路由數據和調用HtmlHelper擴展方法Action指定的數據數據均在作為ChildActionValueProvider數據源的字典對象中。除此之外,還具有一個DictionaryValueProvider<object>對象,對應的Key是一個GUID,這正是我們上麵介紹的針對在HtmlHelper擴展方法Action中指定的路由數據創建的DictionaryValueProvider<object>對象,而調用GetValue方法獲取到的值最終是通過它提供的。

   1: Foo: abc
   2: Bar: ijk
   3: Baz: xyz
   4: controller: Home
   5: action: DisplayRouteData
   6: 289594f6-dfba-45b9-8abb-158b4a582911: 
   7:     System.Web.Mvc.DictionaryValueProvider`1[System.Object]
   8:     Foo: 123
   9:     Bar: 456
  10:     Baz: 789
  11:  
  12: ChildActionValueProvider.GetValue()
  13: Foo: 123
  14: Bar: 456
  15: Baz: 789

類型ValueProviderCollection不僅僅表示一個ValueProvider對象的集合,還。如下麵的代碼片斷所示,ValueProviderCollection不僅僅繼承自Collection<IValueProvider>,還同時實現了IValueProvider、IEnumerableValueProvider和IUnvalidatedValueProvider三個接口。

   1: public class ValueProviderCollection : Collection<IValueProvider>, IUnvalidatedValueProvider, IEnumerableValueProvider, IValueProvider
   2: {
   3:     public ValueProviderCollection();
   4:     public ValueProviderCollection(IList<IValueProvider> list);
   5:  
   6:     public virtual bool ContainsPrefix(string prefix);
   7:     public virtual IDictionary<string, string> GetKeysFromPrefix(string prefix);  
   8:     public virtual ValueProviderResult GetValue(string key);
   9:     public virtual ValueProviderResult GetValue(string key, bool skipValidation);   
  10: }

對於兩個實現值提供機製的GetValue方法重載來說,ValueProviderCollection會遍曆集合直到找到一個GetValue方法返回值不為Null的ValueProvider,而該返回值就是該方法的返回值。如果所有ValueProvider的GetValue方法均返回Null,則ValueProviderCollection的GetValue方法也為Null。也就是說,。

如果有任何一個ValueProvider的ContainsPrefix方法返回True,則ValueProviderCollection的ContainsPrefix也返回True。GetKeysFromPrefix方法的邏輯與GetValue方法類似,它會遍曆作為集合中實現了IEnumerableValueProvider接口的所有ValueProvider對象,並將指定的前綴作為參數調用ContainsPrefix方法,如果返回值為True,則直接返回GetKeysFromPrefix方法的結果;否則返回一個空的Dictionary<string, string>對象。

ASP.NET MVC以ValueProvider為核心的值提供係統: NameValueCollectionValueProvider
ASP.NET MVC以ValueProvider為核心的值提供係統: DictionaryValueProvider
ASP.NET MVC以ValueProvider為核心的值提供係統: ValueProviderFactory


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-26 11:34:18

  上一篇:go  ASP.NET MVC以ValueProvider為核心的值提供係統: NameValueCollectionValueProvider
  下一篇:go  ASP.NET MVC以ValueProvider為核心的值提供係統: ValueProviderFactory