天天看點

MVC 資料綁定

在做Asp.Net MVC項目中,都知道View負責頁面展示資料或者提供頁面收集資料,而所展示的資料或者收集的資料都是從Controller的Action中擷取或送出到Controller的Action。

這裡的資料,可能是基礎類型,或者是Model,或者是Model的部分内容,或者是集合比如List或Dictionary。

資料從View傳遞到Controller的Action時,有幾種方式,RouteData(url中的路由資料),QueryString(http get的查詢參數如?page=2),Forms(表單post的資料), 或者ajax互動的json資料。

而在Controller的action中,常常希望獲得這些View傳遞的資料,并且最好能綁定到Action希望的類型,這些類型可能是Action的基礎類型參數,或者需要更新的Model,或者隻更新Model的部分内容,或者是一些集合的結構如List或Dictionary。

古董方法:

[AcceptVerbs(HttpVerbs.Post)]

publicActionResultCreate()

{

Reciperecipe = newRecipe();

recipe.Name = Request.Form["Name"];

// ...

returnView();

}

前進一步:

publicActionResultCreate(FormCollectionvalues)

recipe.Name = values["Name"];

神奇的DefaultModelBinder:

Asp.Net Mvc内建功能(DefaultModelBinder)可以實作簡單類型、複雜類型、集合類型,以及字典類型的自動綁定。

1. 簡單類型

這裡,我們将下面這個Book類稱為簡單類型:

public class Book

public int BookId { get; set; }

public string BookName { get; set; }

public string Author { get; set; }

public DateTime PublishedDate { get; set; }

假設現在需要實作添加Book的功能,那麼在BookController中,會定義如下的Action:

public ActionResult Create(Book book) {

//TO DO

//Insert book into Database

return RedirectToAction("Index");

現在的問題便是,在View中如何命名TextBox來達到自動綁定,如下:

<div>

<%using (Html.BeginForm("Create", "Book")) { %>

Book Name: <%=Html.TextBox("BookName")%>

</div>

Author: <%=Html.TextBox("Author")%>

Published Date: <%=Html.TextBox("PublishedDate")%>

<input type="submit" id="submit" name="submit" value="submit" />

<%} %>

注意TextBox的name必須是對應綁定類型的PropertyName(不區分大小寫)。 這樣,頁面表單submit後,我們便可以在BookController的“Create” Action中得到自動綁定的book對象。這裡,Asp.Net Mvc還支援在TextBox的name中加上變量名稱字首的情形:

Book Name: <%=Html.TextBox("book.BookName")%>

Author: <%=Html.TextBox("book.Author")%>

Published Date: <%=Html.TextBox("book.PublishedDate")%>

需要注意的是:

1)字首"book"必須與Action中接收的Book參數名一緻

2)如果加了字首,那麼所有的都要加

2. 複雜類型

現在對Book類作些許改動,并引入Author類:

public Author Author { get; set; }

public class Author {

public int AuthorId { get; set; }

public string AuthorName { get; set; }

public string Nation { get; set; }

這裡,将改動後的Book類稱為複雜類。這時,Book類多了一個對Author類的引用。現在,保持BookController中的"Create" Action不變,來看View中的TextBox改如何命名以實作Book類型的自動綁定:

Author's Name: <%=Html.TextBox("Author.AuthorName")%>

Author's Nation: <%=Html.TextBox("Author.Nation")%>

OK,測試通過,想必你也知道命名規則了,要綁定Book類型中的Author類型,必須加上"Author."的字首。

如果你喜歡,你還可以在所有TextBox名稱前面再加"book."的字首。

3. 集合類型

為避免問題複雜化,我們用回原來的簡單Book類型:

現在,把BookController的"Create" Action改為接收IList<Book>的參數:

public ActionResult Create(IList<Book> books) {

然後,在View中運用以下命名規則,以自動綁定IList<book>類型,

Book Name: <%=Html.TextBox("books[0].BookName")%>

Published Date: <%=Html.TextBox("books[0].PublishedDate")%>

Author's Name: <%=Html.TextBox("books[0].Author")%>

Book Name: <%=Html.TextBox("books[1].BookName")%>

Published Date: <%=Html.TextBox("books[1].PublishedDate")%>

Author's Name: <%=Html.TextBox("books[1].Author")%>

可以看到如下規則:Action的變量名"books"加上中括号和索引作為字首,索引必須從0開始,并且必須連續。

通過此命名規則,可以綁定任意集合類型:IList<Book>, ICollection<Book>, IEnumerable<Book>, List<Book>, Book[]等。

4. 字典類型

仍以簡單Book類型為例,現在将"Create" Action改為接收IDictionary<Book>類型,

public ActionResult Create(IDictionary<string, Book> books) {

相應的,View中的命名如下:

Book SN: <%=Html.TextBox("books[0].Key") %>

Book Name: <%=Html.TextBox("books[0].Value.BookName")%>

Published Date: <%=Html.TextBox("books[0].Value.PublishedDate")%>

Author's Name: <%=Html.TextBox("books[0].Value.Author")%>

Book SN: <%=Html.TextBox("books[1].Key") %>

Book Name: <%=Html.TextBox("books[1].Value.BookName")%>

Published Date: <%=Html.TextBox("books[1].Value.PublishedDate")%>

Author's Name: <%=Html.TextBox("books[1].Value.Author")%>

可以看出,對于IDictioinary<Book>類型,在命名規則上,相比于集合類型,多了"Key"和"Value”的字眼。另,此規則也适用于Action參數類型為Dictionary<Book>的情形。

簡單類型、複雜類型、集合類型,以及字典類型 還能混合着用,而且綁定的資料來源也不局限于Form送出的表達資料,還可以是RouteData(url中的路由資料),QueryString(http get的查詢參數如?page=2),或者ajax互動的json資料。

是以,隻要我們遵循一定的命名規則,靈活的運用DefaultModelBinder 就可以輕松實作各種類型的自動綁定了。

BindAttribute:

DefaultModelBinder 已經很強大了,而在有些時候BindAttribute則會為你錦上添花。

BindAttribute中有三個重要的成員:string Exclude, string Include, string Prefix。

"Exclude"用于指定要排除的Property,"Include"用于指定包含在内的Property,"Prefix"用于指定字首。

看個例子,

Book類:

Create Action:

對應的View:

先說,"Prefix"吧,預設情況下,DefaultModelBinder可以自動識别以Action的參數名命名的字首,也就是說,在上述View中,給每個TextBox的名稱都加一個"book“字首,不需要在做任何改動,在Action中仍可得到Book的執行個體。

加上字首”book"的View:

這時,如果我們加的字首不是"book",那麼就需要BindAttribute的協助了,

假設,我們加上一個"b"的字首:

那麼,為了在Action中得到相應的Book執行個體,需要在Action的Book參數上應用BindAttribute:

現在來看"Exclude"和"Include",其實這兩個東西一次應用一個就可以了。現在我希望Binding的時候将"BookName"和"Author"都排除(當然,這裡這樣做沒什麼意義)。出于簡化問題的考慮,View去掉TextBox名稱的字首:

然後,将Action改為:

預設情況下,多個Property用逗号隔開。

BindAttribute出了可以應用在Action的參數上外,還可以應用在Model類定義中:

如果在Model類定義中,和在Action的參數上都應用了BindAttribute,那麼則會取兩者的交集。個人認為,還是應盡量避免它們打架為妙。

是以,BindAttribute 對于字首的使用及部分Model的綁定很有用。

TryUpdateModel/UpdateModel

Mvc中經常遇到的一個典型用例是View通過From或者Ajax送出資料更新相應的Model,或者更新Model的部分内容,并且需要檢查送出的資料對于Model的資料限制是否有效。

這個時候TryUpdateModel就有用武之地了,它可以設定需要更新模型屬性清單的(白名單)或要排除屬性的清單(黑名單) 先看更新相關模型的所有屬性:

publicActionResult Edit(int id, FormCollection collection)

var oldData = _r.GetSingleData(id);

if(TryUpdateModel(oldData, collection.AllKeys))

_r.Save();

更新除ID,Name外的Model屬性:

publicActionResult Edit(Guid id, FormCollection collection)

if(TryUpdateModel(oldData,"", collection.AllKeys,newstring[]{"ID","Name"}))

publicActionResult Save()

Customer customer =newCustomer();

try {

UpdateModel(customer,new[] {"Name","Email",

"Phone","Deposit"});

return RedirectToAction("...");

catch(InvalidOperationException)

returnView(customer);

繼續閱讀