【ASP.NET Core】標記幫助器——元素篩選

2023-02-19 18:00:38

前一篇中老周從標記幫助的底層介紹關鍵性的介面,如 ITagHelper ,它是一個標誌,用於識別哪些類屬於 Tag Helper。

標記幫助器畢竟是針對 HTML 標記的,所以得篩選。說白了就是我寫的這個幫助器在哪些 HTML 標記上起作用。這就需要拿出一個特性類。

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public sealed class HtmlTargetElementAttribute : Attribute

咱們看到,這個特性只能應用到類上面。啥類?當然是從 TagHelper 派生的類(或者實現 ITagHelper 介面的類)。

在使用時,我們一般會呼叫帶一個字串引數的建構函式。

public HtmlTargetElementAttribute(string tag)

用字串說明你這個幫助器用到哪個標記上。比如

[HtmlTargetElement("div")]
[HtmlTargetElement("a")]
[HtmlTargetElement("p")]
[HtmlTargetElement("form")]

這個應該好理解,如果設定的是「div」,表明我這個幫助器是在<div>元素上起作用的。

當然,這個特性類也有無引數的建構函式。如果呼叫此建構函式,即未指定 HTML 標記。

[HtmlTargetElement]

這相當於把標記指定為「*」(星號)。

[HtmlTargetElement("*")]

意思就是我這個幫助器是面向所有 HTML 元素的,通吃。

也許各位大夥伴也發現了,這廝篩選元素的方式很像 CSS 的選擇器。對,的確是的。但是,得記住:這貨是面向標記的,而不是特定某個元素的。啥意思?就是說你不能用元素 id 去篩選,比如這樣就不行。

[HtmlTargetElement("#abc")]

不過,可以根據屬性篩選,比如

[HtmlTargetElement("span", Attributes = "[data=1]")]

屬性篩選要放在 Attributes 屬性上,不要和標記名稱寫一起。上面程式碼是篩選有 data = "1" 的span標記。即

<span data="1">...</span>

----------------------------------------------------------------------------------------------------------

好了,概念的東西說得有點多了,咱們來做個例子。

這裡老周寫了一個面向 <span> 的標記幫助器,把此標記的內容中帶有中括號的文字掩蓋掉。比如

<span>我是一隻小小[小鳥]</span>

被中括號裹起來的是「小鳥」,所以把它掩蓋掉,變成「我是一隻小小**」,或「我是一隻小小##」。

標記幫助器程式碼如下:

namespace Test;

[HtmlTargetElement("span")]
public class ReplaceCharTagHelper : TagHelper
{
    public char MaskChar { get; set; } = '*';

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        // 下面這行程式碼的作用是讓標記執行它的子級
        // 這樣我們才能獲取到目標元素的內容
        var tagContent = await output.GetChildContentAsync();
        string text = tagContent.GetContent();
        int count = text.Length;
        if(count > 0)
        {
            // 原字串的索引不能set,因此先轉為char陣列
            var chararr = text.ToArray();
            // 這個bool變數是個開關
            // 即遇到「[」字元時開,遇到「]」字元時關
            // 後面在替換字元時用得上
            bool flag = false;
            for(int x = 0; x < count; x++)
            {
                char c = chararr[x];
                if(c == '['){
                    flag = true;    //
                    continue;
                }
                else if(c == ']')
                {
                    flag = false;   //
                    continue;
                }
                if(flag){   
                    // 如果「開」說明進入了中括號內,表示字元可替換
                    // 如果「關」說明已經出了中括號,就別替換了
                    chararr[x] = MaskChar;
                }
            }
            // 構建新的字串
            string newStr = new string(chararr);
            // 把「[」、「]」兩個字元清除
            newStr = newStr.Replace("[", "").Replace("]", "");
            // 用新的內容替換標記原來的內容
            output.Content.SetContent(newStr);
        }
    }
}

下面老周解釋一下。

1、這個幫助器是面向<span>元素的。

2、MaskChar 屬性允許咱們自己設定掩蓋文字的字元,算是一掩碼吧。

3、在處理HTML輸出時注意這一句:

var tagContent = await output.GetChildContentAsync();

為什麼要呼叫這一句呢?因為咱們要修改<span>與</span>之間的內容,你如果直接存取 output.Content.GetContent 是什麼也獲取不到的,因為此時<span>的子級內容還沒有呈現。所以啊,為了能獲取到待處理的文字,咱們要先呼叫 GetChildContentAsync 方法。這個方法會先執行子級內容,然後返回內容。

4、這裡老周的處理思路是這樣的。string 型別的範例雖然是 char 的集合,但其索引器是 get 的,不支援 set,即咱們不能直接修改其中某個字元。辦法只能先 ToArray 讓文字變成 char[],然後迴圈裡面每個字元。如果遇到「[」,表明中括號開始了(把 flag 設為 true),從下一個字元起就是中括號包含的內容,需要掩蓋掉;如果遇到「]」字元,說明要離開中括號的包圍圈(flag 設為 false),從下一個字元起就不是中括號中的字元,不能掩蓋。最後,用修改過的 char[] 產生新的字串物件,為了打掃戰場,還要把「[」、「]」去掉。這個直接用 Replace 就行了。

5、呼叫 output.Content.SetContent 方法用新的內容替換原有的內容。

在 Razor 檔案中,用 @addTagHelper 指令匯入剛自定義的標記幫助器。

@addTagHelper Test.ReplaceCharTagHelper, TestApp

這裡 TestApp 是標記幫助器所在程式集的名稱,一般與專案名字相同。我這個專案就叫 TestApp。

來,測試一下。

@page
@addTagHelper Test.ReplaceCharTagHelper, TestApp

<span mask-char="@('#')">
    明天我們去[騎行]
</span>

<span mask-char="@('*')">
    ,順便買幾噸[啤酒]喝
</span>

mask-char 就是類中定義的 MaskChar 屬性,ASP.NET Core 會識別像 mask-char 這樣的寫法,主要是語意明瞭。在設定 MaskChar 屬性時要把值寫在 @( ) 中,不能寫成 mask-char="*",否則編譯不通過的。="*" Razor 引擎預設解析為 string 型別而不是 char,而寫在 @() 中就成了 C# 表示式,編譯器能識別。

執行後的結果如下。

咱們也可以讓標記幫助器支援更多元素。

[HtmlTargetElement("span")]
[HtmlTargetElement("div")]
[HtmlTargetElement("p")]
public class ReplaceCharTagHelper : TagHelper