和普通HTML幫助函數不同,模板幫助函數不需要指定所用的HTML類型,MVC會推斷選擇合適的HTML元素,這讓我們有更多的靈活性。
使用模板幫助函數
我們使用《ASP.NET MVC 4 (六) 幫助函數 》中的數據模型和控制器繼續后面的例子,使用模板幫助函數后改寫編輯輸入的視圖:
@model HelperMethods.Models.Person @{ ViewBag.Title = "CreatePerson"; } <h2>CreatePerson</h2> @using (Html.BeginRouteForm("FormRoute", new { }, FormMethod.Post,new { @class = "personClass", data_formType = "person" })) { <div class="dataElem"> <label>PersonId</label> @Html.Editor("PersonId") </div> <div class="dataElem"> <label>First Name</label> @Html.Editor("FirstName") </div> <div class="dataElem"> <label>Last Name</label> @Html.EditorFor(m => m.LastName) </div> <div class="dataElem"> <label>Role</label> @Html.EditorFor(m => m.Role) </div> <div class="dataElem"> <label>Birth Date</label> @Html.EditorFor(m => m.BirthDate) </div> <input type="submit" value="Submit" /> }
這里用到模板幫助函數Editor和EditorFor,MVC猜測相應的數據類型生成相應類型的輸入HTML標記:
... <h2>CreatePerson</h2> <form action="/app/forms/Home/CreatePerson" class="personClass" data-formtype="person" method="post"> <div class="dataElem"> <label>PersonId</label> <input class="text-box single-line" id="PersonId" name="PersonId" type="number" value="0" /> </div> <div class="dataElem"> <label>First Name</label> <input class="text-box single-line" id="FirstName" name="FirstName" type="text" value="" /> </div> <div class="dataElem"> <label>Last Name</label> <input class="text-box single-line" id="LastName" name="LastName" type="text" value="" /> </div> <div class="dataElem"> <label>Role</label> <input class="text-box single-line" id="Role" name="Role" type="text" value="Admin" /> </div> <div class="dataElem"> <label>Birth Date</label> <input class="text-box single-line" id="BirthDate" name="BirthDate" type="datetime" value="01/01/0001 00:00:00" /> </div> <input type="submit" value="Submit" /> </form> ...
HTML5規范定義了input標簽可以編輯通用類型,比如數字、日期,不同瀏覽器對HTML5的支持有所差別,上面的結果在Opera中number類型會渲染成帶spin按鈕的編輯空間,datetime為渲染成專用的帶可選日歷的日期/時間編輯器。
以下是MVC可用的模板幫助函數列表:
幫助函數 | 示例 | 說明 |
Display | Html.Display("FirstName") | 渲染只讀的HTML元素,根據數據類型和metadata選擇適用的HTML元素 |
DisplayFor | Html.DisplayFor(x => x.FirstName) | Display的強類型形式 |
Editor | Html.Editor("FirstName") | 渲染可編輯的HTML元素,根據數據類型和metadata選擇適用的HTML元素 |
EditorFor | Html.EditorFor(x => x.FirstName) | Editor的強類型形式 |
Label | Html.Label("FirstName") | 根據引用的模型對象屬性渲染<label>標簽 |
LabelFor | Html.LabelFor(x => x.FirstName) | Label的強類型形式 |
這個例子為我們演示如何使用Display和Label:
@model HelperMethods.Models.Person @{ ViewBag.Title = "DisplayPerson"; } <h2>DisplayPerson</h2> <div class="dataElem"> @Html.Label("PersonId") @Html.Display("PersonId") </div> <div class="dataElem"> @Html.Label("FirstName") @Html.Display("FirstName") </div> <div class="dataElem"> @Html.LabelFor(m => m.LastName) @Html.DisplayFor(m => m.LastName) </div> <div class="dataElem"> @Html.LabelFor(m => m.Role) @Html.DisplayFor(m => m.Role) </div> <div class="dataElem"> @Html.LabelFor(m => m.BirthDate) @Html.DisplayFor(m => m.BirthDate) </div>
輸出的HTML結果:
... <div class="dataElem"> <label for="PersonId">PersonId</label> 100 </div> <div class="dataElem"> <label for="FirstName">FirstName</label> Adam </div> <div class="dataElem"> <label for="LastName">LastName</label> Freeman </div> <div class="dataElem"> <label for="Role">Role</label> Admin </div> <div class="dataElem"> <label for="BirthDate">BirthDate</label> 01/01/0001 00:00:00 </div> ...
整模型模板幫助函數
上面的模板幫助函數可以處理單個模型對象屬性,MVC還提供一組模板幫助函數為整個模型對象生成HTML:
幫助函數 | 示例 | 說明 |
DisplayForModel | Html.DisplayForModel() | 為整個模型對象生成只讀的HTML渲染 |
EditorForModel | Html.EditorForModel() | 為整個模型對象生成可編輯的HTML渲染 |
LabelForModel | Html.LabelForModel() | 為整個模型對象生成<label>標簽 |
使用整模型幫助函數后編輯Person的視圖可以簡化為:
@model HelperMethods.Models.Person @{ ViewBag.Title = "CreatePerson"; } <h2>CreatePerson: @Html.LabelForModel()</h2> @using(Html.BeginRouteForm("FormRoute", new {}, FormMethod.Post, new { @class = "personClass", data_formType="person"})) { @Html.EditorForModel() <input type="submit" value="Submit" /> }
MVC會為Person的各個屬性生成相應的HTML編輯元素,枚舉類型也被渲染為一個簡單的編輯框,這也並不是很有用,我們更習慣從下拉框中選擇枚舉值(后面我們可以看到如何實現)。另外並非所有的屬性,比如Address屬性,它不是一個c#的元類型,在生成的結果中不可見,我們可以為Address在調用一次EditFor來展開顯示它(后面我們可以看到的object模板):
@model HelperMethods.Models.Person @{ ViewBag.Title = "CreatePerson"; } <h2>CreatePerson: @Html.LabelForModel()</h2> @using (Html.BeginRouteForm("FormRoute", new { }, FormMethod.Post,new { @class = "personClass", data_formType = "person" })) { <div class="column"> @Html.EditorForModel() </div> <div class="column"> @Html.EditorFor(m => m.HomeAddress) </div> <input type="submit" value="Submit" /> }
這里使用的是強類型的EditFor,以保證生成HTML元素包含正確的ID和Name,比如HomeAddress.Line1生成id="HomeAddress_Line1",name="HomeAddress.Line1",這能保證數據提交后正確綁定到數據模型上。
使用模型metadata
EditorForModel()生成的HTML並不那么完美,這不能怪罪於模板幫助函數,它已經對要顯示的結果做了最好的猜測,我們可以通過metadata給予模板幫助函數更多的提示。比如PersonId我們是不能編輯的,我們可以使用HiddenAttribute標記:
public class Person { [HiddenInput] public int PersonId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime BirthDate { get; set; } public Address HomeAddress { get; set; } public bool IsApproved { get; set; } public Role Role { get; set; } }
EditorForModel會渲染一個只讀的input元素:
... <div class="editor-field"> 0 <input id="PersonId" name="PersonId" type="hidden" value="0" /> </div> ...
如果我們要完全不顯示PersonId:
public class Person { [HiddenInput(DisplayValue=false)] public int PersonId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime BirthDate { get; set; } public Address HomeAddress { get; set; } public bool IsApproved { get; set; } public Role Role { get; set; } }
Html.EditorForModel()會生成一個隱藏的input元素,我們看不到它,但是仍然包含在提交的數據中。如果我們要完完全全的忽略一個屬性,連隱藏input元素都不要生成:
... [ScaffoldColumn(false)] public int PersonId { get; set; } ...
需要注意的是ScaffoldColumn(false)只對EditorForModel()有作用,單個屬性的模板幫助函數比如 @Html.EditorFor(m => m.PersonId)仍然會生成結果,不受ScaffoldColumn影響。
我們還可以通過metadata的方式設定label幫助函數生成的標簽內容:
[DisplayName("New Person")] public class Person { [HiddenInput(DisplayValue=false)] public int PersonId { get; set; } [Display(Name="First")] public string FirstName { get; set; } [Display(Name = "Last")] public string LastName { get; set; } [Display(Name = "Birth Date")] public DateTime BirthDate { get; set; } public Address HomeAddress { get; set; } [Display(Name="Approved")] public bool IsApproved { get; set; } public Role Role { get; set; } }
Label幫助函數會使用DisplayName定義的名稱“New person”為整個model生成Label標簽,使用Display中的Name為各個屬性生成Label標簽。
我們可以使用DataType特性指定屬性的數據類型:
... [Display(Name = "Birth Date")] [DataType(DataType.Date)] public DateTime BirthDate { get; set; } ...
通過指定BirthDate的數據類型為Date,生成的HTML編輯框會只包含日期部分。可用的數據類型包括:DateTime、Date、Time、Text、PhoneNumber、MultilineText、Password、Url、EmailAddress。模板幫助函數根據數據類型的不同選擇生成不同的標簽元素,比如MultilineText生成textarea多行編輯框。
除了通過DateType指定屬性的數據類型,我們還可以使用 UIHint特性明確指定幫助函數生成HTML選用的標簽元素:
... [Display(Name="First")] [UIHint("MultilineText")] public string FirstName { get; set; } ...
這里明確指定為FirstName使用一個textarea編輯框,EditorFor和EditorForModel參考這個特性。可以指定的UI模板包括:
UIHint模板 | Editor輸出結果 | Display輸出結果 |
Boolean | bool值生成一個復選框,bool?生成包含true、false、not set三個選項的選擇框 | 和editor相同但是帶有disabled屬性禁止編輯 |
Collection | 為IEnumerable中每個項目選擇合適的目標生成結果,項目不一定是相同的類型 | 等同Editor |
Decimal | 生成單行textbox類型的input元素,格式化帶2個小數點的 | 顯示帶2個小數點的字符串 |
DateTime | 生成type=datetime的input元素,包含日期和時間 | 顯示日期和時間 |
Date | 生成type=date的input元素,僅包含日期 | 顯示日期 |
EmailAddress | 生成單行textbox的input元素 | 生成a元素,href=mailto: |
HiddenInput | 生成隱藏的input元素 | 生成隱藏Input |
Html | 生成單行textbox的input元素 | 生成a元素標記 |
MultilineText | 生成textarea元素 | 顯示數據 |
Number | 生成type=number的input元素 | 顯示數據 |
Object | 展開對象,為對象的各個屬性生成合適的元素,展開不能遞歸,也就是說如果某個屬性不是一個基本類型就再展開 | |
Password | 生成密碼類型的單行textbox | 顯示模糊處理后的數據 |
String | 生成單行textbox的input元素 | 顯示數據 |
Text | 同string | 同string |
Tel | 生成type=tel的input元素 | 顯示數據 |
Time | 生成type=time的input元素,僅顯示時間 | 顯示時間數據 |
Url | 生成單行input元素 | 生成a元素,內部HTML和href屬性都設置為數值 |
需要注意的是如果所選UI模板和數據類型沖突會產生異常,比如為string數據類型選擇boolean的UI模板。
我們不需要直接在模型類上編輯metadata屬性,特別是那些ORM自動生成的模型類,每次修改數據Schema時就會重建模型類,模型類上的metadata被清除,我們不得不重新編輯metadata,針對這種情況我們可以定義模型類為partial,把metadata放到單獨的伙伴類中:
[MetadataType(typeof(PersonMetaData))] public partial class Person { public int PersonId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime BirthDate { get; set; } public Address HomeAddress { get; set; } public bool IsApproved { get; set; } public Role Role { get; set; } } [DisplayName("New Person")] public partial class PersonMetaData { [HiddenInput(DisplayValue=false)] public int PersonId { get; set; } [Display(Name="First")] public string FirstName { get; set; } [Display(Name = "Last")] public string LastName { get; set; } [Display(Name = "Birth Date")] public DateTime BirthDate { get; set; } [Display(Name="Approved")] [UIHint("Boolean")] public bool IsApproved { get; set; } [UIHint("Boolean")] public Role Role { get; set; } }
伙伴類中不需要包含每個屬性,我們可以只為需要的屬性設置metadata。
自定義編輯模板
我們可以通過創建自定義模板進一步控制模板幫助函數生成的HTML結果,MVC在 /Views/Shared/EditorTemplates目錄下查找自定義的模板,我們可以創建對應某個數據類型的強分部視圖,比如Role枚舉類型,我們為它創建Role.cshtml:
@model HelperMethods.Models.Role
@Html.DropDownListFor(m => m, new SelectList(Enum.GetNames(Model.GetType()), Model.ToString()))
這里為Role類型創建了一個下拉選擇對話框,MVC會在使用內建模板前搜索到這個自定義的模板並使用它。MVC按照一定的順序搜索使用適合的模板:
- 幫助函數中指定的模板,比如 Html.EditorFor(m => m.SomeProperty, "MyTemplate")指定的MyTemplate模板
- metadata中UIHint指定的目標
- 數據類型確定的模板,比如DataType特性
- 正在處理的數據類型類的名稱
- 對於簡單類型使用內建的string模板
- 如果數據類型實現IEnumerable,使用內建的Collection模板
- 以上失敗時,使用ojbect模板展開,展開不能遞歸,也就是不展開子類型的屬性
根據上面的模板搜索順序,可以將Role模板變得更廣泛化,我們創建一個Enum類型都適用的模板:
@model Enum @Html.DropDownListFor(m => m, Enum.GetValues(Model.GetType()) .Cast<Enum>() .Select(m => { string enumVal = Enum.GetName(Model.GetType(), m); return new SelectListItem() { Selected = (Model.ToString() == enumVal), Text = enumVal, Value = enumVal }; }))
我們在metadata伙伴類中指定Role屬性使用這個Enum模板:
[DisplayName("New Person")] public partial class PersonMetaData1 { ... [UIHint("Enum")] public Role Role { get; set; } }
而如果我們創建了一個和內建同名的模板會怎么樣?MVC會使用我們自定義的模板代替內建的模板,比如我們為bool和bool?創建一個替代Boolean內建類型的模板:
@model bool? @if (ViewData.ModelMetadata.IsNullableValueType && Model == null) { @:(True) (False) <b>(Not Set)</b> } else if (Model.Value) { @:<b>(True)</b> (False) (Not Set) } else { @:(True) <b>(False)</b> (Not Set) }
以上為對《Apress Pro ASP.NET MVC 4》第四版相關內容的總結,不詳之處參見原版 http://www.apress.com/9781430242369。