天天看點

[ASP.NET MVC ]13 - Helper Method自定義 Helper Method字元串編碼問題表單元素 Helper Method模闆化的 Helper Method自定義 Helper Method 模闆

我們平時程式設計寫一些輔助類的時候習慣用“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#知識點提要 文章。

上面兩種方式運作效果如下:

[ASP.NET MVC ]13 - Helper Method自定義 Helper Method字元串編碼問題表單元素 Helper Method模闆化的 Helper Method自定義 Helper Method 模闆

字元串編碼問題

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>      

運作後我們可以看到如下兩種結果:

[ASP.NET MVC ]13 - Helper Method自定義 Helper Method字元串編碼問題表單元素 Helper Method模闆化的 Helper Method自定義 Helper Method 模闆

有時候我們就是想通過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);
}      

此時運作結果如下面右圖:

[ASP.NET MVC ]13 - Helper Method自定義 Helper Method字元串編碼問題表單元素 Helper Method模闆化的 Helper Method自定義 Helper Method 模闆
[ASP.NET MVC ]13 - Helper Method自定義 Helper Method字元串編碼問題表單元素 Helper Method模闆化的 Helper Method自定義 Helper Method 模闆

表單元素 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 清單:

[ASP.NET MVC ]13 - Helper Method自定義 Helper Method字元串編碼問題表單元素 Helper Method模闆化的 Helper Method自定義 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 值如下:

如果指定的字元串參數是類似于這種的: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,如下面清單所示:

[ASP.NET MVC ]13 - Helper Method自定義 Helper Method字元串編碼問題表單元素 Helper Method模闆化的 Helper Method自定義 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(右)浏覽器中的顯示如下:

[ASP.NET MVC ]13 - Helper Method自定義 Helper Method字元串編碼問題表單元素 Helper Method模闆化的 Helper Method自定義 Helper Method 模闆
[ASP.NET MVC ]13 - Helper Method自定義 Helper Method字元串編碼問題表單元素 Helper Method模闆化的 Helper Method自定義 Helper Method 模闆

如果浏覽器足夠支援 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 有下面幾個:

[ASP.NET MVC ]13 - Helper Method自定義 Helper Method字元串編碼問題表單元素 Helper Method模闆化的 Helper Method自定義 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 類型的屬性呈現元素時都會顯示為一個下拉清單,效果如下:

[ASP.NET MVC ]13 - Helper Method自定義 Helper Method字元串編碼問題表單元素 Helper Method模闆化的 Helper Method自定義 Helper Method 模闆

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

删除原來的 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 分部視圖來呈現。

繼續閱讀