在上篇中介紹了自動綁定的基本用法,在本篇中,我們將深入瞭解自動綁定的工作原理.
自動綁定的確是讓人感到興奮的特性,然而,為了讓它可以在我們的項目中更好的工作,我們有必要深入瞭解如何更進一步細調該特性以及它是如何工作的.而本文正式即將揭開這個謎底.
為了更好的瞭解該特性,我們有必要到codeplex去下載一份asp.net mvc的原始碼並分析之,在本文寫作的時候,codeplex上已經放上了beta版的源碼,如果想進一步瞭解的朋友可以下載並對照本文分析.
在beta版中,新增了自動綁定這一特性,並對綁定特性做了一定的修改
1. 新增BindAttribute:自動綁定特性設定
2. 修改DefaultModelBinder:自動綁定的實現部分
3. 修改ControllerActionInvoker:綁定的調用入口
4. 新增ModelBinderContext,封裝綁定所需資料
5. 新增BinderResult,封裝綁定結果
其他不大重要的修改略過
我們一步步來分析綁定的執行過程,首先肯定在ControllerActionInvoker中,看到GetParameterValue方法:
這兒便是對每個參數都嘗試調用ModelBinder來綁定參數,這兒的GetModelBinder方法和P5的一樣,在我們自訂ModelBinder的情況下可以進行自訂綁定,然而在自動綁定的時候擷取的則是DefaultModelBinder,然後在GetPropertyFilter方法中通過查閱BindAttribute來擷取關於綁定的設定.最後對資料進行綁定.
關鍵的,我們需要對DefaultModelBinder進行分析,然而在此之前,還有一個類也是需要我們仔細看看的,那就是BindAttribute,該特性是用來修飾參數的,它有4個重要的屬性:Include,Exclude,Prefix和一個方法:IsPropertyAllowed,分別用來設定:綁定的欄位,不綁定的欄位,參數首碼和判斷給定的欄位是否設定運行綁定,且該方法會作為一個Predicate委託封入ModelBinderContext傳入BindModel方法.
現在來討論使用預設綁定的情況,首先給出DefaultModelBinder的所有方法:
分別簡介下這些方法的作用:
1. BindModel:對外的調用介面,根據傳入的ModelBinderContext綁定值
2. BindModelCore:綁定自訂類型,自訂類型數組或者自訂類型字典
3. BindProperty:綁定某個指定的屬性,(此處是一個遞迴調用,仍然調用DefaultModelBinder進行屬性的綁定,也就是說,理論上DefaultModelBinder可以對任意深度的屬性進行綁定)
4. ConvertSimpleArrayType,ConvertSimpleType,用來做類型轉換,一個轉換數組一個轉換普通類型
5. CreateArray:建立一個數組對象
6. CreateModel:建立一個普通對象
7. CreateSubIndexName:建立子索引名,命名方式為prefix[indexName]
8. CreateSubPropertyName:建立子屬性名稱,命名方式為prefix.propertyName
9. GetBinder:擷取modelType的Binder對象
10. GetElementType:擷取一個類型的ElementType
11. GetSimeType:對給定的ModelBinderContext進行簡單值綁定(也就是調用該方法時假定擷取的資料是簡單類型)
12. IsCollectionInserface:判斷是否是數群組類型
13. IsDictinaryInterface:判斷是否是字典類型
14. IsSimpleType:是否是簡單類型(在這兒判斷是否是實值型別或者string)
15. TryUpdateSimpleCollection:嘗試綁定一個簡單數組(即綁定Collection或者Collection,且T能通過IsSimpleType)
16. UpdateCollection:綁定一個數組,該數組一般為Collection,且T是自訂類型
17. UpdateDictionary:綁定一個字典類型
通過以上方法的瀏覽,我們發現,DefaultModelBinder可以進行繫結資料很多,包括簡單類型(實值型別和string).自訂類型,數組,字典,同時由於採用了遞迴調用,理論上可以綁定任意深度的資料,由於這兒綁定的調用比較複雜,且本人表達能力有限,本文將不再詳細講述綁定的工作流程,下面將總結下綁定的規則:
1. 預設綁定採用反射的方式將資料繫結到所需對象上
2. 綁定參數有可選特性BindAttribute定義
3. BindAttribute中可以手動設定允許綁定的屬性或者不允許綁定的屬性,url取參首碼
4. 預設情況下所有屬性都進行綁定
5. DefaultModelBinder採用遞迴對所有需要綁定的參數進行綁定
6. 普通參數的命名方式為:prefix.protertyName,且預設情況下prefix為類型名.如對象MyData擁有類型為string的屬性Name,那麼在ValueProvider中取值的名稱應該為mydata.name,命名不分大小寫
7. 簡單數組命名方式為prefix.protertyName,其中proertyName為數組名,可以存在多個
8. 對於自訂類型數組和字典,綁定必須提供如下參數:
1. index-必須提供一個或多個,指示綁定的子項目名,可存在多個
2. 數組必須提供一個或者多個以protertyName[indexName].subProtertyName形式結尾的表單資料,其中protertyName為數組名,indexName為index中定義過的名稱,subProteryName為自訂類型的屬性名稱.
3. 字典必須提供一個或者多個protertyName[indexName].key和protertyName[indexName].value的表單資料,其中protertyName為字典名稱,indexName為index中定義過的名稱,key代表字典中的key綁定,value代表字典中的value綁定,如果key或者value為非簡單類型,表單的定義繼續參照前面.
9. 對於以上綁定,如果子級屬性為複雜類型,可以依次按照此規則命名表單資料
下面我們通過例子來分別描述上面的規則:
在體驗篇中,我們看到對article的表單命名為article.title,article.content等,這就是根據第6條,父類和屬性用.來間隔,對簡單資料的命名,article.tags,多個同名表單代表多個數族元素,參考第7條,在這兒,如果在article參數中顯式定義了prefix,則需要對應對錶單名進行修改.對於屬性為自訂類型的,繼續使用.間隔,比如上文中的article.advancearticle.today等,此處參考第7,9條,這些部分的例子可以參考第一篇,本篇中將不再說明.
本篇中將重點介紹自訂類型數組,字典以及多級屬性綁定,下面展示如何綁定一個ICollection,T為自訂類型,比如在AdvanceArticle中添加一個屬性Reads,這是一個自訂類型,包含Name和Source,執行個體代碼如下;
然後根據規則在表單aspx中加入:
這兒給這個數組傳入了兩個元素,分別對應article.reads.index中的0和aa,下面的article.reads[0].name和article.reads[0].source表示第一個元素的子屬性,依次類推.如果您在應用中需要在數組中添加更多元素,可以採用js動態添加表單域的方法,不過每次添加必須對應添加index和對應的子屬性工作表單.
然後是綁定Dictinary,我們在AdvanceArticle中繼續加入新屬性:
然後在aspx中加入表單:
和數組類似的,需要定義article.source.index,此處也顯式定義了兩個字典元素,分別是user和vip(注意,這兒的user和vip並不代表字典key,字典key是需要從表單綁定的),然後分別定義article.source[user].key和article.source[user].key等.如果有更多的元素,也需要按照此規則加入.
同樣的,這兒的key和value也不局限於簡單類型,如果key和value是自訂類型,則可以按照前面的規則繼續向下定義.
最後需要說明幾點: By Leven
2008-10-22
1. 綁定取值不限於表單域,在Beta中加入了ValueProvider,只要是能在ValueProvider中能取到的值都能綁定.
2. 自動綁定是基於反射,如果你嫌效率不夠高,請採用自訂ModelBinder的方式對特定的類型進行特定的綁定已提升效率
3. 自動綁是單向的,如果想實現在route中能接受該參數,可以採用重寫綁定對象的ToString方法輸出對應QueryString的方式,也就是說,如果想在調用Url.Action(“xxx”,new { article= myArticle })能正確傳入傳出參數,可以重寫Article的ToString方法,在提供一個類似article.title=xxx&article.content=xxx的字串格式提供一個協助類,將將資料分別放入ValueProvider,而調用Url方法時採用類似Url.Action(“xxx”,article.ToRouteValue())的方法.
By Leven
2008-10-22