MVC Core 1.0 的 Tag Helper

  • 1605
  • 0

ASP.NET Core 1.0 所搭配的是新版的 MVC,我們就稱它為 MVC 6 或 MVC Core 1.0.在整個 MVC Framework 的運作架構跟以前的版本仍是一樣的,因此 MVC 6 並沒有像 ASP.NET Core runtime 一樣整個架構都改變了.所以,你以前學的 MVC 的觀念和語法在 MVC 6 都還是可以使用.儘管運作架構上沒有改變,但細節的功能上還是有許多改進.這篇文章的內容將介紹有關 Tag Helper 的新內容. 

Tag Helper 說明

在你看這篇文章之前,我假設你已經知道什麼是 Tag Helper.在之前的版本裡已經有出現了,所以它並不是完全新的東西.當你在寫 MVC 的 View 時一定會寫到一些 razor 的內容,razor 的內容可以包含一些 HTML 和一些程式描述.這種感覺就有點像回到最早以前 ASP 的時代一樣.其中 tag helper 就是用來幫助你用一些簡單的標示就能達成一些網頁設計上需要的事情.比如,我們可以使用下面的語法來代表一個 HTML Form 

@Html.BeginForm(actionName: "Send", controllerName: "Manage")

透過 Html 這一個 tag helper 的使用,在 server-side 時它會被翻譯成 <form> 的 Html element,所以你在 brower 上就會看到 <form> 的元素在網頁的原始碼裡.因此,我相信你明白了 tag helper 是 server-side 的工作.透過它來幫助你和整個 MVC 做互動.再來看另外一個例子:

@Html.LabelFor(model => model.Email, new { @class = "css-class-name"})

上述的 tag helper 的作用就是幫你把 model 裡面的 email property 顯示在 <label> 上並且這 <label> 還會套用 CSS 裡面的設計.這樣子的方法用久了習慣後,其實沒什麼太大的感覺,但寫在 razor 裡面總是會有點怪怪的感覺,因為要寫個 model ,套用 CSS 還要寫個 class,這可是 C# 裡的關鍵字.於是,tag helper 就稍微地重新設計了一下,讓整體看起來和使用上的感覺就比較像真正在寫 tag 而已.因此,上面兩個例子在 MVC 6 就可以寫成如下:

<form asp-controller="Manage" asp-action="Send">

<label asp-for="Email" class="css-class-name"></label>

新的 tag helper 看起來的確是好多了.此時你可能會覺得奇怪,<form> 也是 HTML element,那怎麼知道它是 tag helper 呢? 其實這就是新版 tag helper 的設計,讓你使用它就像使用 HTML element 一樣,它不但保有該 HTML 的元素,還可以保有 tag helper 提供的屬性.如 asp-controller 就是 form tag helper 的其中一個屬性.當你使用 Visual Studio 2015 時,tag helper 預設上會用紫色來顯示.下圖顯示出新舊版的 <label> tag helper,你可以看到 label 是用紫色呈現,同時 asp-for 也是紫色呈現,這樣你就知道他們來自 tag helper 的內容.

新版的 tag helper 說明,你可以在這網址查到 https://docs.asp.net/en/latest/mvc/views/working-with-forms.html.雖然是英文的文件,但你只要把文件裡有關每個 tag helper 的例子看過一遍,相信你就會知道該如何使用新版的 tag helper.整個 tag helper 的原始碼在 https://github.com/aspnet/Mvc/tree/release/src/Microsoft.AspNetCore.Mvc.TagHelpers,有興趣的話,你可以看看這些原始碼,基本上跟整個 MVC framework 是整合在一起的.

在這篇文章的內容裡我就不對原始碼多做解釋了,接下來的內容我用一個例子來說明如何製做自用的 tag helper.是的,你沒看錯,你也可以創造自己的 tag helper.

自訂 Tag Helper

首先,使用 Visual Studio 開啟一個新的 ASP.NET Core Web Application,請在使用者驗證的選項選擇 Indivisual User Accounts,因為這個範本會產生基本的登入與密碼相關的網頁.專案產生完畢後,你可以打開在 Views 裡面的 _ViewImports.cshtml,它的內容如下:

@using TagHelper
@using TagHelper.Models
@using TagHelper.Models.AccountViewModels
@using TagHelper.Models.ManageViewModels
@using Microsoft.AspNetCore.Identity
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

你可以看到這邊把 Microsoft.AspNetCore.Mvc.TagHelpers 裡面的 tag helper 全部引用進來了,所以你可以使用 MVC 裡預設提供的 tag helper.現在,我們要在這個專案製作 tag helper,因此就把專案的名字加上去.我使用的專案名稱就是 TagHelperExample.

@using TagHelper
@using TagHelper.Models
@using TagHelper.Models.AccountViewModels
@using TagHelper.Models.ManageViewModels
@using Microsoft.AspNetCore.Identity
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, TagHelperExample

以下的例子將做一個 ModelPassword 的 tag helper.打開 /Views/Manage/Index.cshtml,在第 15 行開始你會看到如下的程式碼

@if (Model.HasPassword)
{
    <a asp-controller="Manage" asp-action="ChangePassword" class="btn-bracketed">Change</a>
}
else
{
    <a asp-controller="Manage" asp-action="SetPassword" class="btn-bracketed">Create</a>
}

老實說,這樣的寫法看起來有點蠢,但這也是最直覺的寫法.如果可以寫一個 <ModelHasPassword> 的 tag helper,當有密碼存在時,它會翻譯成 ChangePassword action 的那一段,如果密碼不在,就翻譯成 SetPassword action 那一段.如果像這樣的東西四處出現在你的專案裡,那你就可以用這個 tag helper 幫助你省去重覆寫一段 if .. else ... 的時間了.

首先,我在專案下建立一個 TagHelpers folder,然後在這 folder 裡建立一個 class 叫 ManageHasPasswordTagHelper,然後加上 using Microsoft.AspNetCore.Razor.TagHelpers 並且 ManageHasPasswordTagHelper 要繼承 TagHelper.

using Microsoft.AspNetCore.Razor.TagHelpers;

namespace TagHelperExample.TagHelpers
{
    public class ManageHasPasswordTagHelper : TagHelper
    {
    }
}

此時把專案 Build 之後,再回到剛剛的 index.cshtml,然後打下 <man 的字樣後,你就會看到剛剛建立的 tag helper 出現了.

此時,這個 tag helper 是一個空的 tag helper,因為我們還沒加入任何的程式碼.接下來,我們要做的就是取代表前面所說 if ... else ... 那一段的內容.一開始,我們製造一個 property 用來讓這個 tag helper 知道是要做 ChangePassword 的內容還是 SetPassword 的內容,所以我們做一個 bool property.這邊要注意的是這個 property 一定要是一個 auto property.然後再依照這個 property 的內容來決定翻譯出來 HTML 的長相要變成什麼.因此,如果是改變密碼,我們希望 HTML 的長相如下

<a href="Manage/ChangePassword">Change</a>

如果是要建立密碼,則 HTML 長相如下

<a href="Manage/SetPassword">Create</a>

所以,我們將剛剛的 tag helper 的程式碼新增如下

    public class ManageHasPasswordTagHelper : TagHelper
    {
        public bool Change { get; set; }
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.TagName = "a";

            var link = Change ? "Change" : "Create";
            output.Content.SetContent(link);

            var action = Change ? "ChangePassword" : "SetPassword";
            var href = $"/Manage/{action}";
            output.Attributes.SetAttribute("href", href);
        }
    }

TagHelper 裡有一個 Process() 需要 override,它主要的功能就是要處理邏輯.因此在一開始,設定輸出的 tag 是 a,也就是 HTML <a>,然後透過 output.Content 來決定 <a> 的顯示內容要放什麼,最過再透過 output.Attributes 來設定 <a> 的 href 屬性內容.

回到 Index.cshtml,就可以輸入如下

<manage-has-password change="@Model.HasPassword"></manage-has-password>

這樣就完成了最簡單的 tag helper 用來取代前面說的 if... else .. 那一段.但這樣的寫法過於陽春,畢竟 MVC 現在已經可以從 runtime 那邊透過 dependency injection 的方式得到許多資訊.我這邊的時間已經晚了,明天還得早起上班去,所以這篇文章先寫到這裡,下一篇文章將再繼續以這例子說明如何把這把 tag helper 寫的更好.

Hope it helps.