模型綁定
這一章主要記錄一下MVC模型綁定
一.認識模型綁定
官方詳細介紹模型綁定的資料我沒找到,只是在MSDN上講DefaultModelBinder
類時介紹了一下:將瀏覽器請求映射到數據對象。
這句話剛看上去不大明白意思,還是用自己的話總結一下:
模型綁定實際上是:
服務器端代碼利用用戶在表單中輸入的數據(或其它HTTP請求攜帶的數據),來構造動作方法所需要的參數對象的過程。數據的流向是從客戶端的HTML表單到服務器端動作方法。
更進一層的解釋是:
當我們在瀏覽器輸入一個地址即訪問一個動作時,動作調用器會負責在調用方法之前獲取該方法所需的所有參數。而默認動作調用器依賴於模型綁定來獲取動作方法中的參數值。其中每一個參數依賴於各自的模型綁定器,它們可能是用戶自定義的模型綁定器,也可能是默認的模型綁定器。
下面從一個簡單的示例開始演示一下默認模型綁定器。
二.使用默認模型綁定
1)綁定簡單類型
打開VS,新建一個空模板的MVC 3項目如下:在Models文件夾下面新建一個Sheep類文件,如下:
namespace MvcModelBind.Models
{
public class Sheep
{
private string _name = "No name!";
public string Name
{
get { return _name; }
set { _name = value; }
}
}
}
在Controllers文件夾下添加一個控制器,如下:在該控制器下新建一個方法,注意添加Sheep的引用:
public ActionResult ModelBindSheep(Sheep sheep)
{
return View(sheep);
}
F6編譯項目,爲上面的方法新建一個強類型視圖,如下:將視圖代碼修改爲:
@model MvcModelBind.Models.Sheep
@{
ViewBag.Title = "ModelBindSheep";
}
<h2>ModelBinding示例:綁定Sheep對象的Name屬性</h2>
@using (Html.BeginForm("ModelBindSheep", "Sheep"))
{
@:小羊的名字:@Html.TextBoxFor(n => n.Name, new { style = "background-color:#FF83FA" })<br />
@:目前小羊的名字是:@Model.Name <br />
<input type="submit" name="btn" value="提交" />
}
運行查看網頁:注意將網址改成對應的:/控制器名/action名:更改名字,點擊提交按鈕會看到名字隨之變化,這裏斷點進action方法裏看一下,具體的方法參數值:
實際上,上面的模型綁定流程大體上可以分爲如下:
1)當我們點擊提交按鈕時,模型綁定器發現頁面需要綁定一個Sheep類,而且這個類有一個string型的屬性Name需要賦值;
2)於是模型綁定器就要找能爲這個Name屬性賦值的數據源。它首先在表單中查找,結果發現TextBox的name值是“Name”,於是就認爲這個控件的值可以爲Sheep類 的Name屬性賦值;
3)於是模型綁定器就將textBox的value值取出來,轉化成能爲Sheep類的Name屬性賦值的類型(這裏轉化爲string),並將轉化後的值賦給Sheep類的Name屬性;
4)賦值完成後,模型綁定器就能構造出一個Sheep類的實例對象,該對象裏的屬性值都是剛剛從表單中取出綁定過來的。然後將這個實例對象送給動作調用器;
5)動作調用器在調用action方法之前,將收到的實例對象注入到動作方法的參數裏去。對應上面的示例,就是注入到參數sheep中。
經過上述幾部,纔有我們在斷點時監視到的參數sheep的Name屬性值隨TextBox值變化而變化的情形。
如果要說什麼是默認模型綁定,那麼上面的便是咯
注:上面演示的示例,模型綁定器是在表單中找到與參數名(Name)匹配的數據源的;實際上默認的模型綁定器不僅僅會從表單中查找,它查找數據源的順序是:
1.用戶在HTML表單form中提供的值;(即Request.Form)
2.從應用程序路由獲得的值;(即RouteData.Values)
3.URL中查詢字符串部分的值;(即Request.QueryString)
4.請求中的上傳文件。(即Request.Files)
以上,默認綁定器只要找到一個值,搜索便會停止。
2)綁定複合類型
a)綁定複合類型簡單示例
上面綁定的僅僅是一個string型的屬性Name,接下來綁定一個複雜類型AdditionalInformation AddInfo其中AdditionalInformation 定義如下:
namespace MvcModelBind.Models
{
public class AdditionalInformation
{
public string Country { get; set; }
public string City { get; set; }
}
}
在Sheep類中添加一個新的複合屬性AddInfo:
public class Sheep
{
private string _name = "No name!";
public string Name
{
get { return _name; }
set { _name = value; }
}
public AdditionalInformation AddInfo { get; set; }
}
更改ModelBindSheep方法對應的視圖代碼爲:@model MvcModelBind.Models.Sheep
@{
ViewBag.Title = "ModelBindSheep";
}
<h2>ModelBinding示例:綁定Sheep對象的Name屬性</h2>
@using (Html.BeginForm("ModelBindSheep", "Sheep"))
{
@:小羊的名字:@Html.TextBoxFor(n => n.Name, new { style = "background-color:#FF83FA" })<br />
@:目前小羊的名字是:@Model.Name <br />
@:小羊所在國家: @Html.EditorFor(c=>c.AddInfo.Country) <br />
@:小羊所在的城市: @Html.EditorFor(c=>c.AddInfo.City) <br />
<input type="submit" name="btn" value="提交" />
}
運行查看網頁,對應的國家及城市Html源碼: 小羊所在國家: <input class="text-box single-line" id="AddInfo_Country" name="AddInfo.Country" type="text" value="" /> <br />
小羊所在的城市: <input class="text-box single-line" id="AddInfo_City" name="AddInfo.City" type="text" value="" /> <br />
可以看到name屬性是模型Sheep類的AddInfo屬性名+對應的AdditionalInformation類的Country/City屬性名組合而成。實際上可以完全手動寫上面的HTML代碼,而不必借用Html輔助器,只不過用支持Lambda表達式的Html輔助器生成網頁源碼比較方便而已。
b)指定自定義前綴
前面所有的示例都有一個共同的特點就是:視圖中僅僅綁定了一個模型類,即一個Sheep類型的model
@model MvcModelBind.Models.Sheep
我在視圖代碼中增加另一個Sheep實例:@using MvcModelBind.Models;
@model MvcModelBind.Models.Sheep
@{
Sheep anotherSheep = new Sheep
{
Name = "Tom",
AddInfo = new AdditionalInformation { Country = "America", City = "sanfrancisco" }
};
}
@{
ViewBag.Title = "ModelBindSheep";
}
<h2>ModelBinding示例:綁定Sheep對象的Name屬性</h2>
@using (Html.BeginForm("ModelBindSheep", "Sheep"))
{
@:這是當前小羊的名字: @Html.EditorFor(a=>a.Name) <br />
@:這是當前小羊的國家: @Html.EditorFor(a=>a.AddInfo.Country) <br />
@:這是當前小羊的城市: @Html.EditorFor(a=>a.AddInfo.City)
<p >--------------------------------------------------------</p>
@:這是額外小羊的名字: @Html.EditorFor(a=>anotherSheep.Name) <br />
@:這是額外小羊的國家: @Html.EditorFor(a=>anotherSheep.AddInfo.Country) <br />
@:這是額外小羊的城市: @Html.EditorFor(a=>anotherSheep.AddInfo.City) <br />
<input type="submit" name="btn" value="提交" />
}
編譯運行,無論我如何修改虛線下面的三個TextBox的值,點擊提交按鈕後這三個TextBox始終是顯示:這說明虛線下面的三個根本就沒參與模型綁定。在對應的動作方法中添加一個參數:
public ActionResult ModelBindSheep(Sheep sheep,Sheep sheep1)
{
return View(sheep);
}
編譯運行,還是和上面一樣,無論我如何修改虛線下面的三個TextBox的值,點擊提交按鈕後這三個TextBox始終是恢復上圖的樣子。其實模型綁定器在查找sheep1對應的參數值時,會去查找name爲sheep1.Name/AddInfo.Country/AddInfo.City的數據源,發現壓根就找不到,所以點擊提交按鈕後,虛線下面的三個TextBox又恢復原值。
看一下頁面這三個TextBox對應的name:
<p >--------------------------------------------------------</p>
這是額外小羊的名字: <input class="text-box single-line" id="anotherSheep_Name" name="anotherSheep.Name" type="text" value="Tom" /> <br />
這是額外小羊的國家: <input class="text-box single-line" id="anotherSheep_AddInfo_Country" name="anotherSheep.AddInfo.Country" type="text" value="America" /> <br />
這是額外小羊的城市: <input class="text-box single-line" id="anotherSheep_AddInfo_City" name="anotherSheep.AddInfo.City" type="text" value="sanfrancisco" /> <br />
可見,這些name都有一個前綴:anotherSheep,這是我在視圖中定義的額外Sheep類的實例名。這樣一來,我就可以將動作方法中第二個參數名改成anotherSheep,來實現正確的模型綁定:
public ActionResult ModelBindSheep(Sheep sheep, Sheep anotherSheep)
{
return View(sheep);
}
這時候,編譯運行,修改這三個TextBox值,提交頁面,會發現值都會隨之變化了;其實除了將方法的第二個參數名改成anotherSheep外,還有另一種方法,見下:
public ActionResult ModelBindSheep(Sheep sheep,[Bind(Prefix="anotherSheep")] Sheep sheep1)
{
return View(sheep);
}
該方式是利用Bind註解屬性來告訴模型綁定器找數據源時,應該找數據源name以什麼來開頭的。c)綁定或不綁定某些屬性
假如我不想綁定Sheep中的Name屬性,只想綁定AddInfo屬性,但是我前臺頁面又有對應的Name數據源,因爲綁定器會自動找到並綁定它。可以利用Bind註解屬性:
public ActionResult ModelBindSheep([Bind(Include="AddInfo")] Sheep sheep)
{
return View(sheep);
}
或: public ActionResult ModelBindSheep([Bind(Exclude="Name")] Sheep sheep)
{
return View(sheep);
}
或者在類定義上使用: [Bind(Exclude = "Name")]
public class Sheep
{
private string _name = "No name!";
public string Name
{
get { return _name; }
set { _name = value; }
}
public AdditionalInformation AddInfo { get; set; }
}
或: [Bind(Include = "AddInfo")]
public class Sheep
{
private string _name = "No name!";
public string Name
{
get { return _name; }
set { _name = value; }
}
public AdditionalInformation AddInfo { get; set; }
}
但是一個需要注意的是:如果在類定義上使用了:
[Bind(Exclude = "Name")]
又在動作方法參數上使用了:[Bind(Include="Name")]
那麼,這個Name屬性仍然是不會被綁定的!3)綁定到集合
上面的示例均是綁定的單獨對象,即使使用額外對象也是屈指可數,實際應用中可能會要求綁定幾十到幾百個對象。下面以一個簡單的示例介紹綁定到集合,將原先的視圖代碼修改爲:
@using MvcModelBind.Models;
@model List<MvcModelBind.Models.Sheep>
@{
ViewBag.Title = "ModelBindSheep";
int i = 0;
}
<h2>綁定到集合</h2>
@using (Html.BeginForm("ModelBindSheep", "Sheep"))
{
for ( i = 0; i < Model.Count; i++)
{
@:第 @(i+1) 只羊的名字: @Html.EditorFor(n=>n[i].Name) <br />
@:第 @(i+1) 只羊的國家: @Html.EditorFor(n=>n[i].AddInfo.Country) <br />
@:第 @(i+1) 只羊的城市: @Html.EditorFor(n=>n[i].AddInfo.City) <br />
<p>----------------------------------------------------</p>
}
<input type="submit" name="btn" value="提交" />
}
對應的方法修改爲:
public ActionResult ModelBindSheep(List<Sheep> sheeps)
{
if (sheeps==null)
{
sheeps = new List<Sheep>()
{
new Sheep { Name="Tom" , AddInfo=new AdditionalInformation{Country="China",City="ShangHai"}},
new Sheep { Name="Jack" , AddInfo=new AdditionalInformation{Country="America",City="sanfrancisco"}},
new Sheep { Name="Jerry" , AddInfo=new AdditionalInformation{Country="France",City="London"}},
};
}
return View(sheeps);
}
編譯運行:對應表單中的源碼:
<form action="/Sheep/ModelBindSheep" method="post">
第 1 只羊的名字: <input class="text-box single-line" name="[0].Name" type="text" value="Tom" />
<br />
第 1 只羊的國家: <input class="text-box single-line" name="[0].AddInfo.Country" type="text" value="China" />
<br />
第 1 只羊的城市: <input class="text-box single-line" name="[0].AddInfo.City" type="text" value="ShangHai" />
<br />
<p>----------------------------------------------------</p>
第 2 只羊的名字: <input class="text-box single-line" name="[1].Name" type="text" value="Jack" />
<br />
第 2 只羊的國家: <input class="text-box single-line" name="[1].AddInfo.Country" type="text" value="America" />
<br />
第 2 只羊的城市: <input class="text-box single-line" name="[1].AddInfo.City" type="text" value="sanfrancisco" />
<br />
<p>----------------------------------------------------</p>
第 3 只羊的名字: <input class="text-box single-line" name="[2].Name" type="text" value="Jerry" />
<br />
第 3 只羊的國家: <input class="text-box single-line" name="[2].AddInfo.Country" type="text" value="France" />
<br />
第 3 只羊的城市: <input class="text-box single-line" name="[2].AddInfo.City" type="text" value="London" />
<br />
<p>----------------------------------------------------</p>
<input type="submit" name="btn" value="提交" />
</form>
修改對應TextBox的值,提交表單後,可以看到值都隨之變化,說明綁定成功!4)綁定非數字索引的集合
這裏藉助於隱藏Input元素,作爲指定數據的的鍵,只要將表單中的視圖代碼修改爲:@using (Html.BeginForm("ModelBindSheep", "Sheep"))
{
<input type="hidden" name="index" value="firstSheep" />
@:第 @(i+1) 只羊的名字: @Html.Editor("[firstSheep].Name") <br />
@:第 @(i+1) 只羊的國家: @Html.Editor("[firstSheep].AddInfo.Country") <br />
@:第 @(++i) 只羊的城市: @Html.Editor("[firstSheep].AddInfo.City") <br />
<p>----------------------------------------------------</p>
<input type="hidden" name="index" value="secondSheep" />
@:第 @(i+1) 只羊的名字: @Html.Editor("[secondSheep].Name") <br />
@:第 @(i+1) 只羊的國家: @Html.Editor("[secondSheep].AddInfo.Country") <br />
@:第 @(++i) 只羊的城市: @Html.Editor("[secondSheep].AddInfo.City") <br />
<p>----------------------------------------------------</p>
<input type="hidden" name="index" value="thirdSheep" />
@:第 @(i+1) 只羊的名字: @Html.Editor("[thirdSheep].Name") <br />
@:第 @(i+1) 只羊的國家: @Html.Editor("[thirdSheep].AddInfo.Country") <br />
@:第 @(i+1) 只羊的城市: @Html.Editor("[thirdSheep].AddInfo.City") <br />
<input type="submit" name="btn" value="提交" />
}
即可;5)綁定到字典
同樣藉助於隱藏input元素,只不過元素的name屬性變成了字典的鍵,修改視圖代碼爲如下:
@using MvcModelBind.Models;
@model Dictionary<string,MvcModelBind.Models.Sheep>
@{
ViewBag.Title = "ModelBindSheep";
int i = 0;
}
<h2>綁定到字典</h2>
@using (Html.BeginForm("ModelBindSheep", "Sheep"))
{
<input type="hidden" name="[0].key" value="firstSheep" />
@:第 @(i+1) 只羊的名字: @Html.Editor("[0].value.Name") <br />
@:第 @(i+1) 只羊的國家: @Html.Editor("[0].value.AddInfo.Country") <br />
@:第 @(++i) 只羊的城市: @Html.Editor("[0].value.AddInfo.City")
<p>----------------------------------------------------</p>
<input type="hidden" name="[1].key" value="secondSheep" />
@:第 @(i+1) 只羊的名字: @Html.Editor("[1].value.Name") <br />
@:第 @(i+1) 只羊的國家: @Html.Editor("[1].value.AddInfo.Country") <br />
@:第 @(++i) 只羊的城市: @Html.Editor("[1].value.AddInfo.City")
<p>----------------------------------------------------</p>
<input type="hidden" name="[2].key" value="thirdSheep" />
@:第 @(i+1) 只羊的名字: @Html.Editor("[2].value.Name") <br />
@:第 @(i+1) 只羊的國家: @Html.Editor("[2].value.AddInfo.Country") <br />
@:第 @(++i) 只羊的城市: @Html.Editor("[2].value.AddInfo.City") <br />
<input type="submit" name="btn" value="提交" />
}
對應的方法修改爲:
public ActionResult ModelBindSheep(Dictionary<string,Sheep> sheeps)
{
if (sheeps == null)
{
sheeps = new Dictionary<string, Sheep>();
sheeps.Add("firstSheep", new Sheep { Name="Tom" , AddInfo=new AdditionalInformation{Country="China",City="ShangHai"}});
sheeps.Add("secondSheep", new Sheep { Name = "Jack", AddInfo = new AdditionalInformation { Country = "America", City = "sanfrancisco" }});
sheeps.Add("thirdSheep", new Sheep { Name = "Jerry", AddInfo = new AdditionalInformation { Country = "France", City = "London" }});
}
return View(sheeps);
}
編譯運行,修改TextBox的值,點擊提交按鈕,同樣是可以實現綁定的!