如何迁移使用StringBuilder+TagBuilder进行HTML渲染?

问题描述 投票:0回答:3

我正在将项目从 .NET Framework 迁移到 .NET core。这个问题是关于 ASP MVC 项目的。我有很多

HtmlHelper
扩展方法,其中许多使用
TagBuilder
类来生成 HTML 元素,同时将内容生成到
StringBuilder

public static HtmlString DisplayTiles(this IHtmlHelper html, TileDashboardModel groups, bool displayNotEnabled = false, bool displayUnauthorized = false)
{
    if (groups == null) throw new ArgumentNullException(nameof(groups));

    var content = new StringBuilder();
    var div = new TagBuilder("div");
    content.AppendLine(div.ToString(TagRenderMode.StartTag));                     // does not build
    foreach (var group in groups.Groups)
    {
        DisplayTiles(html, content, group, displayNotEnabled, displayUnauthorized);
    }

    content.AppendLine(div.ToString(TagRenderMode.EndTag));                       // does not build

    ////return new MvcHtmlString(sb.ToString());                                  // does not build
    return new HtmlString(content.ToString());
}

在 .NET Core 中,

TagBuilder.ToString()
方法不接受任何参数。并且渲染方法似乎迫使您使用
StringWriter

您会通过哪些方式更改代码以使其正常工作?

考虑:易于更改、内存消耗、扩展方法干扰、代码可读性。


相关但不同:将 TagBuilder 核心方法从 ASP.NET MVC 5 迁移到 ASP.NET Core如何将 StringBuilder 的内容显示为 HTML?

c# asp.net-mvc asp.net-core tagbuilder
3个回答
1
投票

我发现的方法就是这些,从“最好”到最差。 完整代码在这里

初始代码

这只适用于.NET Framework。

    /// <summary>
    /// The old way.
    /// </summary>
    [Fact]
    public void Test0()
    {
        var contents = new StringBuilder();
        var tag = new TagBuilder("div");
        tag.SetInnerText("hello");                              // method does not exist
    
        contents.Append(tag.ToString(TagRenderMode.StartTag));  // missing method overload
        Assert.Equal("<div>", contents.ToString());

        contents.Append(tag.InnerHtml);
        Assert.Equal("<div>hello", contents.ToString());

        contents.Append(tag.ToString(TagRenderMode.EndTag));    // missing method overload
        Assert.Equal("<div>hello</div>", contents.ToString());

        var result = MvcHtmlString.Create(contents.ToString()); // class does not exist
        Assert.Equal("<div>hello</div>", result.ToString());
    }

最小的改变

此方法侧重于最小的变化。您只需要更改一种类型并声明 3 个扩展方法。

    /// <summary>
    /// Minimal code changes: extension methods.
    /// </summary>
    [Fact]
    public void Test4()
    {
        var contents = new StringWriter();                     // change this type
        var tag = new TagBuilder("div");
        tag.SetInnerText("hello");                             // 1 extension method

        contents.Append(tag.ToString(TagRenderMode.StartTag)); // 2 extension methods
        Assert.Equal("<div>", contents.ToString());

        contents.Append(tag.InnerHtml);                        // 1 extension method
        Assert.Equal("<div>hello", contents.ToString());

        contents.Append(tag.ToString(TagRenderMode.EndTag));   // 2 extension methods
        Assert.Equal("<div>hello</div>", contents.ToString());
    }

    public static void SetInnerText(this TagBuilder tag, string value)
    {
        tag.InnerHtml.Append(value);
    }

    public static void Append(this StringWriter writer, IHtmlContent html)
    {
        html.WriteTo(writer, HtmlEncoder.Default);
    }

    public static IHtmlContent ToString(this TagBuilder tag, TagRenderMode mode)
    {
        if (mode == TagRenderMode.StartTag)
        {
            return tag.RenderStartTag();
        }
        else if (mode == TagRenderMode.EndTag)
        {
            return tag.RenderEndTag();
        }
        else
        {
            throw new ArgumentException();
        }
    }

最小调用堆栈

这里重点强调不要使用额外的方法;代码改变很多。


    /// <summary>
    /// Use StringWriter instead of StringBuilder, no ext. methods.
    /// </summary>
    [Fact]
    public void Test3()
    {
        var contents = new StringWriter();                           // changed
        var tag = new TagBuilder("div");
        tag.InnerHtml.Append("hello");

        tag.RenderStartTag().WriteTo(contents, HtmlEncoder.Default); // changed
        Assert.Equal("<div>", contents.ToString());

        tag.RenderBody()?.WriteTo(contents, HtmlEncoder.Default);    // changed
        Assert.Equal("<div>hello", contents.ToString());

        tag.RenderEndTag().WriteTo(contents, HtmlEncoder.Default);   // changed
        Assert.Equal("<div>hello</div>", contents.ToString());
    }

内存霸主

有一种已知的扩展方法会分配过多的内存。别做这个。

    /// <summary>
    /// Use Render methods and bad extension methods.
    /// </summary>
    /// <remarks>So many StringWriters</remarks>
    [Fact]
    public void Test2()
    {
        var contents = new StringBuilder();
        var tag = new TagBuilder("div");
        tag.InnerHtml.Append("hello");
        
        contents.AppendEx(tag.RenderStartTag());
        Assert.Equal("<div>", contents.ToString());

        contents.AppendEx(tag.RenderBody());
        Assert.Equal("<div>hello", contents.ToString());

        contents.AppendEx(tag.RenderEndTag());
        Assert.Equal("<div>hello</div>", contents.ToString());
    }

    public static void AppendEx(this StringBuilder builder, IHtmlContent? content)
    {
        if (content != null)
        {
            using (var writer = new StringWriter(builder))
            {
                content.WriteTo(writer, HtmlEncoder.Default);
            }
        }
    }

这是使用

TagRenderMode
的变体。

    /// <summary>
    /// Set RenderMode and use extension methods.
    /// </summary>
    /// <remarks>So many StringWriters</remarks>
    [Fact]
    public void Test1()
    {
        var contents = new StringBuilder();
        var tag = new TagBuilder("div");
        tag.InnerHtml.Append("hello");
        
        tag.TagRenderMode = TagRenderMode.StartTag;
        contents.AppendEx(tag);
        Assert.Equal("<div>", contents.ToString());

        contents.AppendEx(tag.InnerHtml);
        Assert.Equal("<div>hello", contents.ToString());

        tag.TagRenderMode = TagRenderMode.EndTag;
        contents.AppendEx(tag);
        Assert.Equal("<div>hello</div>", contents.ToString());
    }

    public static void AppendEx(this StringBuilder contents, TagBuilder tag)
    {
        // why do we need to do that? my StringBuilder is no good enough?
        using (var writer = new StringWriter(contents))
        {
            tag.WriteTo(writer, HtmlEncoder.Default);
        }
    }

0
投票

新的

TagBuilder
(在 Microsoft.AspNetCore.Mvc.Rendering 中)与旧的
TagBuilder
(在 System.Web.Mvc 中)的行为非常不同。这使得代码更改变得非常困难;如果你没有单元测试,那就更难了;您可能会在页面上的任何地方打印“Microsoft.AspNetCore.Mvc.Rendering.TagBuilder”。

解决方法

为了避免真正迁移内容时遇到困难,最快的操作是:

  • 旧 TagBuilder.cs 复制到您的项目中
  • 重命名它
    TagBuilderOld
    (以避免类型名称冲突)
  • 复制旧TagRenderMode
  • 重命名它
    TagRenderModeOld
    (以避免类型名称冲突)
  • 将其导入您的助手中:
    using TagBuilder = System.Web.Mvc.TagBuilderOld;

    using TagRenderMode = System.Web.Mvc.TagRenderModeOld;
  • 如果你想变得疯狂,你也可以导入新的:
    using TagBuilderNew = Microsoft.AspNetCore.Mvc.Rendering.TagBuilder;

    using TagRenderModeNew = Microsoft.AspNetCore.Mvc.Rendering.TagRenderMode;

这样效果很好。页面上到处都没有“Microsoft.AspNetCore.Mvc.Rendering.TagBuilder”。

警告:这是一个解决方法!如果您这样做,您将不会获得功能和安全更新。请参阅其他答案,了解解决问题的更严肃的方法。


0
投票

是的,你应该使用StringWriter。 示例:

var container = new TagBuilder("div");
container.MergeAttributes(htmlAttrs);
using (var writer = new StringWriter())
{
  container.TagRenderMode = TagRenderMode.StartTag;
  container.WriteTo(writer, HtmlEncoder.Default);
  result = writer.ToString();
}
© www.soinside.com 2019 - 2024. All rights reserved.