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>對象結構如下圖所示。
當調用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(" {0}: {1}<br/>", "Foo", innerValueProvider.GetValue("Foo").RawValue));
27: sb.Append(string.Format(" {0}: {1}<br/>", "Bar", innerValueProvider.GetValue("Bar").RawValue));
28: sb.Append(string.Format(" {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