[ASP.NET MVC 小牛之路]13 - Helper Method

Liam Wang發表於2013-11-19

我們平時程式設計寫一些輔助類的時候習慣用“XxxHelper”來命名。同樣,在 MVC 中用於生成 Html 元素的輔助類是 System.Web.Mvc 名稱空間下的 HtmlHelper,習慣上我們把 HtmlHelper 中的(擴充套件)方法叫 HtmlHelper Method,由於使用頻繁,就簡單稱為Helper Method。它的作用是把生成 Html 程式碼的任務交給 MVC,以便 MVC 能完成很多自動處理的工作,也減少了程式碼量。我們在 View 中使用的 Html.ActionLink、Html.BeginForm、Html.CheckBox、Html.Raw 方法等都是 HtmlHelper 中的(擴充套件)方法。本文將進行簡要系統的介紹 Helper Method。

本文目錄

自定義 Helper Method

通過自定義 Helper Method,我們可以把一大段的 Html 程式碼打包成一個方法以便在整個應用程式中重複呼叫。如下面這個 View:

@{
    ViewBag.Title = "Index";
}

@helper StripHtml(string input) {
    @System.Text.RegularExpressions.Regex.Replace(input, "<.*?>", string.Empty)
}
<div>
    HTML Content: @HttpUtility.HtmlDecode("<div><p>Test</p><br/></div>")
    <br />
    Plain Content:@StripHtml("<div><p>Test</p></div>")
</div>

在 View 中通過 @helper 標記定義的方法,MVC 會把它編譯成 HtmlHelper 類的擴充套件方法。但在 View 中定義的 Helper Method 的作用域是當前的 View。如果要在整個應用程式都能使用,我們可以把它定義在一個公用的類中,如下面的 CustomHelpers 類中定義的 StripHtml 方法和 View 中的是一樣的:

public static class CustomHelpers {
    public static MvcHtmlString StripHtml(this HtmlHelper html, string input) {
        return new MvcHtmlString(System.Text.RegularExpressions.Regex.Replace(input, "<.*?>", string.Empty));
    }
}

關於擴充套件方法,不清楚的讀者可以閱讀本系列的 [ASP.NET MVC 小牛之路]02 - C#知識點提要 文章。

上面兩種方式執行效果如下:

字串編碼問題

MVC 框架會自動把從 Controller 中傳遞到 View 的字串進行Html編碼,如下面Action方法中的字串:

public ActionResult Index() {
    string message = "This is an HTML element: <input>";
    return View((object)message);
}

當這個字串用 Razor 呈現到 View時,生成的 Html 程式碼如下: 

This is an HTML element: &lt;input&gt;

Razor 引擎會自動對後臺傳遞過來的字串進行Html編碼,這是一種保護機制,使得後臺傳遞給View的字串不與 Html 標記衝突,也避免了用標記語言來攻擊網站的惡意行為。

但在 Helper Mothod 返回 MvcHtmlString 是被 Razor 信任的,Razor 不會對其進行編碼。我們可以簡單驗證一下。

在 CustomHelpers 類中加入一個 Helper Mothod,如下:

public static MvcHtmlString DisplayMessage(this HtmlHelper html, string msg) {
    string result = String.Format("This is the message: <p>{0}</p>", msg);
    return new MvcHtmlString(result);
}

然後我們在 Index.cshtml View 中以兩種方式來輸出 <input>:

@using MvcApplication1.Infrastructure
@model string

@{
    ViewBag.Title = "Index";
}

<p>This is the content from the view:</p>
<div style="border: thin solid black; padding: 10px">
    Here is the message:
    <p>@Model</p>
</div>
<p>This is the content from the helper method:</p>
<div style="border: thin solid black; padding: 10px">@Html.DisplayMessage(Model)
</div>

執行後我們可以看到如下兩種結果:

有時候我們就是想通過Help Meothod 輸出 <input> 字串怎麼辦,很簡單,像下面這樣把 Helper Method 的返回型別改為 string 型別:

public static string DisplayMessage(this HtmlHelper html, string msg) { 
    return String.Format("This is the message: <p>{0}</p>", msg); 
} 

但它也會把 <p> 給編碼了(如下面左圖),即返回給 View 的字串會全部被編碼。我們需要的是把那些要輸出為 Html 程式碼的部分字串進行編碼,而不是全部,這時我們可以這樣做:

public static MvcHtmlString DisplayMessage(this HtmlHelper html, string msg) {
    string result = String.Format("This is the message: <p>{0}</p>", html.Encode(msg));
    return new MvcHtmlString(result);
}

此時執行結果如下面右圖:

  

表單元素 Helper Method

這部分的內容其實沒什麼好講的,無非就是一些生成表單元素(如 <input type="text"...等)的 Helper Method,通過VS的智慧提示可以很方便的知道每個方法的用法,本節只對這些 Helper Method 進行一個簡要的概括。

首先來看看生成 form 元素的 Helper Method。在 View 中通過  Html.BeginForm 和 Html.EndForm 兩個方法可以很便捷地生成一個 form,如下:

@Html.BeginForm() 
    <label>PersonId</label> 
    <input name="personId" value="@Model.PersonId"/> 
    ...
    <input type="submit" value="Submit" />
@{Html.EndForm();} 

我們熟悉的另外一種寫法可以把 Html.EndForm 省略,如下:

@Html.BeginForm(){ 
    <label>PersonId</label> 
    <input name="personId" value="@Model.PersonId"/> 
    ...
    <input type="submit" value="Submit" />
}

BeginForm 有很多過載方法,在需要的時候我們可以通過VS智慧提示進行了解。

一般放在form表單內的元素的Helper Method就很多了,下面是生成 <input> 標籤的 Helper Method 列表:

這裡列出的每個 Helper Method 的第一個引數對應 input 元素 的 name 屬性(預設也是id屬性),第二個引數指定了 input 元素的值。它們都有若干個過載方法,這裡列出來的是其中的一個。

這個列表的每個方法都有帶一個 string 型別引數的過載方法,如 Html.TextBox("DataValue")。對於這個過載方法,MVC 框架會依次從 ViewBag 和 @Model 中去查詢值。如 Html.TextBox("DataValue") 方法,MVC 會依次查詢 ViewBag.DataValue 和 @Model.DataValue 的值作為生成 input 的 value 值。我們可以驗證一下,如下面的 View

@{
    ViewBag.Title = "Index";
    ViewBag.DataValue = "This is the value from ViewBag.";
}

@Html.TextBox("DataValue") 

它生成 input 標籤的 value 值如下:

<input id="DataValue" name="DataValue" type="text"value="This is the value from ViewBag." /> 

如果指定的字串引數是類似於這種的:DataValue.First.Name ,MVC 會依次查詢 ViewBag.DataValue.First.Name、ViewBag.DataValue["First"].Name 等的值,我們知道有這麼回事就可以了,不去研究它。

對於上面列表中的 Helper Method 都有對應的強型別版本,如 Html.CheckBox 方法對應有 Html.CheckBoxFor 方法。它們的引數是 lambda 表示式,如:

Html.TextBoxFor(x => x.FirstName)

其中 x 的型別是 View Model 的型別,它會根據 model 的屬性名和屬性值生成input標籤的 name (id 預設和 name 一樣)和 value 屬性值,如下:

<input id="FirstName" name="FirstName" type="text" value="" />

另外,還有一種生成 Select 標籤的 Helper Method,如下面列表所示:

模板化的 Helper Method

MVC 提供了另外一套生成 Html 元素的 Helper Method,它可以通過 Model 資訊來生成想要的 Html 標籤。例如要生成一個文字框可以用 Html.Editor 方法,它是多行還是單行或者其他,可以在 View Model 中進行定製。它就像一個模板(Template),要怎麼顯示元素,大部分都在 Model 中指定。下面來做個Demo 可能會讓你更地好理解。

為了演示,我們先建立一個 Person Model,程式碼如下:

namespace MvcApplication1.Models {
    public class Person {
        public int PersonId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public Role Role { get; set; }
    }
    public enum Role {
        Admin, User, Guest
    }
}

新增一個 Action,如下:

public ActionResult CreatePerson() {
    return View(new Person());
}

為該 action 新增 View:

@model MvcApplication1.Models.Person
@{
    ViewBag.Title = "CreatePerson";
}

@Html.Editor("PersonId")

執行後分別在 IE 11(左)和 Chrome(右)瀏覽器中的顯示如下:

 

如果瀏覽器足夠支援 Html 5 的話,對於數字型別會出現像 Chrome 瀏覽器那樣的上下增減小按鈕。

我們再來看它生成的 Html 程式碼:

<input class="text-box single-line" data-val="true" data-val-number="欄位 PersonId 必須是一個數字。" data-val-required="PersonId 欄位是必需的。" 
id
="PersonId" name="PersonId"type="number" value="0" />

這就是模板化的 Helper Method 生成的 Html 程式碼,通過生成的這些屬性,MVC 可以(配合 jQuery 庫)自動完成一些例如客戶端驗證之類的工作(後續博文介紹)。如果需要禁用客戶端驗證,可以在 View 中新增如下程式碼:

@{ Html.EnableClientValidation(false); } 

模板化的 Helper Method 有下面幾個:

每個模板化Helper Method都有兩個版本,它們除了引數不一樣,其它沒什麼區別。

我們可以用C#特性來表示的Model後設資料(Metadata)來告訴Helper Method如何為Model呈現Html元素。如:

public class Person { 
    [HiddenInput(DisplayValue=false)]
    public int PersonId { get; set; } 
    ...
} 

當在View中通過 @Html.EditorForModel 或 @Html.EditorFor(m => m.PersonId) 方法時,會生成一個隱藏的 input 元素,如下:

<input id="PersonId" name="PersonId"type="hidden" value="0" />

像這樣“指導” Helper Method 生成 Html 元素的特性還有很多,如 [Display(Name="Your name")]、[DataType(DataType.Date)]、[UIHint("MultilineText")]等。

自定義 Helper Method 模板

前面我們簡要介紹了 Helper Method 模板根據 Model 後設資料生成 Html 元素的便捷之處。但有時候MVC提供的模板並不能滿足我們的需求,這時我們可以為 Model 物件的某個屬性自定義一個 Helper Method 模板。

在前文中我們知道,使用 Html.DropDownList(For) 可以為我們建立一個下拉選單,但這個方法有一點不好使,每次使用都需要給它構造器的第二個引數指定資料來源,很是不方便。對於頻繁使用的下拉選單,我們可以把它做成模板。下面我將通過為 Role 列舉型別(前文有定義)做一個下拉選單模板來演示如何自定義。

按照約定,MVC框架會在 /Views/Shared/EditorTemplates 資料夾下查詢自定義的模板。因此我們需要先新增一個EditorTemplates 資料夾,然後在該資料夾下新增一個 Role.cshtml 檔案,程式碼如下:

@model MvcApplication1.Models.Role

@Html.DropDownListFor(m => m, new SelectList(Enum.GetNames(Model.GetType()), Model.ToString()))

這樣就做好了一個模板,我們可以像下面這樣在一個View中使用它:

@model MvcApplication1.Models.Person

@Html.EditorFor(m => m.Role)

除了 EditorFor,呼叫任何一個模板化的Helper Method 為 Role 型別的屬性呈現元素時都會顯示為一個下拉選單,效果如下:

為了讓這個功能更通用,使所有列舉型別都可以顯示為這樣的下拉選單,我們再改造一下這個功能。

刪除原來的 Role.cshtml 檔案,在同一目錄下再新建一個 Enum.cshtml 分部檢視,程式碼參考如下:

@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
        };
    })
)

然後我們可以在 Model 中通過 UIHint 特性來應用它,如下:

public class Person {
    public int PersonId { get; set; }
    ...
    [UIHint("Enum")]
    public Role Role { get; set; }
}

再執行程式看到的效果是和上面一樣的。

注意,MVC 是根據屬性的型別在 /Views/Shared/EditorTemplates 目錄下找自定義的模板的,所以一定要保證模板的檔名和屬性型別名一致(或用UIHint特性指定為模板的名稱)。

另外,如果自定義的模板和內建的模板同名,MVC會使用自定義的。可以根據這個特點來用自定義的模板替換系統內建的。例如,如果在 EditorTemplates 資料夾下建立一個 Boolean.cshtml,當MVC要為 Boolean 型別的屬性呈現 Html 元素時,它會使用自定義的 Boolean.cshtml 分部檢視來呈現。 

 


參考:Pro ASP.NET MVC 4 4th Edition

相關文章