模型綁定
一.手工調用模型綁定
使用前面演示的綁定字典的一個示例:
將方法參數去掉:
public ActionResult ModelBindSheep()
{
Dictionary<string, Sheep> sheeps=null;
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);
}
前臺代碼改爲:@using MvcModelBind.Models;
@model Dictionary<string, MvcModelBind.Models.Sheep>
@{
ViewBag.Title = "ModelBindSheep";
int i = 0;
}
<h2>綁定到字典</h2>
@{
ViewData["[0].value.Name"] = Model["firstSheep"].Name;
ViewData["[0].value.AddInfo.Country"] = Model["firstSheep"].AddInfo.Country;
ViewData["[0].value.AddInfo.City"] = Model["firstSheep"].AddInfo.City;
ViewData["[1].value.Name"] = Model["secondSheep"].Name;
ViewData["[1].value.AddInfo.Country"] = Model["secondSheep"].AddInfo.Country;
ViewData["[1].value.AddInfo.City"] = Model["secondSheep"].AddInfo.City;
ViewData["[2].value.Name"] = Model["thirdSheep"].Name;
ViewData["[2].value.AddInfo.Country"] = Model["thirdSheep"].AddInfo.Country;
ViewData["[2].value.AddInfo.City"] = Model["thirdSheep"].AddInfo.City;
}
@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="提交" />
}
編譯運行。修改TextBox框的值,可以發現,修改完成,點擊提交按鈕之後,TextBox數據又恢復原樣了(如下圖)。可見根本就沒發生模型綁定。那怎樣才能不添加動作方法參數,又要執行模型綁定呢?只要在方法中加一句話就可以:
public ActionResult ModelBindSheep()
{
Dictionary<string, Sheep> sheeps=null;
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" }});
}
UpdateModel(sheeps);//就是這一句了
return View(sheeps);
}
編譯運行。修改TextBox框的值,修改完成,點擊提交按鈕之後,TextBox數據隨之變化,即發生模型綁定了~UpdateModel這個方法很有很有意思,它將模型綁定過來的對象覆蓋到當前對象,所以無論我如何初始化sheeps,經過UpdateModel之後,sheeps對象就會被覆蓋成頁面最新綁定過來的對象!
// 摘要:
// Updates the specified model instance using values from the controller's current
// value provider.
//
// 參數:
// model:
// The model instance to update.
//
// 類型參數:
// TModel:
// The type of the model object.
//
// 異常:
// System.InvalidOperationException:
// The model was not successfully updated.
protected internal void UpdateModel<TModel>(TModel model) where TModel : class;
二.限制綁定到的特定數據源
之前介紹過,默認的模型綁定器,查找數據源的順序是:
1.用戶在HTML表單form中提供的值;(即Request.Form)
2.從應用程序路由獲得的值;(即RouteData.Values)
3.URL中查詢字符串部分的值;(即Request.QueryString)
4.請求中的上傳文件。(即Request.Files)
那如果我只想讓綁定器查找表單中的數據,怎麼辦呢?
很簡單,只需要給上述方法加一個參數即可:
public ActionResult ModelBindSheep()
{
Dictionary<string, Sheep> sheeps=null;
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" }});
}
UpdateModel(sheeps,new FormValueProvider(ControllerContext));//就是第二個參數
return View(sheeps);
}
或者: public ActionResult ModelBindSheep(FormValueProvider formData)
{
Dictionary<string, Sheep> sheeps = null;
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" } });
}
UpdateModel(sheeps, formData);//就是第二個參數
return View(sheeps);
}
上述是隻想讓其查找表單中的數據源,若要限制成只查找其它數據源,只需UpdateModel第二個參數即可,四個數據源位置對應的參數類型分別是:
源 | IValueProvider實現 |
Request.Form | FormValueProvider |
RouteData.Values | RouteDataValueProvider |
Request.QueryString | QueryStringValueProvider |
Request.Files | HttpFileCollectionValueProvider |
注:如果使用UpdateModel方法,就要小心了。如果我們綁定的數據源的值並不能綁定到相應模型屬性的值(如模型屬性是個數字型,而綁定的數據源的值是字符串型,這時候模型綁定可能就會出異常,所以就要藉助try...catch:
try
{
UpdateModel(sheeps,new FormValueProvider(ControllerContext));
}
catch (InvalidOperationException ex)
{
//...
}
或者藉助TryUpdateModel: if (TryUpdateModel(sheeps,new FormValueProvider(ControllerContext)))
{
//...
}
三.自定義模型綁定,實現文件上傳
想要自定義模型綁定,只要自己定義一個類,讓其繼承IModelBinder接口即可,下面隨意定義一個類:
public class DefineModelBind : IModelBinder
繼承該接口的話,就需要自己來完成接口中的BindModel方法啦,因爲我要上傳文件,那麼提交表單後,我的action參數肯定是一個文件集合咯,所以BindModel方法就是返回一個文件集合: public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (bindingContext == null)
{
throw new ArgumentNullException("bindingContext");
}
//獲得用戶已經上傳的文件集合
HttpFileCollectionBase uploadFiles = controllerContext.HttpContext.Request.Files;
//獲得需要綁定的模型名稱(即action的參數名稱,亦即file控件的name屬性名稱)
string modelName = bindingContext.ModelName;
//獲得與模型名稱匹配的文件,並將它們轉化成列表
List<HttpPostedFileBase> postedFiles = uploadFiles.AllKeys
.Select((thisKey, index) => (String.Equals(thisKey, modelName, StringComparison.OrdinalIgnoreCase)) ? index : -1)
.Where(index => index >= 0)
.Select(index => uploadFiles[index])
.ToList();
//返回綁定好的對象(這裏是一個文件集合)
if (postedFiles.Count == 0)
{
return null;
}
else
{
return postedFiles;
}
}
光有上面的具體實現代碼是不行的,因爲MVC壓根就不知道我定義的這個模型綁定,還要在Global.asax.cs文件中註冊一下我自定義的綁定器: protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
// 默認情況下對 Entity Framework 使用 LocalDB
Database.DefaultConnectionFactory = new SqlConnectionFactory(@"Data Source=(localdb)\v11.0; Integrated Security=True; MultipleActiveResultSets=True");
//利用我的自定義綁定器來綁定一個HttpPostedFileBase列表
ModelBinders.Binders[typeof(List<HttpPostedFileBase>)] = new DefineModelBind();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
OK,一切搞定,這時候就可以實驗一下能否上傳文件了,爲了簡單演示,我就以上傳圖片爲例:1)在網站根目錄下新建一個文件夾Uploads用於保存上傳的文件;
2)修改控制器中的Index方法:
public ActionResult Index()
{
var model = Directory.GetFiles(Server.MapPath(@"\Uploads\")).AsEnumerable().ToList(); //獲取Uploads目錄下的圖片列表
for (int i = 0; i < model.Count(); i++)
{
string[] filesplit = model[i].Split('\\'); //把路徑拆分成數組
model[i] = filesplit[filesplit.Length - 1]; //最後一項就是文件名稱
}
if (model!=null)
{
return View(model);
}
else
{
return View();
}
}
並添加另一個方法:
public ActionResult HandleUploadFiles([ModelBinder(typeof(DefineModelBind))] IList<HttpPostedFileBase> files)
{
if (files.Count() == 0)
{
return RedirectToAction("Index");
}
foreach (HttpPostedFileBase file in files)
{
if (file==null)
{
return RedirectToAction("Index");
}
if (file.ContentLength != 0)
{
string[] filesplit = file.FileName.Split('\\'); //把路徑拆分成數組
string fileName = filesplit[filesplit.Length - 1]; //最後一項就是文件名稱
file.SaveAs(Server.MapPath(@"\Uploads\") + fileName); //保存到服務器Uploads目錄下
}
}
var model = Directory.GetFiles(Server.MapPath(@"\Uploads\")).AsEnumerable().ToList(); //獲取Uploads目錄下的圖片列表
for (int i = 0; i < model.Count(); i++)
{
string[] filesplit = model[i].Split('\\'); //把路徑拆分成數組
model[i] = filesplit[filesplit.Length - 1]; //最後一項就是文件名稱
}
if (model!=null)
{
return RedirectToAction("Index",model);
}
else
{
return RedirectToAction("Index");
}
}
@model IEnumerable<string>
@{
ViewBag.Title = "Index";
}
<h2>自定義模型綁定,綁定上傳文件!</h2>
@using (Html.BeginForm("HandleUploadFiles", "Upload", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<p><input type="file" name="files" /> </p>
<input type="submit" value="開始上傳" />
}
<script language="JavaScript" type="text/javascript">
function AdjustImageSize(img) {
img.width = img.width / 3;
}
</script>
@if (Model!=null)
{
<p>您已經上傳的圖片有:</p>
<br />
foreach (string fileName in Model.ToList())
{
<img src ="../../Uploads/@fileName" alt ="image" οnlοad="AdjustImageSize(this)" />
}
}
以上任何代碼若出現需解析引用的提示,需自行添加using。編譯運行,上傳幾張圖片試試:
OK,成功上傳!
總結,關於上面的示例,需要澄清一下原理:
1)當我選擇瀏覽好文件後,確定;
2)點擊開始上傳按鈕,這時候表單被提交了。於是就執行了HandleUploadFiles方法,該方法有一個參數files,而且註解屬性要求使用我的自定義綁定器;
3)自定義綁定器根據HandleUploadFiles的參數名(這裏是files)找到頁面中name屬性的值也是files的數據源;這裏是:
<input type="file" name="files" />
4)於是自定義綁定器接收該數據源的值,並轉化成HandleUploadFiles參數需要的對象(這裏是文件列表);5)動作調用器接收上面傳過來的文件列表參數,並執行HandleUploadFiles方法;
於是就成功利用自定義模型綁定上傳了文件以上,HandleUploadFiles參數名和頁面file控件的name屬性名必須一致,否則模型綁定會失敗。