Add Copy/Move for content - Remove HtmlTextWriter
Currently we overload the definition of WriteTo on IHtmlContent implementation classes to either represent a "real" write or a "flatten" by checking if the writer inherits HtmlText writer. This overloading is a bit of an odd fit and hides the real semantic we want for flattening. Additionally, we want to gradually make the concept of a pooled backing-buffer for IHtmlContent first-class - using pooled buffers dictates that we support move-semantics to some degree. This change makes the work that we do for flattening into pooled buffers explicit rather than hidden.
This commit is contained in:
parent
b9d2dc89aa
commit
2705510508
|
|
@ -96,6 +96,70 @@ namespace Microsoft.AspNetCore.Html
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void CopyTo(IHtmlContentBuilder destination)
|
||||||
|
{
|
||||||
|
if (destination == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(destination));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < Entries.Count; i++)
|
||||||
|
{
|
||||||
|
var entry = Entries[i];
|
||||||
|
|
||||||
|
string entryAsString;
|
||||||
|
IHtmlContentContainer entryAsContainer;
|
||||||
|
if ((entryAsString = entry as string) != null)
|
||||||
|
{
|
||||||
|
destination.Append(entryAsString);
|
||||||
|
}
|
||||||
|
else if ((entryAsContainer = entry as IHtmlContentContainer) != null)
|
||||||
|
{
|
||||||
|
// Since we're copying, do a deep flatten.
|
||||||
|
entryAsContainer.CopyTo(destination);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Only string, IHtmlContent values can be added to the buffer.
|
||||||
|
destination.AppendHtml((IHtmlContent)entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void MoveTo(IHtmlContentBuilder destination)
|
||||||
|
{
|
||||||
|
if (destination == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(destination));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < Entries.Count; i++)
|
||||||
|
{
|
||||||
|
var entry = Entries[i];
|
||||||
|
|
||||||
|
string entryAsString;
|
||||||
|
IHtmlContentContainer entryAsContainer;
|
||||||
|
if ((entryAsString = entry as string) != null)
|
||||||
|
{
|
||||||
|
destination.Append(entryAsString);
|
||||||
|
}
|
||||||
|
else if ((entryAsContainer = entry as IHtmlContentContainer) != null)
|
||||||
|
{
|
||||||
|
// Since we're moving, do a deep flatten.
|
||||||
|
entryAsContainer.MoveTo(destination);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Only string, IHtmlContent values can be added to the buffer.
|
||||||
|
destination.AppendHtml((IHtmlContent)entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Entries.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
|
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -17,17 +17,20 @@ namespace Microsoft.AspNetCore.Html
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly IHtmlContent NewLine = new HtmlEncodedString(Environment.NewLine);
|
public static readonly IHtmlContent NewLine = new HtmlEncodedString(Environment.NewLine);
|
||||||
|
|
||||||
private readonly string _value;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="HtmlEncodedString"/>.
|
/// Creates a new <see cref="HtmlEncodedString"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">The HTML encoded value.</param>
|
/// <param name="value">The HTML encoded value.</param>
|
||||||
public HtmlEncodedString(string value)
|
public HtmlEncodedString(string value)
|
||||||
{
|
{
|
||||||
_value = value;
|
Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the HTML encoded value.
|
||||||
|
/// </summary>
|
||||||
|
public string Value { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
|
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
|
||||||
{
|
{
|
||||||
|
|
@ -41,13 +44,13 @@ namespace Microsoft.AspNetCore.Html
|
||||||
throw new ArgumentNullException(nameof(encoder));
|
throw new ArgumentNullException(nameof(encoder));
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.Write(_value);
|
writer.Write(Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return _value ?? string.Empty;
|
return Value ?? string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Html
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A <see cref="TextWriter"/> which supports special processing of <see cref="IHtmlContent"/>.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class HtmlTextWriter : TextWriter
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Writes an <see cref="IHtmlContent"/> value.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">The <see cref="IHtmlContent"/> value.</param>
|
|
||||||
public abstract void Write(IHtmlContent value);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Write(object value)
|
|
||||||
{
|
|
||||||
var htmlContent = value as IHtmlContent;
|
|
||||||
if (htmlContent == null)
|
|
||||||
{
|
|
||||||
base.Write(value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Write(htmlContent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void WriteLine(object value)
|
|
||||||
{
|
|
||||||
var htmlContent = value as IHtmlContent;
|
|
||||||
if (htmlContent == null)
|
|
||||||
{
|
|
||||||
base.Write(value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Write(htmlContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
base.WriteLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.Html
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A builder for HTML content.
|
/// A builder for HTML content.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IHtmlContentBuilder : IHtmlContent
|
public interface IHtmlContentBuilder : IHtmlContentContainer
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Appends an <see cref="IHtmlContent"/> instance.
|
/// Appends an <see cref="IHtmlContent"/> instance.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Html
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a contract for <see cref="IHtmlContent"/> instances made up of several components which
|
||||||
|
/// can be copied into an <see cref="IHtmlContentBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHtmlContentContainer : IHtmlContent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Copies the contained content of this <see cref="IHtmlContentContainer"/> into <paramref name="builder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The <see cref="IHtmlContentBuilder"/>.</param>
|
||||||
|
void CopyTo(IHtmlContentBuilder builder);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// Moves the contained content of this <see cref="IHtmlContentContainer"/> into <paramref name="builder"/>.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// After <see cref="MoveTo"/> is called, this <see cref="IHtmlContentContainer"/> instance should be left
|
||||||
|
/// in an empty state.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The <see cref="IHtmlContentBuilder"/>.</param>
|
||||||
|
void MoveTo(IHtmlContentBuilder builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -392,6 +392,20 @@ namespace Microsoft.AspNetCore.Html.Test
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CopyTo(IHtmlContentBuilder destination)
|
||||||
|
{
|
||||||
|
foreach (var entry in Entries)
|
||||||
|
{
|
||||||
|
destination.AppendHtml(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MoveTo(IHtmlContentBuilder destination)
|
||||||
|
{
|
||||||
|
CopyTo(destination);
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
|
||||||
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
|
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
|
||||||
{
|
{
|
||||||
foreach (var entry in Entries)
|
foreach (var entry in Entries)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using Microsoft.AspNetCore.Html;
|
using Microsoft.AspNetCore.Html;
|
||||||
|
|
@ -105,6 +106,106 @@ namespace Microsoft.Extensions.Internal
|
||||||
Assert.Equal(0, content.Entries.Count);
|
Assert.Equal(0, content.Entries.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CopyTo_CopiesAllItems()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var source = new HtmlContentBuilder();
|
||||||
|
source.AppendHtml(new TestHtmlContent("hello"));
|
||||||
|
source.Append("Test");
|
||||||
|
|
||||||
|
var destination = new HtmlContentBuilder();
|
||||||
|
destination.Append("some-content");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
source.CopyTo(destination);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, source.Entries.Count);
|
||||||
|
Assert.Equal(3, destination.Entries.Count);
|
||||||
|
|
||||||
|
Assert.Equal("some-content", Assert.IsType<string>(destination.Entries[0]));
|
||||||
|
Assert.Equal(new TestHtmlContent("hello"), Assert.IsType<TestHtmlContent>(destination.Entries[1]));
|
||||||
|
Assert.Equal("Test", Assert.IsType<string>(destination.Entries[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CopyTo_DoesDeepCopy()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var source = new HtmlContentBuilder();
|
||||||
|
|
||||||
|
var nested = new HtmlContentBuilder();
|
||||||
|
source.AppendHtml(nested);
|
||||||
|
nested.AppendHtml(new TestHtmlContent("hello"));
|
||||||
|
source.Append("Test");
|
||||||
|
|
||||||
|
var destination = new HtmlContentBuilder();
|
||||||
|
destination.Append("some-content");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
source.CopyTo(destination);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, source.Entries.Count);
|
||||||
|
Assert.Equal(1, nested.Entries.Count);
|
||||||
|
Assert.Equal(3, destination.Entries.Count);
|
||||||
|
|
||||||
|
Assert.Equal("some-content", Assert.IsType<string>(destination.Entries[0]));
|
||||||
|
Assert.Equal(new TestHtmlContent("hello"), Assert.IsType<TestHtmlContent>(destination.Entries[1]));
|
||||||
|
Assert.Equal("Test", Assert.IsType<string>(destination.Entries[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MoveTo_CopiesAllItems_AndClears()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var source = new HtmlContentBuilder();
|
||||||
|
source.AppendHtml(new TestHtmlContent("hello"));
|
||||||
|
source.Append("Test");
|
||||||
|
|
||||||
|
var destination = new HtmlContentBuilder();
|
||||||
|
destination.Append("some-content");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
source.MoveTo(destination);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, source.Entries.Count);
|
||||||
|
Assert.Equal(3, destination.Entries.Count);
|
||||||
|
|
||||||
|
Assert.Equal("some-content", Assert.IsType<string>(destination.Entries[0]));
|
||||||
|
Assert.Equal(new TestHtmlContent("hello"), Assert.IsType<TestHtmlContent>(destination.Entries[1]));
|
||||||
|
Assert.Equal("Test", Assert.IsType<string>(destination.Entries[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MoveTo_DoesDeepMove()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var source = new HtmlContentBuilder();
|
||||||
|
|
||||||
|
var nested = new HtmlContentBuilder();
|
||||||
|
source.AppendHtml(nested);
|
||||||
|
nested.AppendHtml(new TestHtmlContent("hello"));
|
||||||
|
source.Append("Test");
|
||||||
|
|
||||||
|
var destination = new HtmlContentBuilder();
|
||||||
|
destination.Append("some-content");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
source.MoveTo(destination);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, source.Entries.Count);
|
||||||
|
Assert.Equal(0, nested.Entries.Count);
|
||||||
|
Assert.Equal(3, destination.Entries.Count);
|
||||||
|
|
||||||
|
Assert.Equal("some-content", Assert.IsType<string>(destination.Entries[0]));
|
||||||
|
Assert.Equal(new TestHtmlContent("hello"), Assert.IsType<TestHtmlContent>(destination.Entries[1]));
|
||||||
|
Assert.Equal("Test", Assert.IsType<string>(destination.Entries[2]));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void WriteTo_WritesAllItems()
|
public void WriteTo_WritesAllItems()
|
||||||
{
|
{
|
||||||
|
|
@ -122,7 +223,7 @@ namespace Microsoft.Extensions.Internal
|
||||||
Assert.Equal("Written from TestHtmlContent: HelloHtmlEncode[[Test]]", writer.ToString());
|
Assert.Equal("Written from TestHtmlContent: HelloHtmlEncode[[Test]]", writer.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestHtmlContent : IHtmlContent
|
private class TestHtmlContent : IHtmlContent, IEquatable<TestHtmlContent>
|
||||||
{
|
{
|
||||||
private string _content;
|
private string _content;
|
||||||
|
|
||||||
|
|
@ -140,6 +241,27 @@ namespace Microsoft.Extensions.Internal
|
||||||
{
|
{
|
||||||
return "Written from TestHtmlContent: " + _content;
|
return "Written from TestHtmlContent: " + _content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return _content.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var other = obj as TestHtmlContent;
|
||||||
|
if (other != null)
|
||||||
|
{
|
||||||
|
return Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(TestHtmlContent other)
|
||||||
|
{
|
||||||
|
return string.Equals(_content, other._content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue