表單 Partial View / Editor Template 使用抉擇
前言
最近有不少同事都會問到「為什麼在這邊要使用Editor Tamplate? 怎麼不用Partial View來做?」,其實如果想要知道各自適用的時機,首先就必須了解兩者差異為何。從以下表格不難發現,兩者都是從VIEW中將ViewModel之Boo屬性物件傳入Partial View / Editor Template,並且都是使用@Html.EditorFor() 方法來產出Html 元素,但最終產出Html Input元素名稱prefix卻不相同。因此以下將針對各自特性來思考適用情境。
適用情境
使用Partial View或Editor Tempalte除共用考量因素外,就是避免頁面過於龐大造成維護不易,因此在拆解複雜資料輸入頁面區塊時,我們需要考量表單資料被送出的方式,是否所有資料都隸屬於同一個表單中,亦或者會有分散在各表單的情況,需要有各自被送出的需求。以下提供兩種使用情境供讀者參考。
Editor Template
單一表單情況下,我們若想將各子屬性類別拆解成獨自的檢視,讓複雜表單不會這麼龐大時,就可以使用Editor Template來實現。簡略示意圖如下,在Editor Template中若是透過@Html.EditorFor()產出Html元素時,元素名稱會以傳入物件屬性名稱(VMA/VMB)作為 prefix,因此在按下Submit送出表單資料到後端時,便可依照名稱將資料完整地Binding至ViewModel中。
Partial View
在多表單情況下,可考慮依照各表單Model來拆解成各自Partial View使用。簡略示意圖如下,在Partial View中若是透過@Html.EditorFor()產出Html元素時,元素名稱並不會以傳入物件屬性名稱(VMA/VMB)作為 prefix,因此可利用此特性,讓各表單送出屬於自己的Model資料至Controller中。
實作演練
假設目前有個新增使用者功能需要實作,由於畫面可能會很複雜(身家調查之類),因此可能會存在許多不同種類的資訊,所以希望將表單頁面稍微分割一下,把表單中連絡資訊(Contact)及教育資訊(Education)給分割出來,預期介面如下所示。雖然知道此時應使用EditorTemplate來實做會比較合適,但抱持著實驗的精神,我們就姑且先使用Partial View來實做,看看會發生什麼問題。
首先定義檢視Model(UserCreateViewModel)、連絡資訊(Contact)及教育資訊(Education)相關類別。
public class UserCreateViewModel
{
public string UserName { get; set; }
public Contact Contact { get; set; }
public Education Education { get; set; }
}
public class Contact
{
public string Phone { get; set; }
public string Email { get; set; }
}
public class Education
{
public string HightSchool { get; set; }
public string University { get; set; }
}
接著設計輸入連絡資訊(Contact)及教育資訊(Education)的Partial View頁面
為了讓結果更加清晰,直接透過@Html.NameFor()把輸入框元素名稱印出,方便後續比較使用。
_ContactPartialView.cshtml
@model PartialViewWebApp.ViewModel.Contact
@{Layout = null;}
<fieldset class="well the-fieldset col-md-12">
<legend class="the-legend">contact</legend>
<div class="form-horizontal">
<div class="form-group">
@Html.LabelFor(model => model.Phone, htmlAttributes: new { @class = "control-label col-md-3" })
<div class="col-md-7">
@Html.EditorFor(model => model.Phone, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Phone, "", new { @class = "text-danger" })
name: @Html.NameFor(model => model.Phone)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-3" })
<div class="col-md-7">
@Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" })
name: @Html.NameFor(model => model.Email)
</div>
</div>
</div>
</fieldset>
_EducationPartialView.cshtml
@model PartialViewWebApp.ViewModel.Education
@{Layout = null;}
<fieldset class="well the-fieldset col-md-12">
<legend class="the-legend">Education</legend>
<div class="form-horizontal">
<div class="form-group">
@Html.LabelFor(model => model.HightSchool, htmlAttributes: new { @class = "control-label col-md-3" })
<div class="col-md-7">
@Html.EditorFor(model => model.HightSchool, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.HightSchool, "", new { @class = "text-danger" })
name: @Html.NameFor(model => model.HightSchool)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.University, htmlAttributes: new { @class = "control-label col-md-3" })
<div class="col-md-7">
@Html.EditorFor(model => model.University, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.University, "", new { @class = "text-danger" })
name: @Html.NameFor(model => model.University)
</div>
</div>
</div>
</fieldset>
主頁面(View)如下,使用Partial View來呈現連絡資訊(Contact)及教育資訊(Education)輸入介面。
@model PartialViewWebApp.ViewModel.UserCreateViewModel
@{ ViewBag.Title = "Create"; }
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(model => model.UserName, htmlAttributes: new { @class = "control-label col-md-3" })
<div class="col-md-7">
@Html.EditorFor(model => model.UserName, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.UserName, "", new { @class = "text-danger" })
name: @Html.NameFor(model => model.UserName)
</div>
</div>
<!-- 使用Partial View顯示Contact輸入介面-->
@Html.Partial("_ContactPartialView", Model.Contact)
<!-- 使用Partial View顯示Education輸入介面-->
@Html.Partial("_EducationPartialView", Model.Education)
<div class="form-group">
<div class="col-md-offset-8 col-md-4">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
}
產生畫面如下,我們可以看到元素名稱並沒有依照物件屬性階層來命名,因此將資料POST到後端時,除了UserName可以正確綁定至UserCreateViewModel外,其他透過Partial View產出輸入框中的資料並無法正確Binding至UserCreateViewModel。
最終會造成部分資料的遺失
接著使用EditorTemplate來實做。首先建立EditorTemplates資料夾,並且在剛目錄中建立Contact.cshtml及Education.cshtml來對應Contact及Education類別,而各類別對應的EditorTemplate內容其實與先前設計的Partial View完全相同,因此不再此贅述了。
然後調整一下主頁面,改使用EditorTemplate來呈現連絡資訊(Contact)及教育資訊(Education)
@model PartialViewWebApp.ViewModel.UserCreateViewModel
@{ ViewBag.Title = "Create"; }
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(model => model.UserName, htmlAttributes: new { @class = "control-label col-md-3" })
<div class="col-md-7">
@Html.EditorFor(model => model.UserName, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.UserName, "", new { @class = "text-danger" })
name: @Html.NameFor(model => model.UserName)
</div>
</div>
<!-- 使用Editor Template顯示Contact輸入介面-->
@Html.EditorFor(m => m.Contact)
<!-- 使用Editor Template顯示Education輸入介面-->
@Html.EditorFor(m => m.Education)
<div class="form-group">
<div class="col-md-offset-8 col-md-4">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
}
產生的畫面如下,我們可以看到元素名稱依照物件屬性階層來命名,因此將資料POST到後端時,所有資料都可以依據名稱,正確Binding至userCreateViewModel中。
結果如預期般正確Binding至userCreateViewModel中。
結論
Partial View 與 Editor Template 雖然都可以傳入物件來呈現獨立的檢視,但是若是牽涉到表單或需要POST資料到後端時,需要特別注意兩者差別。Partial View 在使用 @Html.EditorFor() 產生Html元素時,就如同一般View的情況只根據檢視中定義的Model來命名Html元素名稱;而使用EditorTemplate時,會依照傳入物件的屬性名稱作為產出Html元素名稱之prefix,讓元素名稱可對應至主Model類別結構。
參考資訊
http://stackoverflow.com/questions/13746697/mvc3-4-multiple-forms-using-partial-views
http://stackoverflow.com/questions/10608766/post-a-form-with-multiple-partial-views
希望此篇文章可以幫助到需要的人
若內容有誤或有其他建議請不吝留言給筆者喔 !