ASP.NET MVC 4 (七) 模板幫助函數
和普通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。

浙公網安備 33010602011771號