自訂支援 ModelExpression 的 TagHelper

  • 96
  • 0

在 ASP.Net Core 微軟提供了 TagHelper 來更精簡我們在寫 View 的語法,整個使用上會比較清爽,而我們也可以很方便的自訂和擴充,但是在實做擴充上想可以支援 asp-for 卻卡住了,後來找了討論和文章,要支援的化需要使用 ModelExpression,因此針對 ModelExpression 的實做上寫了這篇實做的記錄。 

前言

在 ASP.Net Core 微軟提供了 TagHelper 來更精簡我們在寫 View 的語法,整個使用上會比較清爽,而我們也可以很方便的自訂和擴充,但是在實做擴充上想可以支援 asp-for 卻卡住了,後來找了討論和文章,要支援的化需要使用 ModelExpression,因此針對 ModelExpression 的實做上寫了這篇實做的記錄。 

實做的情境為表單的欄位名稱,預設只有單純顯示 label,而我想要可以在 Model 設定為必填欄位時候可以自動加上 * 來顯示,因此就可以用上 ModelExpression 來實做這樣的需求了。

實做

首先定義一個 LoginViewModel 並且設定好相關的 Attribute。

public class LoginViewModel
{
    /// <summary>
    /// 帳號
    /// </summary>
    [Display(Name = "帳號", Description = "請不要輸入 Admin 作為帳號!")]
    [Required(ErrorMessage = "請輸入 {0}")]
    public string Account { get; set; }
    /// <summary>
    /// 密碼
    /// </summary>
    [Display(Name = "密碼")]
    [Required(ErrorMessage = "請輸入 {0}")]
    [DataType(DataType.Password)]
    public string Password { get; set; }
}

再來實做 DisplayTitleTaghelper,裡面定義了 ModelExpressionaspFor 屬性,另外也加入了 ViewContext ,到時候可以直接呼叫內建的 TagHelper 方法來建立內容。程式重點在透過 aspFor.Metadata 來取得我們在 Model 設定的 Attribute,拿到 Metadata 之後就可以取得設定的 IsRequiredDisplayNameDescription,有了這些之後剩下的就單純用 TagBuilder 來組出我們要的 HTML了。

public class DisplayTitleTagHelper : TagHelper
{
    public ModelExpression aspFor { get; set; }

    [ViewContext]
    [HtmlAttributeNotBound]
    public ViewContext ViewContext { get; set; }

    protected IHtmlGenerator _generator { get; set; }

    public DisplayTitleTagHelper(IHtmlGenerator generator)
    {
        _generator = generator;
    }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "";
        var propMetadata = aspFor.Metadata;
        var @class = context.AllAttributes["class"].Value;

        var label = _generator.GenerateLabel(ViewContext, aspFor.ModelExplorer,
            propMetadata.Name, propMetadata.Name, new { @class });

        var strong = new TagBuilder("strong");
        strong.InnerHtml.Append(propMetadata.DisplayName);
        label.InnerHtml.Clear();
        label.InnerHtml.AppendHtml(strong);

        if (propMetadata.IsRequired)
        {
            var span = new TagBuilder("span");
            span.AddCssClass("text-danger");
            span.InnerHtml.Append("*");

            label.InnerHtml.AppendHtml(span);
        }

        output.Content.AppendHtml(label);


        if (string.IsNullOrEmpty(propMetadata.Description) == false)
        {
            var span = new TagBuilder("span");
            span.AddCssClass("text-success");
            span.InnerHtml.Append(propMetadata.Description);

            output.Content.AppendHtml(span);
        }

        var validation = _generator.GenerateValidationMessage(ViewContext, aspFor.ModelExplorer,
            propMetadata.Name, string.Empty, string.Empty, new { @class = "text-danger" });

        output.Content.AppendHtml(validation);

        base.Process(context, output);
    }
}

寫好之後就是使用了,首先就是要記得在 View 上加上  @addTagHelper "*, TagHelperForModel",這樣程式才會認得我們自定義的標籤,而在 View 上使用我們設定的標籤的語法如下,注意的點是標籤名稱是 display-title

<div class="row">
    <div class="col-md-4">
        <form asp-action="Login">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <display-title asp-for="Account" class="control-label"></display-title>
                <input asp-for="Account" class="form-control" />
            </div>
            <div class="form-group">
                <display-title asp-for="Password" class="control-label"></display-title>
                <input asp-for="Password" class="form-control" />
            </div>
            <div class="form-group">
                <input type="submit" value="Login" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

再來就是執行結果,如預期的呈獻我們要的 HTML ,顯示上也如預期,必填欄位自動加入 *,有設定 Description 也會顯示出來,並且也加上的驗證的區塊。

完整程式碼請參考 GitHub

結論

微軟在 ASP.Net Core 提供的 TagHelper,讓我們在編輯 View 的時候會更接近 HTML,因此就可以更方便讓前端人員配合來使用,透過自訂也可以有更多的可能性,也可以封裝成 DLL 之後更方便重複使用。

參考資料