Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding BufferedHtmlContent and removing BufferEntryCollection. #32

Closed
wants to merge 2 commits into from

Conversation

sornaks
Copy link

@sornaks sornaks commented Aug 3, 2015

No description provided.

/// <summary>
/// Enumerable object collection which knows how to write itself.
/// </summary>
internal class BufferedHtmlContent : IHtmlContent, IEnumerable<object>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the IEnumerable<object> for?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of the type of Entries? It can be both string and IHtmlContent..

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be IEnumerable<string> to avoid the recursion in callers. That is, copy over BufferEntryEnumerator from the deleted class.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note BufferEntryEnumerator handled nested BufferEntryCollections just fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this IEnumerable<object> exposure used anywhere other than the DefaultTagHelperContent "check" methods?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it seems like it doesn't need to be an IEnumerable<anything>, does it? It merely needs to have an IEnumerable<something>.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main counter-argument to not implementing IEnumerable<T> would have been the strangeness of a BufferEntryCollection that did not expose itself as a collection. Given the rename 👍

The Entries property is already public (called internal but this is an internal class so that's irrelevant). That means the IEnumerable<object> implementation is redundant. Just get rid of the internal for testing comment if product code e.g. the DefaultTagHelperOutput implementation uses Entries.

One option to avoid any product code using the enumeration would be DefaultTagHelperOutput calculating the IsWhiteSpace and IsEmpty values in the Append(), AppendFormat() and Clear() methods. Bit more code and that code wouldn't really be needed in the 80% case. But the code would be rather straighforward. The worst case:

    public override TagHelperContent Append(IHtmlContent htmlContent)
    {
        var tagHelperContent = htmlContent as TagHelperContent;
        if (tagHelperContent != null)
        {
            if (!tagHelperContent.IsEmpty)
            {
                _isEmpty = false;
            }
            if (!tagHelperContent.IsWhiteSpace)
            {
                _isWhiteSpace = false;
            }
        }
        else
        {
            var value = htmlContent as string;
            if (value == null)
            {
                // Mentioned GetRawString() in Razor PR.
                value = htmlContent.GetRawString();
            }

            if (string.IsNullOrEmpty(value))
            {
                // no-op
            }
            else if (string.IsNullOrWhiteSpace(value))
            {
                _isEmpty = false;
            }
            else
            {
                _isEmpty = false;
                _isWhiteSpace = false;
            }
        }

        Buffer.Append(htmlContent);
        return this;
    }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value of that change for whitespace computation seems rather dubious...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above Append method is assuming that TagHelperContent.Append can be called with string and TagHelperContent only. If we change TagBuilder to implement IHtmlContent as pointed out here, that would be Appended to TagHelperContent as well. We would end up converting TagBuilders (and other IHtmlContents) to strings even if IsEmpty/IsWhitespace is never called. So I think this is a bad idea.

The enumeration is not used anywhere apart from the check methods in DefaultTagHelperContent. I'll remove BufferedHtmlContent from being an IEnumerable. Instead I'll add constructor like:

public BufferedHtmlContent(List<object> store)

This would initialize the BufferedHtmlContent's backing store with the one passed in. With this, I can solve the enumeration problem and also conversion to strings happen only when necessary. What do you think?

@dougbu
Copy link
Member

dougbu commented Aug 3, 2015

/// Appends a <see cref="IHtmlContent"/> to the collection.
/// </summary>
/// <param name="htmlContent">The <see cref="IHtmlContent"/> to be appended.</param>
public BufferedHtmlContent Append(IHtmlContent htmlContent)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How much does performance improve if you special case appending another BufferedHtmlContent instance as BufferEntryCollection did before -- appending the passed instance's entries rather than sticking the BufferedHtmlCotnent itself into the collection? That would avoid some recursion in WriteTo() and allow the old instance to be garbage collected earlier.

- Making BufferedHtmlContent non-enumerable.
- Updating tests and comments.
@sornaks
Copy link
Author

sornaks commented Aug 7, 2015

Updated..

@dougbu
Copy link
Member

dougbu commented Aug 8, 2015

:shipit:

@dougbu
Copy link
Member

dougbu commented Aug 8, 2015

One high-level question: MVC has previously been quite conservative about HTML encoding. It generally HTML encodes everything except for a few well-known cases e.g. HtmlString instances and tag / attribute names that TagBuilder writes out.

This new system does the opposite: No string is HTML encoded unless wrapped in a StringHtmlContent instance. Is that safe? How much will the reversal confuse users?

@Eilon
Copy link
Member

Eilon commented Aug 8, 2015

@dougbu what exactly do you mean? What was previously encoded that would no longer be encoded?

@dougbu
Copy link
Member

dougbu commented Aug 8, 2015

I didn't say the system wasn't working as @sornaks has rewritten it. I said the approach has switched around -- which seems less safe and may confuse users. This PR takes us from "encode this unless it's an HtmlString" to "encode this only if it's a StringHtmlContent". (Every other IHtmlContent implementation ignores the IHtmlEncoder parameter to WriteTo().)

It's possible to create a system that behaves more like MVC 5 and earlier MVC 6 milestones. In that world BufferedHtmlContent.WriteTo() would HTML encode all string values and MVC would probably need to use HtmlString a bit more often.

@Eilon
Copy link
Member

Eilon commented Aug 8, 2015

Sorry, still not sure I understand This stuff is basically all plumbing. How, whether on purpose or by accident, would something that would previously have been encoded, no longer be encoded? What type of error, whether by us, or by a customer, would cause an issue?

@dougbu
Copy link
Member

dougbu commented Aug 8, 2015

E.g. user appends a string to a TagHelperContent instance, expecting the value will be encoded when the response is generated. Now that string will not be encoded.

@Eilon
Copy link
Member

Eilon commented Aug 8, 2015

Wait a sec - what? That sounds completely broken to me. I thought the whole point of IHtmlContent is that they represent encoded content (or, that is, content that will be assumed to be properly encoded), whereas string represents raw content that must be encoded.

Let's discuss Monday with the right set of folks.

@sornaks
Copy link
Author

sornaks commented Aug 10, 2015

Checked in. Thanks! 051a5f7

@sornaks sornaks closed this Aug 10, 2015
@natemcmaster natemcmaster deleted the BufferedHtmlContent branch June 12, 2017 21:51
@ghost ghost locked as resolved and limited conversation to collaborators May 31, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants