Skip to content
This repository has been archived by the owner on Dec 19, 2018. It is now read-only.

Commit

Permalink
Introducing buffering in TagHelperOutput.PreContent, Content, PostCon…
Browse files Browse the repository at this point in the history
…tent.
  • Loading branch information
sornaks committed Mar 4, 2015
1 parent 7ecce1b commit bd9d57d
Show file tree
Hide file tree
Showing 26 changed files with 1,159 additions and 469 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.IO;
using Microsoft.Framework.Internal;

namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
/// <summary>
/// Default concrete <see cref="TagHelperContent"/>.
/// </summary>
public class DefaultTagHelperContent : TagHelperContent, ITextWriterCopyable
{
private readonly BufferEntryCollection _buffer;

/// <summary>
/// Instantiates a new instance of <see cref="DefaultTagHelperContent"/>.
/// </summary>
public DefaultTagHelperContent()
{
_buffer = new BufferEntryCollection();
}

/// <inheritdoc />
public override bool IsModified
{
get
{
return _buffer.IsModified;
}
}

/// <inheritdoc />
public override bool IsWhiteSpace
{
get
{
foreach (var value in _buffer)
{
if (!string.IsNullOrWhiteSpace(value))
{
return false;
}
}

return true;
}
}

/// <inheritdoc />
public override bool IsEmpty
{
get
{
foreach (var value in _buffer)
{
if (!string.IsNullOrEmpty(value))
{
return false;
}
}

return true;
}
}

/// <inheritdoc />
public override TagHelperContent SetContent(string value)
{
Clear();
Append(value);
return this;
}

/// <inheritdoc />
public override TagHelperContent SetContent(TagHelperContent tagHelperContent)
{
Clear();
Append(tagHelperContent);
return this;
}


/// <inheritdoc />
public override TagHelperContent Append(string value)
{
_buffer.Add(value);
return this;
}

/// <inheritdoc />
public override TagHelperContent Append(TagHelperContent tagHelperContent)
{
if (tagHelperContent != null)
{
foreach (var value in tagHelperContent)
{
Append(value);
}
}

// If Append() was called with an empty TagHelperContent IsModified should
// still be true. If the content was not already modified, it means it is empty.
// So the Clear() method can be used to indirectly set the IsModified.
if (!IsModified)
{
Clear();
}

return this;
}

/// <inheritdoc />
public override TagHelperContent Clear()
{
_buffer.Clear();
return this;
}

/// <inheritdoc />
public override string GetContent()
{
return string.Join(string.Empty, _buffer);
}

/// <inheritdoc />
public void CopyTo([NotNull] TextWriter writer)
{
foreach (var value in _buffer)
{
writer.Write(value);
}
}

/// <inheritdoc />
public override string ToString()
{
return GetContent();
}

/// <inheritdoc />
public override IEnumerator<string> GetEnumerator()
{
// The enumerator is exposed so that SetContent(TagHelperContent) and Append(TagHelperContent)
// can use this to iterate through the values of the buffer.
return _buffer.GetEnumerator();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Razor.Runtime.TagHelpers
{
/// <summary>
/// Specifies the contract for copying to a <see cref="TextWriter"/>.
/// </summary>
public interface ITextWriterCopyable
{
/// <summary>
/// Copies to the <paramref name="writer"/>.
/// </summary>
/// <param name="writer">The <see cref="TextWriter"/> target.</param>
void CopyTo(TextWriter writer);
}
}
78 changes: 78 additions & 0 deletions src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperContent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections;
using System.Collections.Generic;

namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
/// <summary>
/// Abstract class used to buffer content returned by <see cref="ITagHelper"/>s.
/// </summary>
public abstract class TagHelperContent : IEnumerable<string>
{
/// <summary>
/// Gets a value indicating whether the content was modifed.
/// </summary>
public abstract bool IsModified { get; }

/// <summary>
/// Gets a value indicating whether the content is empty.
/// </summary>
public abstract bool IsEmpty { get; }

/// <summary>
/// Gets a value indicating whether the content is whitespace.
/// </summary>
public abstract bool IsWhiteSpace { get; }

/// <summary>
/// Sets the content.
/// </summary>
/// <param name="value">The <see cref="string"/> that replaces the content.</param>
/// <returns>A reference to this instance after the set operation has completed.</returns>
public abstract TagHelperContent SetContent(string value);

/// <summary>
/// Sets the content.
/// </summary>
/// <param name="tagHelperContent">The <see cref="TagHelperContent"/> that replaces the content.</param>
/// <returns>A reference to this instance after the set operation has completed.</returns>
public abstract TagHelperContent SetContent(TagHelperContent tagHelperContent);

/// <summary>
/// Appends <paramref name="value"/> to the existing content.
/// </summary>
/// <param name="value">The <see cref="string"/> to be appended.</param>
/// <returns>A reference to this instance after the append operation has completed.</returns>
public abstract TagHelperContent Append(string value);

/// <summary>
/// Appends <paramref name="tagHelperContent"/> to the existing content.
/// </summary>
/// <param name="tagHelperContent">The <see cref="TagHelperContent"/> to be appended.</param>
/// <returns>A reference to this instance after the append operation has completed.</returns>
public abstract TagHelperContent Append(TagHelperContent tagHelperContent);

/// <summary>
/// Clears the content.
/// </summary>
/// <returns>A reference to this instance after the clear operation has completed.</returns>
public abstract TagHelperContent Clear();

/// <summary>
/// Gets the content.
/// </summary>
/// <returns>A <see cref="string"/> containing the content.</returns>
public abstract string GetContent();

/// <inheritdoc />
public abstract IEnumerator<string> GetEnumerator();

/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
/// </summary>
public class TagHelperContext
{
private readonly Func<Task<string>> _getChildContentAsync;
private readonly Func<Task<TagHelperContent>> _getChildContentAsync;

/// <summary>
/// Instantiates a new <see cref="TagHelperContext"/>.
Expand All @@ -28,7 +28,7 @@ public TagHelperContext(
[NotNull] IDictionary<string, object> allAttributes,
[NotNull] IDictionary<object, object> items,
[NotNull] string uniqueId,
[NotNull] Func<Task<string>> getChildContentAsync)
[NotNull] Func<Task<TagHelperContent>> getChildContentAsync)
{
AllAttributes = allAttributes;
Items = items;
Expand Down Expand Up @@ -59,7 +59,7 @@ public TagHelperContext(
/// A delegate used to execute and retrieve the rendered child content asynchronously.
/// </summary>
/// <returns>A <see cref="Task"/> that when executed returns content rendered by children.</returns>
public Task<string> GetChildContentAsync()
public Task<TagHelperContent> GetChildContentAsync()
{
return _getChildContentAsync();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Framework.Internal;

Expand All @@ -17,9 +15,9 @@ public class TagHelperExecutionContext
{
private readonly List<ITagHelper> _tagHelpers;
private readonly Func<Task> _executeChildContentAsync;
private readonly Action _startWritingScope;
private readonly Func<TextWriter> _endWritingScope;
private string _childContent;
private readonly Action _startTagHelperWritingScope;
private readonly Func<TagHelperContent> _endTagHelperWritingScope;
private TagHelperContent _childContent;

/// <summary>
/// Internal for testing purposes only.
Expand All @@ -30,8 +28,8 @@ internal TagHelperExecutionContext(string tagName, bool selfClosing)
items: new Dictionary<object, object>(),
uniqueId: string.Empty,
executeChildContentAsync: async () => await Task.FromResult(result: true),
startWritingScope: () => { },
endWritingScope: () => new StringWriter())
startTagHelperWritingScope: () => { },
endTagHelperWritingScope: () => new DefaultTagHelperContent())
{
}

Expand All @@ -45,21 +43,21 @@ internal TagHelperExecutionContext(string tagName, bool selfClosing)
/// <see cref="ITagHelper"/>s</param>
/// <param name="uniqueId">An identifier unique to the HTML element this context is for.</param>
/// <param name="executeChildContentAsync">A delegate used to execute the child content asynchronously.</param>
/// <param name="startWritingScope">A delegate used to start a writing scope in a Razor page.</param>
/// <param name="endWritingScope">A delegate used to end a writing scope in a Razor page.</param>
/// <param name="startTagHelperWritingScope">A delegate used to start a writing scope in a Razor page.</param>
/// <param name="endTagHelperWritingScope">A delegate used to end a writing scope in a Razor page.</param>
public TagHelperExecutionContext(
[NotNull] string tagName,
bool selfClosing,
[NotNull] IDictionary<object, object> items,
[NotNull] string uniqueId,
[NotNull] Func<Task> executeChildContentAsync,
[NotNull] Action startWritingScope,
[NotNull] Func<TextWriter> endWritingScope)
[NotNull] Action startTagHelperWritingScope,
[NotNull] Func<TagHelperContent> endTagHelperWritingScope)
{
_tagHelpers = new List<ITagHelper>();
_executeChildContentAsync = executeChildContentAsync;
_startWritingScope = startWritingScope;
_endWritingScope = endWritingScope;
_startTagHelperWritingScope = startTagHelperWritingScope;
_endTagHelperWritingScope = endTagHelperWritingScope;

SelfClosing = selfClosing;
AllAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
Expand Down Expand Up @@ -171,18 +169,18 @@ public Task ExecuteChildContentAsync()
/// <returns>A <see cref="Task"/> that on completion returns the rendered child content.</returns>
/// <remarks>
/// Child content is only executed once. Successive calls to this method or successive executions of the
/// returned <see cref="Task{string}"/> return a cached result.
/// returned <see cref="Task{TagHelperContent}"/> return a cached result.
/// </remarks>
public async Task<string> GetChildContentAsync()
public async Task<TagHelperContent> GetChildContentAsync()
{
if (_childContent == null)
{
_startWritingScope();
_startTagHelperWritingScope();
await _executeChildContentAsync();
_childContent = _endWritingScope().ToString();
_childContent = _endTagHelperWritingScope();
}

return _childContent;
return new DefaultTagHelperContent().SetContent(_childContent);
}
}
}
Loading

0 comments on commit bd9d57d

Please sign in to comment.