ASP.NET MVC Core的TagHelper (高級特性)


這篇博文ASP.NET MVC Core的TagHelper(基礎篇)介紹了TagHelper的基本概念和創建自定義TagHelper的方式,接着繼續介紹一些新的看起來比較高級的特性。(示例代碼緊接着上一遍博文)

一、使用自定義的標記元素

之前基礎篇介紹的TagHelper的功能是給已有的HTML元素提供一個自定義的屬性標記,然后服務器認出這個標記后,將標記轉化成最終的HTML。這里將要介紹的功能是,定義個全新的Tag,看起來跟普通的HTML元素一樣。是不是覺得很熟悉呢(前提是你用過AngularJS),完全類似於AngularJS的強大的元素定義功能。

比如我們這里創建一個新的標記元素,formbutton,使用方式如下

<formbutton type="submit" bg-color="danger" />

當然這個標記完全不是HTML內部定義的,瀏覽器也不能認出這是個啥玩意。

這個Tag跟自定義的屬性標記一樣,都會被MVC Core框架識別出來,然后轉化成最終的HTML。

接下來我們創建這個TagHelper

在TagHelpers文件夾新建一個類

[HtmlTargetElement("formbutton")]
    public class FormButtonTagHelper : TagHelper
    {
        public string Type { get; set; } = "Submit";

        public string BgColor { get; set; } = "primary";

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.TagName = "button";
            output.TagMode = TagMode.StartTagAndEndTag;
            output.Attributes.SetAttribute("class", $"btn btn-{BgColor}");
            output.Attributes.SetAttribute("type", Type);
            output.Content.SetContent(Type == "submit" ? "Add" : "Reset");
        }
    }

這個class定義的兩個屬性Type和BgColor,如大部分的猜想,這兩個屬性會匹配成html中定義的屬性,然后把值自動賦給TagHelper Instance中的屬性。

Process一連串的output調用也比較直接,大概意思是要生成一個button元素,並且根據用戶提供的Type和BgColor生成class和type兩個html屬性的值。

其中SetContent是要設置需要輸出的內容,由於TagMode是StartTagAndEndTag,所以內容會顯示在標記之間。

接下來在home/create這個頁面使用我們的自定義標記

@model City
@{ Layout = "_Layout"; }
<form method="post" action="/Home/Create">
    <div class="form-group">
        <label for="Name">Name:</label>
        <input class="form-control" name="Name" />
    </div>
    <div class="form-group">
        <label for="Country">Country:</label>
        <input class="form-control" name="Country" />
    </div>
    <div class="form-group">
        <label for="Population">Population:</label>
        <input class="form-control" name="Population" />
    </div>
    <formbutton type="submit" bg-color="danger" />
    <formbutton type="reset" />
    <a bs-button-color="primary" href="/Home/Index">Cancel</a>
</form>

我們使用formbuttion分別創建了一個submit和reset按鈕,並且給submit按鈕設置了danger樣式

那么這兩個按鈕輸出后的html分別是

<button class="btn btn-danger" type="submit">Add</button>

<button class="btn btn-primary" type="reset">Reset</button>

這是創建一個基本的自定義TagHelper的使用方式。

二、在目標元素之前或者之后插入內容

上一個栗子,比較中規中矩,實際上我們經常需要給元素前后插入一些內容,通常是一些外圍包含元素。比如有如下元素

<div title="Cities"></div>

我們希望這個標記在輸出成html的時候能在前后都自動加上一個div class=panel-body的色塊,那么我么可以利用TagHelperOutput提供的方法實現。

可以創建如下的自定義TagHelper來說明

在TagHelpers文件夾新建類ContentWrapperTagHelper

[HtmlTargetElement("div", Attributes = "title")]
    public class ContentWrapperTagHelper : TagHelper
    {
        public bool IncludeHeader { get; set; } = true;
        public bool IncludeFooter { get; set; } = true;

        public string Title { get; set; }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.Attributes.SetAttribute("class", "panel-body");

            var title = new TagBuilder("h1");
            title.InnerHtml.Append(Title);

            var container = new TagBuilder("div");
            container.Attributes["class"] = "bg-info panel-body";
            container.InnerHtml.AppendHtml(title);


            if (IncludeHeader)
            {
                output.PreElement.SetHtmlContent(container);
            }

            if (IncludeFooter)
            {
                output.PostElement.SetHtmlContent(container);
            }
        }
    }

1.這里指定了TagHelper的應用范圍是包含了title屬性的div元素

2.分別提供了IncludeHeader和IncludeFooter的屬性,默認都是true

3.然后分別使用PreElement和PostElement設置前后內容

我們把這個標簽應用在_Layout文件中

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Cities</title>
    <link href="/lib/bootstrap/dist/css/bootstrap.css" rel="stylesheet" />
</head>
<body class="panel-body">
   <div title="Cities">@RenderBody()</div>
</body>
</html>

運行就能看到頭部和底部分別都輸出了一個色塊,並且包含了標題內容

三、在已有標記內容中插入內容

上一個栗子講的是插入元素,這里演示一下插入內容到標簽中,比如已有標簽里面已經有內容了,可以在內容之前或者之后插入內容。

在TagHelpers目錄新建一個TableCellTagHelper類

[HtmlTargetElement("td", Attributes = "wrap")]
    public class TableCellTagHelper : TagHelper
    {
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.PreContent.SetHtmlContent("<b><i>");
            output.PostContent.SetHtmlContent("</i></b>");
        }
    }

通過使用TagHelperOutput的PreContent和PostContent,分別在已有內容的前后插入了一段html標記包裹,這個TagHelper只會用於帶有wrap屬性的td標記。

把這個標記用在Home/Index.cshtml頁面,把city的名稱的td加入wrap屬性即可

@model IEnumerable<City>
@{ Layout = "_Layout"; }
<table class="table table-condensed table-bordered">
    <thead class="bg-primary">
        <tr>
            <th>Name</th>
            <th>Country</th>
            <th class="text-right">Population</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var city in Model)
        {
            <tr>
                <td wrap>@city.Name</td>
                <td>@city.Country</td>
                <td class="text-right">@city.Population?.ToString("#,###")</td>
            </tr>
        }
    </tbody>
</table>
<a href="/Home/Create" class="btn btn-primary">Create</a>

運行后可以看到已有的內容都被<i><b></b></i>包裹起來,呈現的是加粗和斜體的效果。

 

四、使用ViewModel提供的屬性值

在VIew里面輸出ViewModel的值,經常會用到一些強類型的幫助方法,比如asp-for="Name"等,那么實際上就會讀取ViewModel的Name的屬性值。

自定義的TagHelper也支持這種方式,我們來看一下如何調用,還是繼續在TagHelpers目錄新建一個類,如下

LabelAndInputTagHelper

[HtmlTargetElement("label", Attributes = "helper-for")]
    [HtmlTargetElement("input", Attributes = "helper-for")]
    public class LabelAndInputTagHelper : TagHelper
    {
        public ModelExpression HelperFor { get; set; }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            if (output.TagName == "label")
            {
                output.TagMode = TagMode.StartTagAndEndTag;
                output.Content.Append(HelperFor.Name);
                output.Attributes.SetAttribute("for", HelperFor.Name);
            } else if (output.TagName == "input")
            {
                output.TagMode = TagMode.SelfClosing;
                output.Attributes.SetAttribute("name", HelperFor.Name);
                output.Attributes.SetAttribute("class", "form-control");
                if (HelperFor.Metadata.ModelType == typeof(int?))
                {
                    output.Attributes.SetAttribute("type", "number");
                }
            }
        }
    }

這個TagHelper的作用用,將for屬性應用到label和input元素上,實現常見的點擊label后聚焦到input的功能。

這里一個關鍵屬性是HelperFor,用來讀取ViewModel提供的屬性的信息,它的類型是ModelExpression,看起來比較高級,用它可以很方便得到ViewModel的信息。

我們把這個TagHelper應用到Home/Create.cshtml頁面中

比如之前我們是這樣寫的

<label for="Name">Name:</label>

<input class="form-control" name="Name" />

現在用了標記之后就可以簡化成如下

<label helper-for="Name"/>

<input helper-for="Name"/>

看起來更加的整潔,和符合強迫症程序員的口味。

 

五. TagHelper之前相互通訊協同

兩個不同的TagHelper之前實際上可以通過共享數據的方式實現協同,當然共享數據的方式很多啊,比如粗暴一點的用數據,什么Session之類的(經常面試被問到的Asp.net頁面傳遞有哪些方法啊,通常是老家伙裝13的樣子在問)

當然我們不會用數據或者Session去保存共享的數據,TagHelperContext為我們提供了一個便利的實現方式,類似於HttpContent.Items,直接看看例子。

在TagHelpers文件夾新建一個CoordinatingTagHelpers文件

[HtmlTargetElement("div", Attributes = "theme")]
    public class ButtonGroupThemeTagHelper : TagHelper
    {
        public string Theme { get; set; }

        public override void Process(TagHelperContext context,
            TagHelperOutput output)
        {
            context.Items["theme"] = Theme;
        }
    }

    [HtmlTargetElement("button", ParentTag = "div")]
    [HtmlTargetElement("a", ParentTag = "div")]
    public class ButtonThemeTagHelper : TagHelper
    {
        public override void Process(TagHelperContext context,
            TagHelperOutput output)
        {
            if (context.Items.ContainsKey("theme"))
                output.Attributes.SetAttribute("class",
                    $"btn btn-{context.Items["theme"]}");
        }
    }

這個文件包含兩個TagHelper,第一個是定義了div標簽,它在context.Items里設置了theme的值,然后在另外一個TagHelper中讀取items的值。

用法簡單到沒有朋友

<div theme="primary">
        <button type="submit">Add</button>
        <button type="reset">Reset</button>
        <a href="/Home/Index">Cancel</a>
</div>

里面的button的樣式會根據外層theme的值來設置對應的樣式,比如設置theme="Danger",里面的按鈕顯示為如下樣式

image

 

六. 禁止內容輸出

最后要介紹的是禁止內容輸出。禁止內容輸出很多方法,最簡單的不顯示或者加個if else判斷。

這里使用TagHelperOutput提供的SuppressOutput方法。

新建如下TagHelper

[HtmlTargetElement(Attributes = "show-for-action")]
    public class SelectiveTagHelper : TagHelper
    {
        public string ShowForAction { get; set; }
        [ViewContext]
        [HtmlAttributeNotBound]
        public ViewContext ViewContext { get; set; }
        public override void Process(TagHelperContext context,
        TagHelperOutput output)
        {
            if (!ViewContext.RouteData.Values["action"].ToString()
            .Equals(ShowForAction, StringComparison.OrdinalIgnoreCase))
            {
                output.SuppressOutput();
            }
        }
    }

這個TagHelper定義了其標簽內容只有在當前Action跟目標Action一致的時候在顯示內容,否則調用Suppress禁止內容輸出

比如如下html標記

<div show-for-action="Index" class="panel-body bg-danger">
<h2>Important Message</h2>
</div>

指定了只有在Index action下才顯示important Message

 

示例代碼路徑

https://github.com/shenba2014/AspDotNetCoreMvcExamples/tree/master/CustomTagHelper


免責聲明!

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



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