如何讓 TagHelper 取出 Model 屬性的特性(Attribute)

客製化 TagHelper 是 Asp.net Core 一個很重要的擴充,允許我們非常方便的建立各種彈性的運用。另外一方面在 Model 中如何定義好屬性(Property),也有相當一部分要透過 Attribute 擴充我們的定義。

以下定義一個 Attribute 用途只為了標註在特定屬性中,可以對應的代碼:

[AttributeUsage(AttributeTargets.Property)]
public class DropdownAttribute : Attribute
{
    public string CodeMap { get; set; }

    public DropdownAttribute(string code)
    {
        this.CodeMap = code;
    }
}

使用方式很簡單,直接在物件中的屬性加入此 Attribute 就可以指定代碼:

public class DailyReportItem
{
    public int Visitors { get; set; }
    
    [Required]
    [Dropdown("DailyProgress")]
    public string Progress { get; set; }
}

現在我們希望可以自訂 TagHelper,用來處理 Required attribute 的內容(上面範例的 DailyProgress)。在轉寫自訂義的 TagHelper,只有一點需要注意:HtmlAttributeName 要使用 ModelExpression 型態,不可以用 String。這裡最大差別在於如果指定 String 則 razor 會傳入值,而非物件本身;但我們需要物件本身用來解析屬性值。

程式碼如下:

public class CustomerTagHelper: TagHelper
{
    [HtmlAttributeName("asp-for")]
    public ModelExpression Source { get; set; }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "p";
        output.TagMode = TagMode.StartTagAndEndTag;

        var contents = $@"
            Model name: {Source.Metadata.ContainerType.FullName}<br/>
            Property name: {Source.Name}<br/>
            Current Value: {Source.Model}<br/> 
            Is Required: {Source.Metadata.IsRequired}";

        output.Content.SetHtmlContent(new HtmlString(contents));
    }
}

其中 IsRequired 就是代表是否有宣告 required attribute。使用方式跟一般宣告一樣:

<customer asp-for="Name"></customer>

如果需要處理 Dropdown attributte,可以用 MetaData 取出客製化的屬性:

var dropdown = model.Metadata.ContainerType.GetProperty(code)
                    .GetCustomAttributes()
                    .OfType<DropdownAttribute>().FirstOrDefault();

有問題嗎?歡迎一起討論喔!