MVC5模型繫結 - 2

by vivid 22. 三月 2017 12:33

.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號: N170318102
出刊日期: 2017/03/22

Model Binder是一個MVC的元件,根據HTTP請求傳送到伺服端的資料,來建立模型物件。本文將延續上一篇文章《MVC5模型繫結 - 1》介紹ASP.NET MVC 5預設模型繫結器(Default Model Binder)的基本應用。

複雜型別的繫結-自訂型別屬性

假設目前控制器程式如下,提供兩個Index方法,第一個Index方法回傳空白的Index檢視呈現空白表單以搜集使用者資料,標示HttpPost的第二個Index方法則接收一個Employee型別的參數,最後Employee物件丟到Result檢視來顯示,參考以下範例程式碼:

namespace ModelBindingDemo.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Index(Employee model)
        {
            return View("Result", model);
        }
    }
}

 

Employee模型中包含一個WorkExperience屬性,WorkExperience屬性的型別是一個自訂的WorkExperience類別,包含CompanyName與Years兩個屬性,參考以下範例程式碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ModelBindingDemo.Models
{
    public class Employee
    {
        public int EmployeeId { get; set; }
        public string EmployeeName { get; set; }
        public DateTime BirthDate { get; set; }
        public WorkExperience WorkExperience { get; set; }
    }

    public class WorkExperience
    {
        public string CompanyName { get; set; }
        public int Years { get; set; }
    }

}

 

Index 檢視若要顯示WorkExperience自訂型別屬性時,可以使用「WorkExperience.CompanyName」與「WorkExperience.Years」語法來搜集屬性值,參考以下範例程式碼:

@model ModelBindingDemo.Models.Employee

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>Employee</h4>
        <hr />
        <div class="form-group">
            @Html.LabelFor(model => model.EmployeeId, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.EmployeeId, new { htmlAttributes = new { @class = "form-control" } })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.EmployeeName, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.EmployeeName, new { htmlAttributes = new { @class = "form-control" } })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.BirthDate, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.BirthDate, new { htmlAttributes = new { @class = "form-control" } })
            </div>
        </div>


        <div class="form-group">
            @Html.LabelFor(model => model.WorkExperience.CompanyName, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.WorkExperience.CompanyName, new { htmlAttributes = new { @class = "form-control" } })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.WorkExperience.Years, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.WorkExperience.Years, new { htmlAttributes = new { @class = "form-control" } })
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

 

同樣的,Result檢視也可以使用來讀取屬性值,參考以下範例程式碼:

@model ModelBindingDemo.Models.Employee

@{
    ViewBag.Title = "Result";
}

<h2>Result</h2>

<div>
    <h4>Employee</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.EmployeeId)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.EmployeeId)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.EmployeeName)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.EmployeeName)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.BirthDate)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.BirthDate)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.WorkExperience.CompanyName)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.WorkExperience.CompanyName)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.WorkExperience.Years)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.WorkExperience.Years)
        </dd>
    </dl>
</div>
<p>
    @Html.ActionLink("Back to List", "Index")
</p>

 

執行結果請參考如下圖所示:

clip_image002

圖 1:執行結果測試。

Index檢視產生出的HTML分別為CompanyName與name兩個文字方塊設定了「name="WorkExperience.CompanyName"」「name="WorkExperience.Years"」,參考以下範例程式碼:

<form action="/Home/Index" method="post"><input name="__RequestVerificationToken" type="hidden" value="… 略" />    <div class="form-horizontal">
        <h4>Employee</h4>
        <hr />
        <div class="form-group">
            <label class="control-label col-md-2" for="EmployeeId">EmployeeId</label>
            <div class="col-md-10">
                <input class="form-control text-box single-line" data-val="true" data-val-number="The field EmployeeId must be a number." data-val-required="The EmployeeId field is required." id="EmployeeId" name="EmployeeId" type="number" value="" />
            </div>
        </div>

        <div class="form-group">
            <label class="control-label col-md-2" for="EmployeeName">EmployeeName</label>
            <div class="col-md-10">
                <input class="form-control text-box single-line" id="EmployeeName" name="EmployeeName" type="text" value="" />
            </div>
        </div>

        <div class="form-group">
            <label class="control-label col-md-2" for="BirthDate">BirthDate</label>
            <div class="col-md-10">
                <input class="form-control text-box single-line" data-val="true" data-val-date="The field BirthDate must be a date." data-val-required="The BirthDate field is required." id="BirthDate" name="BirthDate" type="datetime" value="" />
            </div>
        </div>


        <div class="form-group">
            <label class="control-label col-md-2" for="WorkExperience_CompanyName">CompanyName</label>
            <div class="col-md-10">
                <input class="form-control text-box single-line" id="WorkExperience_CompanyName" name="WorkExperience.CompanyName" type="text" value="" />
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="WorkExperience_Years">Years</label>
            <div class="col-md-10">
                <input class="form-control text-box single-line" data-val="true" data-val-number="The field Years must be a number." data-val-required="The Years field is required." id="WorkExperience_Years" name="WorkExperience.Years" type="number" value="" />
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
</form>

送出請求後攔截到的封包如下,Model Binder會利用WorkExperience.CompanyName與WorkExperience.Years的值來建立WorkExperience物件,放到Employee物件的WorkExperience屬性:

EmployeeId=1&EmployeeName=mary&BirthDate=2016%2F1%2F2&WorkExperience.CompanyName=Contoso&WorkExperience.Years=5

執行結果,請參考下圖所示:

clip_image004

圖 2:執行結果測試。

設定前綴字

假設我們想要為員工資料記錄工作經歷,並將工作經歷拆開來放到部分檢視中(Partial View)之中呈現。在Views\Home資料夾下,定義一個DisplayWrokExperience部分檢視,程式碼如下所示,使用Html.DisplayNameFor 方法顯示提示字串,使用Html.DisplayFor方法來顯示屬性值:

@model ModelBindingDemo.Models.WorkExperience

@{
    ViewBag.Title = "WorkExperience";
}

<h2>WorkExperience</h2>
<div>
    <h4>WorkExperience</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.CompanyName)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.CompanyName)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.Years)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Years)
        </dd>

    </dl>
</div>

 

在控制器定義一個DisplayWrokExperience方法,接收WorkExperience型別的參數,方法中將WorkExperience物件傳入DisplayWrokExperience部分檢視中顯示,參考以下範例程式碼:

namespace ModelBindingDemo.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Index(Employee model)
        {
            return View("Result", model);
        }

        public ActionResult DisplayWrokExperience(WorkExperience model)
        {
            return PartialView(model);
        }

    }
}

 

參考以下範例程式碼,目前Index檢視定義如下:

@model ModelBindingDemo.Models.Employee

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>Employee</h4>
        <hr />
        <div class="form-group">
            @Html.LabelFor(model => model.EmployeeId, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.EmployeeId, new { htmlAttributes = new { @class = "form-control" } })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.EmployeeName, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.EmployeeName, new { htmlAttributes = new { @class = "form-control" } })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.BirthDate, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.BirthDate, new { htmlAttributes = new { @class = "form-control" } })
            </div>
        </div>


        <div class="form-group">
            @Html.LabelFor(model => model.WorkExperience.CompanyName, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.WorkExperience.CompanyName, new { htmlAttributes = new { @class = "form-control" } })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.WorkExperience.Years, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.WorkExperience.Years, new { htmlAttributes = new { @class = "form-control" } })
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

在Result檢視使用HTML.Action方法,叫用控制器的DisplayWrokExperience方法插入部分檢視,參考以下範例程式碼:

@model ModelBindingDemo.Models.Employee

@{
    ViewBag.Title = "Result";
}

<h2>Result</h2>

<div>
    <h4>Employee</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.EmployeeId)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.EmployeeId)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.EmployeeName)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.EmployeeName)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.BirthDate)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.BirthDate)
        </dd>

    </dl>

    @Html.Action("DisplayWrokExperience")

</div>
<p>
    @Html.ActionLink("Back to List", "Index")
</p>

 

按CTRL + F5執行這個專案,執行時輸入以下URL:

http://localhost:46158/Home/Index

填寫員工資料後,提交到伺服器,請參考下圖所示:

clip_image006

圖 3:執行結果測試。

一執行你就會發現在Result檢視收不到WorkExprience資料,請參考下圖所示:

clip_image008

圖 4:執行結果測試。

這是因為表單送出的表單資料在CompanyName與Years之前前置「WorkExperience」字串的關係,攔截HTTP請求檢視表單資料,可以看到以下的結果:

EmployeeId=1&EmployeeName=mary&BirthDate=2016%2F1%2F2&WorkExperience.CompanyName=Contoso&WorkExperience.Years=5

我們可以使用前綴字(Prefix)來修訂這個問題,修改控制器DisplayWrokExperience方法,在WorkExperience參數前方使用Bind Attribute的Prefix屬性設定前綴字「WorkExperience」,參考以下範例程式碼:

namespace ModelBindingDemo.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Index(Employee model)
        {
            return View("Result", model);
        }

        public ActionResult DisplayWrokExperience([Bind(Prefix = "WorkExperience")]WorkExperience model)
        {
            return PartialView(model);
        }

    }
}

 

重新執行程式,這次Model Binder便可以從「WorkExperience.CompanyName=Contoso&WorkExperience.Years=5」表單資料之中截取出WorkExperience.CompanyName與WorkExperience.Years的值,放到WorkExperience物件的CompanyName與Years屬性中。執行結果請參考下圖所示:

clip_image010

圖 5:執行結果測試。

繫結陣列

Default Model Binder支援將HTTP請求的資料還原成陣列或集合,假設我們的Employee模型包含一個Interests屬性,型別為字串陣列,參考以下範例程式碼:

namespace ModelBindingDemo.Models
{
    public class Employee
    {
        public int EmployeeId { get; set; }
        public string EmployeeName { get; set; }
        public DateTime BirthDate { get; set; }
        public string[] Interests { get; set; }
    }
}

 

Index檢視使用一個for 迴圈讓使用者填寫三個興趣資料,參考以下範例程式碼:

@model ModelBindingDemo.Models.Employee

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>Employee</h4>
        <hr />
        <div class="form-group">
            @Html.LabelFor(model => model.EmployeeId, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.EmployeeId, new { htmlAttributes = new { @class = "form-control" } })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.EmployeeName, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.EmployeeName, new { htmlAttributes = new { @class = "form-control" } })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.BirthDate, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.BirthDate, new { htmlAttributes = new { @class = "form-control" } })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Interests, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                <ul>
                    @for (int i = 0; i < 3; i++)
                    {
                        <li>@Html.TextBoxFor(model => model.Interests)</li>
                    }
                </ul>
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

 

控制器的程式碼,提供兩個Index方法,沒有參數的第一個Index方法將檢視產生的HTML標籤送到瀏覽器;標識HttpPost Attribute,且包含一個Employee參數的Index方法,則利用Model Binder將HTTP請求送至的表單資料取出,填入Employee物件與表單資料同名的屬性,最後將Employee物件傳到Result檢視來顯示,參考以下範例程式碼:

namespace ModelBindingDemo.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Index(Employee model)
        {
            return View("Result", model);
        }

    }
}


Result檢視使用for迴圈,顯示出Interests屬性值,參考以下範例程式碼:

@model ModelBindingDemo.Models.Employee

@{
    ViewBag.Title = "Result";
}

<h2>Result</h2>

<div>
    <h4>Employee</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.EmployeeId)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.EmployeeId)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.EmployeeName)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.EmployeeName)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.BirthDate)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.BirthDate)
        </dd>

    </dl>

    <hr />
    <h4>Interests</h4>
    <ul>

        @foreach (var item in Model.Interests)
        {
            <li>@item</li>
        }
    </ul>


</div>
<p>
    @Html.ActionLink("Back to List", "Index")
</p>

 

執行結果請參考如下圖所示,輸入興趣之後提交資料到伺服端:

clip_image012

圖 6:執行結果測試。

Result檢視正確地將興趣的內容顯示在畫面上,請參考下圖所示:

clip_image014

圖 7:執行結果測試。

在伺服端,你也可以宣告一個字串陣列變數,單純來接收Interests資料,參考以下範例程式碼:

[HttpPost]
public ActionResult Index(Employee model, string[] Interests)
{
    return View("Result", model);
}

繫結到集合

繫結到集合的方法大致繫結到陣列相同,我們只要將上述範例模型屬性換成集合類型即可,例如以下範例程式碼:

namespace ModelBindingDemo.Models
{
    public class Employee
    {
        public int EmployeeId { get; set; }
        public string EmployeeName { get; set; }
        public DateTime BirthDate { get; set; }
        public List<string> Interests { get; set; }
    }
}

 

在控制器便可以透過集合型別的參數得到它們的值,參考以下範例程式碼:

 

[HttpPost]
public ActionResult Index(Employee model, List<string> Interests)
{
    return View("Result", model);
}

繫結到自訂型別的集合

若要繫結到自訂型別的集合,例如List<Employee>,我們需要在檢視的<form>標籤中產生<input>項目,讓<input>項目的name前綴陣列的索引。例如,我們想要搜集三個員工的EmployeeId、EmployeeName與BirthDate的資料,你需要產生類似以下的標籤,其中name Attribute中的[0],代表第一個物件;[1]代表第二個物件,依此類推。:

<table class="table">
    <tbody>
        <tr>
            <th>
                EmployeeId
            </th>
            <th>
                EmployeeName
            </th>
            <th>
                BirthDate
            </th>
        </tr>

        <tr>
            <td>
                <input class="text-box single-line" name="[0].EmployeeId" type="text" value="">
            </td>
            <td>
                <input class="text-box single-line" name="[0].EmployeeName" type="text" value="">
            </td>
            <td>
                <input class="text-box single-line" name="[0].BirthDate" type="text" value="">
            </td>

        </tr>
        <tr>
            <td>
                <input class="text-box single-line" name="[1].EmployeeId" type="text" value="">
            </td>
            <td>
                <input class="text-box single-line" name="[1].EmployeeName" type="text" value="">
            </td>
            <td>
                <input class="text-box single-line" name="[1].BirthDate" type="text" value="">
            </td>

        </tr>
        <tr>
            <td>
                <input class="text-box single-line" name="[2].EmployeeId" type="text" value="">
            </td>
            <td>
                <input class="text-box single-line" name="[2].EmployeeName" type="text" value="">
            </td>
            <td>
                <input class="text-box single-line" name="[2].BirthDate" type="text" value="">
            </td>

        </tr>
    </tbody>
</table>

 

當表單提交時,Default Model Binder便知道要建立一個List<Employee>集合,根據前綴的索引,將資料複到集合中物件的屬性。

我們先定義Employee模型如下,包含EmployeeId、EmployeeName、BirthDate三個屬性,參考以下範例程式碼:

namespace ModelBindingDemo.Models
{
    public class Employee
    {
        public int EmployeeId { get; set; }
        public string EmployeeName { get; set; }
        public DateTime BirthDate { get; set; }
    }
}

 

控制器提供兩個Index方法,第二個Index方法則接收一個List<Employee>型別的參數,並將還原的集合物件丟到Result檢視來顯示,參考以下範例程式碼:

namespace ModelBindingDemo.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
        [HttpPost]
        public ActionResult Index(List<Employee> model)
        {
          
            return View("Result", model);
        }
    }
}

 

Index檢視程式碼如下, 使用for迴圈產生三組Input方塊來輸資料,參考以下範例程式碼:

@model IEnumerable<ModelBindingDemo.Models.Employee>

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    <table class="table">
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.EmployeeId)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.EmployeeName)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.BirthDate)
            </th>
        </tr>

        @for (int i = 0; i < 3; i++)
        {
            <tr>
                <td>
                    @Html.Editor($"[{i}].EmployeeId")
                </td>
                <td>
                    @Html.Editor($"[{i}].EmployeeName")
                </td>
                <td>
                    @Html.Editor($"[{i}].BirthDate")
                </td>

            </tr>
        }


    </table>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Create" class="btn btn-default" />
        </div>
    </div>
}

 

Result 檢視程式碼如下,使用foreach迴圈將集合中Employee物件的屬性值印出,參考以下範例程式碼:

@model IEnumerable<ModelBindingDemo.Models.Employee>

@{
    ViewBag.Title = "Result";
}

<h2>Result</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.EmployeeId)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.EmployeeName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.BirthDate)
        </th>
        <th></th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <th>
            @Html.DisplayFor(model => item.EmployeeId)
        </th>
        <td>
            @Html.DisplayFor(modelItem => item.EmployeeName)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.BirthDate)
        </td>
    </tr>
}

</table>

執行結果請參考下圖所示,填寫資料之後將資料提交到伺服端:

clip_image016

圖 8:執行結果測試。

攔截HTTP請求檢視表單資料,我們送出以下格式的表單資料來描述三筆Employee的資訊,[0]指定第一筆資料的索引,[1]是第二筆資料的索引,依此類推,請參考下圖所示:

clip_image018

圖 9:執行結果測試。

Result檢視執行結果請參考下圖所示:

clip_image020

圖 10:執行結果測試。

手動繫結

只要在行動方法宣告參數,就會自動啟動模型繫結的程序,我們也可以跳過這個步驟,在適當時候叫用UpdateModel或TryUpdateModel方法來進行繫結。這兩個方法差別在UpdateModel方法在資料驗證出問題時,會觸發例外錯誤;而TryUpdateModel方法則不會產生例外。舉例來說,修改上例程式碼中的Employee模型,使用Required Attribute設定EmployeeName不可為空白,參考以下範例程式碼:

namespace ModelBindingDemo.Models
{
    public class Employee
    {
        public int EmployeeId { get; set; }
        [Required]
        public string EmployeeName { get; set; }
        public DateTime BirthDate { get; set; }
    }
}

修改控制器,在第二個Index方法中,叫用TryUpdateModel或UpdateModel方法,手動繫結資料,參考以下範例程式碼:

namespace ModelBindingDemo.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
        [HttpPost]
        public ActionResult Index(int? notUsed)
        {
            List<Employee> model = new List<Employee>();
            UpdateModel(model);
            //TryUpdateModel(model);
            return View("Result", model);
        }
    }
}

 

若叫用UpdateModel方法,執行時使用者未輸入員工名稱,將觸發例外錯誤,Visual Studio就會進入中斷畫面,執行結果請參考下圖所示:

clip_image022

圖 11:執行結果測試。

使用FormCollection搜集表單資料

Model Binder是利用FormValueProvider來讀取表單資料,若要取得表單資料有一個簡單的做法,只要在行動方法中宣告一個FormCollection型別的變數,就可以取得表單資料的內容,參考以下範例程式碼,在控制器第二個Index方法中,利用FormCollection讀取表單資料,然後叫用TryUpdateModel方法,將表單資料複製到Employee物件的屬性:

namespace ModelBindingDemo.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Index(FormCollection form)
        {
            Employee model = new Employee();
            TryUpdateModel(model);
            return View("Result", model);
        }
    }

}


 

Index檢視的程式碼參考如下:

@model ModelBindingDemo.Models.Employee

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>Employee</h4>
        <hr />
        <div class="form-group">
            @Html.LabelFor(model => model.EmployeeId, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.EmployeeId, new { htmlAttributes = new { @class = "form-control" } })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.EmployeeName, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.EmployeeName, new { htmlAttributes = new { @class = "form-control" } })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.BirthDate, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.BirthDate, new { htmlAttributes = new { @class = "form-control" } })
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

 

Result檢視的程式碼參考如下:

@model ModelBindingDemo.Models.Employee

@{
    ViewBag.Title = "Result";
}

<h2>Result</h2>

<div>
    <h4>Employee</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.EmployeeId)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.EmployeeId)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.EmployeeName)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.EmployeeName)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.BirthDate)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.BirthDate)
        </dd>

    </dl>
</div>
<p>
    @Html.ActionLink("Back to List", "Index")
</p>

限定繫結來源資料

預設Model Binder會照優先順序,找表單、路由、查詢字串以及上傳檔案等資料來進行繫結的動作,我們可以限制它,只找特定來源的資料。舉例來說,若想要限定只繫結表單資料,我們定義模型如下:

namespace ModelBindingDemo.Models
{
    public class Employee
    {
        public int EmployeeId { get; set; }
        public string EmployeeName { get; set; }
        public DateTime BirthDate { get; set; }
    }
}

 

在控制器定義Index方法手動繫結模型,參考以下範例程式碼:

namespace ModelBindingDemo.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            Employee model = new Employee();
            TryUpdateModel(model);
            return View("Result", model);
        }
    }
}

 

定義Result檢視程式碼如下,顯示模型的屬性值,參考以下範例程式碼:

@model ModelBindingDemo.Models.Employee

@{
    ViewBag.Title = "Result";
}

<h2>Result</h2>

<div>
    <h4>Employee</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.EmployeeId)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.EmployeeId)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.EmployeeName)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.EmployeeName)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.BirthDate)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.BirthDate)
        </dd>

    </dl>
</div>
<p>
    @Html.ActionLink("Back to List", "Index")
</p>

 

執行網站時,輸入以下URL送Get請求,以查詢字串帶參數進行測試:

http://localhost:46158/Home/Index?employeeid=1&employeename=mary&birthdate=2016/1/2

執行結果請參考下圖所示,Model Binder將查詢字串的值繫結到模型屬性,並呈現在網頁上:

clip_image024

圖 12:執行結果測試。

再來我們使用Fiddler工具,使用POST送出表單資料進行測試,請參考下圖所示:

clip_image026

圖 13:執行結果測試。

得到結果一樣,Model Binder將表單的值繫結到模型屬性,請參考下圖所示:

clip_image028

圖 13:執行結果測試。

修改控制器程式碼,在Index方法,建立一個FormValueProvider物件傳入ControllerContext。ControllerContext是定義在ControllerBase類別的屬性,包含一個ControllerContext物件。將

FormValueProvider物件傳入TryUpdateModel方法中,表示只繫結表單資料,不繫結查詢字串的資料,參考以下範例程式碼:

namespace ModelBindingDemo.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            Employee model = new Employee();
            TryUpdateModel(model, new FormValueProvider(ControllerContext));
            return View("Result", model);
        }
    }
}

 

這次使用查詢字串帶的參數就不會被繫結到屬性,而使用Fiddler送出的表單資料則可正常執行,使用查詢字串的執行結果請參考下圖所示:

clip_image030

圖 14:執行結果測試。

下面的寫法和上例相同,要求只從表單資料進行繫結:

namespace ModelBindingDemo.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index(FormCollection form)
        {
            Employee model = new Employee();
            TryUpdateModel(model, form);
            return View("Result", model);
        }
    }
}

Tags:

.NET Magazine國際中文電子雜誌 | ASP.NET MVC | 許薰尹Vivid Hsu

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List