[ASP NET MVC] 表單 Partial View / Editor Template 使用抉擇

表單 Partial View / Editor Template 使用抉擇

前言

 

最近有不少同事都會問到「為什麼在這邊要使用Editor Tamplate? 怎麼不用Partial View來做?」,其實如果想要知道各自適用的時機,首先就必須了解兩者差異為何。從以下表格不難發現,兩者都是從VIEW中將ViewModel之Boo屬性物件傳入Partial View / Editor Template,並且都是使用@Html.EditorFor() 方法來產出Html 元素,但最終產出Html Input元素名稱prefix卻不相同。因此以下將針對各自特性來思考適用情境。

 

image

 

 

適用情境

 

使用Partial View或Editor Tempalte除共用考量因素外,就是避免頁面過於龐大造成維護不易,因此在拆解複雜資料輸入頁面區塊時,我們需要考量表單資料被送出的方式,是否所有資料都隸屬於同一個表單中,亦或者會有分散在各表單的情況,需要有各自被送出的需求。以下提供兩種使用情境供讀者參考。

 

 

Editor Template

 

單一表單情況下,我們若想將各子屬性類別拆解成獨自的檢視,讓複雜表單不會這麼龐大時,就可以使用Editor Template來實現。簡略示意圖如下,在Editor Template中若是透過@Html.EditorFor()產出Html元素時,元素名稱會以傳入物件屬性名稱(VMA/VMB)作為 prefix,因此在按下Submit送出表單資料到後端時,便可依照名稱將資料完整地Binding至ViewModel中。

 

image

 

 

Partial View

 

在多表單情況下,可考慮依照各表單Model來拆解成各自Partial View使用。簡略示意圖如下,在Partial View中若是透過@Html.EditorFor()產出Html元素時,元素名稱並不會以傳入物件屬性名稱(VMA/VMB)作為 prefix,因此可利用此特性,讓各表單送出屬於自己的Model資料至Controller中。

 

image

 

 

實作演練

 

假設目前有個新增使用者功能需要實作,由於畫面可能會很複雜(身家調查之類),因此可能會存在許多不同種類的資訊,所以希望將表單頁面稍微分割一下,把表單中連絡資訊(Contact)及教育資訊(Education)給分割出來,預期介面如下所示。雖然知道此時應使用EditorTemplate來實做會比較合適,但抱持著實驗的精神,我們就姑且先使用Partial View來實做,看看會發生什麼問題。

 

image

 

首先定義檢視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頁面

 

image

 

為了讓結果更加清晰,直接透過@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。

 

image

 

最終會造成部分資料的遺失

 

image

 

接著使用EditorTemplate來實做。首先建立EditorTemplates資料夾,並且在剛目錄中建立Contact.cshtml及Education.cshtml來對應Contact及Education類別,而各類別對應的EditorTemplate內容其實與先前設計的Partial View完全相同,因此不再此贅述了。

 

image

 

然後調整一下主頁面,改使用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中。

 

image

 

結果如預期般正確Binding至userCreateViewModel中。

 

image

 

 

結論

 

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


希望此篇文章可以幫助到需要的人

若內容有誤或有其他建議請不吝留言給筆者喔 !