ASP.NETのTagHelperからHTMLの文字列を取得する拡張メソッドを定義するには?

Higtyのシステムの作り方

ASP.NETのTagHelperはとても便利ですが、TagHelperからHTMLを簡単に取得する方法がありません。そこでHTMLを取得する拡張メソッドを作ってみました。


using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Text.Encodings.Web;

namespace HigLabo.Web;

public static class TagHelperExtensions
{
    public static async ValueTask<string> WriteHtmlAsync(this TagHelper tagHelper)
    {
        using (var writer = new StringWriter())
        {
            await WriteToAsync(tagHelper, writer, HtmlEncoder.Default);
            return writer.ToString();
        }
    }

    public static ValueTask WriteToAsync(this TagHelper tagHelper, TextWriter writer)
    {
        return WriteToAsync(tagHelper, writer, HtmlEncoder.Default);
    }

    public static async ValueTask WriteToAsync(this TagHelper tagHelper, TextWriter writer, HtmlEncoder htmlEncoder)
    {
        var uniqueId = Guid.NewGuid().ToString(); 
        var tagHelperContext = new TagHelperContext(new TagHelperAttributeList(), new Dictionary<object, object>(), uniqueId);
        var tagHelperOutput = new TagHelperOutput(uniqueId, new TagHelperAttributeList(), (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()));

        await tagHelper.ProcessAsync(tagHelperContext, tagHelperOutput);
        tagHelperOutput.WriteTo(writer, htmlEncoder);
    }
}


これで以下のように簡単にHTMLを取得できます。

var pl = new LoadingPanelTagHelper();
var html = await pl.WriteHtmlAsync();


またTagHelperの中で別のTagHelperのHTMLを取得することも可能になります。保存ボタンの内部にロード中を示すインジケーターを表示するTagHelperを表示するには以下のように書きます。

using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System;

namespace HigLabo.Web.TagHelpers;

[HtmlTargetElement("save-button")]
public class SaveButtonTagHelper : TagHelper
{
    public string HxPost { get; set; } = "";
    public string Text { get; set; } = T.Text.Save;

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "button";
        output.Attributes.Add("class", "large-button");
        {
            var span = new TagBuilder("span");
            span.Attributes.Add("class", "text");
            span.InnerHtml.AppendHtml(this.Text);
            output.Content.AppendHtml(span);
        }
        {
            var pl = new LoadingPanelTagHelper();
            pl.Text = "";
            output.Content.AppendHtml(await pl.WriteHtmlAsync());
        }
        await base.ProcessAsync(context, output);
    }
}