28

It seems to me that there are major breaking changes in TagBuilder as of beta7 with no mention about them in the announcements repo.

Specifically .ToString no longer renders the tagbuilder, it just returns the name of the type. previously we could do things like this inside our HtmlHelper extensions to build up nested html elements:

var li = new TagBuilder("li");
li.AddCssClass("inactive");
var span = new TagBuilder("span");
span.SetInnerText(somestring);
li.InnerHtml = span.ToString();

.InnerHtml now no longer accepts string because it is now IHtmlContent

but since .ToString() doesn't render the tag this doesn't work either:

li.InnerHtml = new HtmlString(span.ToString())

it merely renders as "Microsoft.AspNet.Mvc.Rendering.TagBuilder", the name of the type.

I don't see any new methods on TagBuilder to provide the needed functionality. What am I missing? How can I build complex nested html with TagBuilder now?

Mihai Dinculescu
  • 19,743
  • 8
  • 55
  • 70
Joe Audette
  • 35,330
  • 11
  • 106
  • 99
  • `TagBuilder` implements `IHtmlContent` so perhaps you can just assign the TagBuilder direct to `li.InnerHtml`? – fiat Sep 06 '15 at 04:56

6 Answers6

24

Using MVC 6, at the time of writing, Tagbuiler.InnerHtml has indeed no setter anymore. It has some methods instead to append the element. For instance you could write:

var container = new TagBuilder("div");
var input = new TagBuilder("input");

container.InnerHtml.AppendHtml(input);
gunr2171
  • 16,104
  • 25
  • 61
  • 88
Memet Olsen
  • 4,578
  • 5
  • 40
  • 50
19

Because TagBuilder now implements IHtmlContent, you should be able to use it directly, without doing .ToString().

var li = new TagBuilder("li");
li.AddCssClass("inactive");
var span = new TagBuilder("span");
span.SetInnerText(somestring);
li.InnerHtml = span;

The real problem with the current implementation in Beta 7 is that there is no easy way to append two child tag builder contents to a parent one. You can follow the discussion on GitHub.

The current proposal is to make InnerHtml not assignable, but support Append instead. This is targeted to be implemented in Beta 8.

The workaround in Beta 7 is to call parent.WriteTo with a StringWriter to convert it to a string.

Mihai Dinculescu
  • 19,743
  • 8
  • 55
  • 70
  • 3
    I would have said the real problem was the vast quantity of MVC3+ code that used `TagBuilder.ToString()` in custom `HtmlHelper` methods and is now completely broken. Is it that radical to want to get an HTML string out of a class that, er, builds HTML strings? – Sam Sep 08 '15 at 09:49
  • 3
    MVC 6 is a completely different beast than MVC 5 and prior. Backwards compatibility was never a priority. The difference is comparable with the one between WebForms and MVC (vast over exaggeration - I know). You won't see many projects migrated. MVC 6 is more targeted towards new projects and complete reworks. – Mihai Dinculescu Sep 08 '15 at 09:56
  • 3
    I appreciate this, but I suspect many of these people https://github.com/search?l=csharp&q=TagBuilder+HtmlHelper&type=Code&utf8=✓ are going to get a rude shock when they migrate their reusable component code and it silently fails on them. That `ToString()` implementation violates the Principle of Least Surprise and makes migration that much more difficult, for no good reason (other than “We think people shouldn’t do this”). – Sam Sep 08 '15 at 13:56
6

Having lost span.SetInnerText(somestring); it is now possible to do

span.InnerHtml.SetContent(somestring);

using Microsoft.AspNetCore.Html.HtmlContentBuilderExtensions.

This is coming from a perspective of 4.7 with AspNetCore 2.0.1.

Alex19
  • 61
  • 1
  • 2
5

Building on @Mihai's answer to show the actual code for the StringWriter approach:

// Create tag builder
var builder = new TagBuilder("img");
//...
// Render tag approach also changed in .NetCore
builder.TagRenderMode = TagRenderMode.SelfClosing;

//Create the StringWriter and make TagBuilder "WriteTo" it
var stringWriter = new System.IO.StringWriter();
builder.WriteTo(stringWriter, HtmlEncoder.Default);
var tagBuilderIsFinallyAStringNow = stringWriter.ToString();
goamn
  • 1,939
  • 2
  • 23
  • 39
4

@Memet Olsen

All my custom TagHelpers broke with an update to 4.6.1 (1.0.0-rc2). InnerHtml.Append() will no longer accept a TagBuilder.

Instead, AppendHtml() method should be used:

var container = new TagBuilder("div");
var input = new TagBuilder("input");

container.InnerHtml.AppendHtml(input);

2nd bug-fix [here]

2

Beta 8 solved this issue by adding an Append() method to tag helpers.

For beta 7 the solution would be to use the BufferedHtmlContent() class, but since it is not accessible we must do some extra work.

private class MyBufferedHtmlContent : IHtmlContent
{
    internal List<IHtmlContent> Entries { get; } = new List<IHtmlContent>();

    public MyBufferedHtmlContent Append(IHtmlContent htmlContent)
    {
        Entries.Add(htmlContent);
        return this;
    }

    public void WriteTo(TextWriter writer, IHtmlEncoder encoder)
    {
        foreach (var entry in Entries)
        {
            entry.WriteTo(writer, encoder);
        }
    }
}

Usage:

TagBuilder firstChild = new TagBuilder("input");
firstChild.MergeAttribute("type", "hidden");
firstChild.MergeAttribute("name", "Ids");
firstChild.TagRenderMode = TagRenderMode.SelfClosing;

TagBuilder secondChild = new TagBuilder("input");
secondChild.MergeAttribute("type", "hidden");
secondChild.MergeAttribute("name", "Ids");
secondChild.TagRenderMode = TagRenderMode.SelfClosing;

var innerHtml = new MyBufferedHtmlContent();
innerHtml.Append(firstChild);
innerHtml.Append(secondChild);
TagBuilder parent = new TagBuilder("div");
parent.InnerHtml = innerHtml;
Leonardo Herrera
  • 8,388
  • 5
  • 36
  • 66