我正在将项目从 .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?
我发现的方法就是这些,从“最好”到最差。 完整代码在这里
这只适用于.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);
}
}
新的
TagBuilder
(在 Microsoft.AspNetCore.Mvc.Rendering 中)与旧的 TagBuilder
(在 System.Web.Mvc 中)的行为非常不同。这使得代码更改变得非常困难;如果你没有单元测试,那就更难了;您可能会在页面上的任何地方打印“Microsoft.AspNetCore.Mvc.Rendering.TagBuilder”。
为了避免真正迁移内容时遇到困难,最快的操作是:
TagBuilderOld
(以避免类型名称冲突)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”。
警告:这是一个解决方法!如果您这样做,您将不会获得功能和安全更新。请参阅其他答案,了解解决问题的更严肃的方法。
是的,你应该使用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();
}