天天看點

MVC學習筆記六:模型綁定【下】模型綁定

模型綁定

一.手工調用模型綁定

使用前面示範的綁定字典的一個示例: 将方法參數去掉:

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資料又恢複原樣了(如下圖)。可見根本就沒發生模型綁定。

MVC學習筆記六:模型綁定【下】模型綁定

那怎樣才能不添加動作方法參數,又要執行模型綁定呢?隻要在方法中加一句話就可以:

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用于儲存上傳的檔案;

MVC學習筆記六:模型綁定【下】模型綁定

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");
            }
            
        }
           

3)為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。

編譯運作,上傳幾張圖檔試試:

MVC學習筆記六:模型綁定【下】模型綁定

OK,成功上傳!

總結,關于上面的示例,需要澄清一下原理: 1)當我選擇浏覽好檔案後,确定; 2)點選開始上傳按鈕,這時候表單被送出了。于是就執行了HandleUploadFiles方法,該方法有一個參數files,而且注解屬性要求使用我的自定義綁定器; 3)自定義綁定器根據HandleUploadFiles的參數名(這裡是files)找到頁面中name屬性的值也是files的資料源;這裡是:

<input type="file" name="files" />
           

4)于是自定義綁定器接收該資料源的值,并轉化成HandleUploadFiles參數需要的對象(這裡是檔案清單); 5)動作調用器接收上面傳過來的檔案清單參數,并執行HandleUploadFiles方法;

于是就成功利用自定義模型綁定上傳了檔案

MVC學習筆記六:模型綁定【下】模型綁定

以上,HandleUploadFiles參數名和頁面file控件的name屬性名必須一緻,否則模型綁定會失敗。