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按照一定的順序搜索使用適合的模板:

  1. 幫助函數中指定的模板,比如 Html.EditorFor(m => m.SomeProperty, "MyTemplate")指定的MyTemplate模板
  2. metadata中UIHint指定的目標
  3. 數據類型確定的模板,比如DataType特性
  4. 正在處理的數據類型類的名稱
  5. 對於簡單類型使用內建的string模板
  6. 如果數據類型實現IEnumerable,使用內建的Collection模板
  7. 以上失敗時,使用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。  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM