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;
|
||||
}
|
||||
|
||||
/// <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 />
|
||||
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -17,17 +17,20 @@ namespace Microsoft.AspNetCore.Html
|
|||
/// </summary>
|
||||
public static readonly IHtmlContent NewLine = new HtmlEncodedString(Environment.NewLine);
|
||||
|
||||
private readonly string _value;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="HtmlEncodedString"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The HTML encoded value.</param>
|
||||
public HtmlEncodedString(string value)
|
||||
{
|
||||
_value = value;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTML encoded value.
|
||||
/// </summary>
|
||||
public string Value { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
|
||||
{
|
||||
|
|
@ -41,13 +44,13 @@ namespace Microsoft.AspNetCore.Html
|
|||
throw new ArgumentNullException(nameof(encoder));
|
||||
}
|
||||
|
||||
writer.Write(_value);
|
||||
writer.Write(Value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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>
|
||||
/// A builder for HTML content.
|
||||
/// </summary>
|
||||
public interface IHtmlContentBuilder : IHtmlContent
|
||||
public interface IHtmlContentBuilder : IHtmlContentContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// 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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
foreach (var entry in Entries)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// 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;
|
||||
using System.IO;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
|
|
@ -105,6 +106,106 @@ namespace Microsoft.Extensions.Internal
|
|||
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]
|
||||
public void WriteTo_WritesAllItems()
|
||||
{
|
||||
|
|
@ -122,7 +223,7 @@ namespace Microsoft.Extensions.Internal
|
|||
Assert.Equal("Written from TestHtmlContent: HelloHtmlEncode[[Test]]", writer.ToString());
|
||||
}
|
||||
|
||||
private class TestHtmlContent : IHtmlContent
|
||||
private class TestHtmlContent : IHtmlContent, IEquatable<TestHtmlContent>
|
||||
{
|
||||
private string _content;
|
||||
|
||||
|
|
@ -140,6 +241,27 @@ namespace Microsoft.Extensions.Internal
|
|||
{
|
||||
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