Add `TagHelperOutput.GetChildContentAsync()` overloads including an `HtmlEncoder`

- #643 part 1
- change is viral and requires an update to `RazorPage.StartTagHelperWritingScope()`
  - memoize `GetChildContentAsync()` per-encoder
  - update generation tests to match and to test new behaviour
- note `HtmlEncoder`s used elsewhere e.g. in other `RazorPage` instances are unaffected

Add `NullHtmlEncoder`

Nits:
- generally clean up affected doc comments and make them more consistent
- remove unused `using`s in files I had open
This commit is contained in:
Doug Bunting 2016-01-17 22:34:19 -08:00
parent e68c55ab41
commit 37850b5d85
23 changed files with 1409 additions and 803 deletions

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Encodings.Web;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Razor.TagHelpers; using Microsoft.AspNet.Razor.TagHelpers;
@ -15,9 +16,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{ {
private readonly List<ITagHelper> _tagHelpers; private readonly List<ITagHelper> _tagHelpers;
private readonly Func<Task> _executeChildContentAsync; private readonly Func<Task> _executeChildContentAsync;
private readonly Action _startTagHelperWritingScope; private readonly Action<HtmlEncoder> _startTagHelperWritingScope;
private readonly Func<TagHelperContent> _endTagHelperWritingScope; private readonly Func<TagHelperContent> _endTagHelperWritingScope;
private TagHelperContent _childContent; private TagHelperContent _childContent;
private Dictionary<HtmlEncoder, TagHelperContent> _perEncoderChildContent;
/// <summary> /// <summary>
/// Internal for testing purposes only. /// Internal for testing purposes only.
@ -28,7 +30,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
items: new Dictionary<object, object>(), items: new Dictionary<object, object>(),
uniqueId: string.Empty, uniqueId: string.Empty,
executeChildContentAsync: async () => await Task.FromResult(result: true), executeChildContentAsync: async () => await Task.FromResult(result: true),
startTagHelperWritingScope: () => { }, startTagHelperWritingScope: _ => { },
endTagHelperWritingScope: () => new DefaultTagHelperContent()) endTagHelperWritingScope: () => new DefaultTagHelperContent())
{ {
} }
@ -42,7 +44,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
/// <see cref="ITagHelper"/>s</param> /// <see cref="ITagHelper"/>s</param>
/// <param name="uniqueId">An identifier unique to the HTML element this context is for.</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="executeChildContentAsync">A delegate used to execute the child content asynchronously.</param>
/// <param name="startTagHelperWritingScope">A delegate used to start a writing scope in a Razor page.</param> /// <param name="startTagHelperWritingScope">
/// A delegate used to start a writing scope in a Razor page and optionally override the page's
/// <see cref="HtmlEncoder"/> within that scope.
/// </param>
/// <param name="endTagHelperWritingScope">A delegate used to end 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( public TagHelperExecutionContext(
string tagName, string tagName,
@ -50,7 +55,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
IDictionary<object, object> items, IDictionary<object, object> items,
string uniqueId, string uniqueId,
Func<Task> executeChildContentAsync, Func<Task> executeChildContentAsync,
Action startTagHelperWritingScope, Action<HtmlEncoder> startTagHelperWritingScope,
Func<TagHelperContent> endTagHelperWritingScope) Func<TagHelperContent> endTagHelperWritingScope)
{ {
if (tagName == null) if (tagName == null)
@ -224,23 +229,57 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
} }
/// <summary> /// <summary>
/// Execute and retrieve the rendered child content asynchronously. /// Executes children asynchronously with the given <paramref name="encoder"/> in scope and returns their
/// rendered content.
/// </summary> /// </summary>
/// <param name="useCachedResult">
/// If <c>true</c>, multiple calls with the same <see cref="HtmlEncoder"/> will not cause children to
/// re-execute; returns cached content.
/// </param>
/// <param name="encoder">
/// The <see cref="HtmlEncoder"/> to use when the page handles
/// non-<see cref="Microsoft.AspNet.Html.IHtmlContent"/> C# expressions. If <c>null</c>, executes children with
/// the page's current <see cref="HtmlEncoder"/>.
/// </param>
/// <returns>A <see cref="Task"/> that on completion returns the rendered child content.</returns> /// <returns>A <see cref="Task"/> that on completion returns the rendered child content.</returns>
/// <remarks> public async Task<TagHelperContent> GetChildContentAsync(bool useCachedResult, HtmlEncoder encoder)
/// Child content is only executed once. Successive calls to this method or successive executions of the
/// returned <see cref="Task{TagHelperContent}"/> return a cached result.
/// </remarks>
public async Task<TagHelperContent> GetChildContentAsync(bool useCachedResult)
{ {
if (!useCachedResult || _childContent == null) // Get cached content for this encoder.
TagHelperContent childContent;
if (encoder == null)
{ {
_startTagHelperWritingScope(); childContent = _childContent;
await _executeChildContentAsync(); }
_childContent = _endTagHelperWritingScope(); else
{
if (_perEncoderChildContent == null)
{
childContent = null;
_perEncoderChildContent = new Dictionary<HtmlEncoder, TagHelperContent>();
}
else
{
_perEncoderChildContent.TryGetValue(encoder, out childContent);
}
} }
return new DefaultTagHelperContent().SetContent(_childContent); if (!useCachedResult || childContent == null)
{
_startTagHelperWritingScope(encoder);
await _executeChildContentAsync();
childContent = _endTagHelperWritingScope();
if (encoder == null)
{
_childContent = childContent;
}
else
{
_perEncoderChildContent[encoder] = childContent;
}
}
return new DefaultTagHelperContent().SetContent(childContent);
} }
} }
} }

View File

@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Razor.TagHelpers; using Microsoft.AspNet.Razor.TagHelpers;

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Encodings.Web;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Razor.TagHelpers; using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
@ -31,7 +32,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
/// <param name="tagMode">HTML syntax of the element in the Razor source.</param> /// <param name="tagMode">HTML syntax of the element in the Razor source.</param>
/// <param name="uniqueId">An identifier unique to the HTML element this scope is for.</param> /// <param name="uniqueId">An identifier unique to the HTML element this scope is for.</param>
/// <param name="executeChildContentAsync">A delegate used to execute the child content asynchronously.</param> /// <param name="executeChildContentAsync">A delegate used to execute the child content asynchronously.</param>
/// <param name="startTagHelperWritingScope">A delegate used to start a writing scope in a Razor page.</param> /// <param name="startTagHelperWritingScope">
/// A delegate used to start a writing scope in a Razor page and optionally override the page's
/// <see cref="HtmlEncoder"/> within that scope.
/// </param>
/// <param name="endTagHelperWritingScope">A delegate used to end a writing scope in a Razor page.</param> /// <param name="endTagHelperWritingScope">A delegate used to end a writing scope in a Razor page.</param>
/// <returns>A <see cref="TagHelperExecutionContext"/> to use.</returns> /// <returns>A <see cref="TagHelperExecutionContext"/> to use.</returns>
public TagHelperExecutionContext Begin( public TagHelperExecutionContext Begin(
@ -39,7 +43,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
TagMode tagMode, TagMode tagMode,
string uniqueId, string uniqueId,
Func<Task> executeChildContentAsync, Func<Task> executeChildContentAsync,
Action startTagHelperWritingScope, Action<HtmlEncoder> startTagHelperWritingScope,
Func<TagHelperContent> endTagHelperWritingScope) Func<TagHelperContent> endTagHelperWritingScope)
{ {
if (tagName == null) if (tagName == null)

View File

@ -0,0 +1,120 @@
// 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;
namespace Microsoft.AspNet.Razor.TagHelpers
{
/// <summary>
/// A <see cref="HtmlEncoder"/> that does not encode. Should not be used when writing directly to a response
/// expected to contain valid HTML.
/// </summary>
public class NullHtmlEncoder : HtmlEncoder
{
/// <summary>
/// Initializes a <see cref="NullHtmlEncoder"/> instance.
/// </summary>
protected NullHtmlEncoder()
{
}
/// <summary>
/// A <see cref="HtmlEncoder"/> instance that does not encode. Should not be used when writing directly to a
/// response expected to contain valid HTML.
/// </summary>
public static new NullHtmlEncoder Default { get; } = new NullHtmlEncoder();
/// <inheritdoc />
public override int MaxOutputCharactersPerInputCharacter
{
get
{
return 1;
}
}
/// <inheritdoc />
public override string Encode(string value)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
return value;
}
/// <inheritdoc />
public override void Encode(TextWriter output, char[] value, int startIndex, int characterCount)
{
if (output == null)
{
throw new ArgumentNullException(nameof(output));
}
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
if (characterCount == 0)
{
return;
}
output.Write(value, startIndex, characterCount);
}
/// <inheritdoc />
public override void Encode(TextWriter output, string value, int startIndex, int characterCount)
{
if (output == null)
{
throw new ArgumentNullException(nameof(output));
}
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
if (characterCount == 0)
{
return;
}
output.Write(value.Substring(startIndex, characterCount));
}
/// <inheritdoc />
public override unsafe int FindFirstCharacterToEncode(char* text, int textLength)
{
return -1;
}
/// <inheritdoc />
public override unsafe bool TryEncodeUnicodeScalar(
int unicodeScalar,
char* buffer,
int bufferLength,
out int numberOfCharactersWritten)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
numberOfCharactersWritten = 0;
return false;
}
/// <inheritdoc />
public override bool WillEncode(int unicodeScalar)
{
return false;
}
}
}

View File

@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
/// </summary> /// </summary>
public class TagHelperOutput : IHtmlContent public class TagHelperOutput : IHtmlContent
{ {
private readonly Func<bool, Task<TagHelperContent>> _getChildContentAsync; private readonly Func<bool, HtmlEncoder, Task<TagHelperContent>> _getChildContentAsync;
private TagHelperContent _preElement; private TagHelperContent _preElement;
private TagHelperContent _preContent; private TagHelperContent _preContent;
private TagHelperContent _content; private TagHelperContent _content;
@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
: this( : this(
tagName, tagName,
new TagHelperAttributeList(), new TagHelperAttributeList(),
(cachedResult) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent())) (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()))
{ {
} }
@ -36,12 +36,14 @@ namespace Microsoft.AspNet.Razor.TagHelpers
/// </summary> /// </summary>
/// <param name="tagName">The HTML element's tag name.</param> /// <param name="tagName">The HTML element's tag name.</param>
/// <param name="attributes">The HTML attributes.</param> /// <param name="attributes">The HTML attributes.</param>
/// <param name="getChildContentAsync">A delegate used to execute and retrieve the rendered child content /// <param name="getChildContentAsync">
/// asynchronously.</param> /// A delegate used to execute children asynchronously with the given <see cref="HtmlEncoder"/> in scope and
/// return their rendered content.
/// </param>
public TagHelperOutput( public TagHelperOutput(
string tagName, string tagName,
TagHelperAttributeList attributes, TagHelperAttributeList attributes,
Func<bool, Task<TagHelperContent>> getChildContentAsync) Func<bool, HtmlEncoder, Task<TagHelperContent>> getChildContentAsync)
{ {
if (attributes == null) if (attributes == null)
{ {
@ -206,24 +208,65 @@ namespace Microsoft.AspNet.Razor.TagHelpers
} }
/// <summary> /// <summary>
/// A delegate used to execute children asynchronously. /// Executes children asynchronously and returns their rendered content.
/// </summary> /// </summary>
/// <returns>A <see cref="Task"/> that on completion returns content rendered by children.</returns> /// <returns>A <see cref="Task"/> that on completion returns content rendered by children.</returns>
/// <remarks>This method is memoized.</remarks> /// <remarks>
/// This method is memoized. Multiple calls will not cause children to re-execute with the page's original
/// <see cref="HtmlEncoder"/>.
/// </remarks>
public Task<TagHelperContent> GetChildContentAsync() public Task<TagHelperContent> GetChildContentAsync()
{ {
return GetChildContentAsync(useCachedResult: true); return GetChildContentAsync(useCachedResult: true, encoder: null);
} }
/// <summary> /// <summary>
/// A delegate used to execute children asynchronously. /// Executes children asynchronously and returns their rendered content.
/// </summary> /// </summary>
/// <param name="useCachedResult">If <c>true</c> multiple calls to this method will not cause re-execution /// <param name="useCachedResult">
/// of child content; cached content will be returned.</param> /// If <c>true</c>, multiple calls will not cause children to re-execute with the page's original
/// <see cref="HtmlEncoder"/>; returns cached content.
/// </param>
/// <returns>A <see cref="Task"/> that on completion returns content rendered by children.</returns> /// <returns>A <see cref="Task"/> that on completion returns content rendered by children.</returns>
public Task<TagHelperContent> GetChildContentAsync(bool useCachedResult) public Task<TagHelperContent> GetChildContentAsync(bool useCachedResult)
{ {
return _getChildContentAsync(useCachedResult); return GetChildContentAsync(useCachedResult, encoder: null);
}
/// <summary>
/// Executes children asynchronously with the given <paramref name="encoder"/> in scope and returns their
/// rendered content.
/// </summary>
/// <param name="encoder">
/// The <see cref="HtmlEncoder"/> to use when the page handles non-<see cref="IHtmlContent"/> C# expressions.
/// If <c>null</c>, executes children with the page's current <see cref="HtmlEncoder"/>.
/// </param>
/// <returns>A <see cref="Task"/> that on completion returns content rendered by children.</returns>
/// <remarks>
/// This method is memoized. Multiple calls with the same <see cref="HtmlEncoder"/> instance will not cause
/// children to re-execute with that encoder in scope.
/// </remarks>
public Task<TagHelperContent> GetChildContentAsync(HtmlEncoder encoder)
{
return GetChildContentAsync(useCachedResult: true, encoder: encoder);
}
/// <summary>
/// Executes children asynchronously with the given <paramref name="encoder"/> in scope and returns their
/// rendered content.
/// </summary>
/// <param name="useCachedResult">
/// If <c>true</c>, multiple calls with the same <see cref="HtmlEncoder"/> will not cause children to
/// re-execute; returns cached content.
/// </param>
/// <param name="encoder">
/// The <see cref="HtmlEncoder"/> to use when the page handles non-<see cref="IHtmlContent"/> C# expressions.
/// If <c>null</c>, executes children with the page's current <see cref="HtmlEncoder"/>.
/// </param>
/// <returns>A <see cref="Task"/> that on completion returns content rendered by children.</returns>
public Task<TagHelperContent> GetChildContentAsync(bool useCachedResult, HtmlEncoder encoder)
{
return _getChildContentAsync(useCachedResult, encoder);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -2,8 +2,9 @@
"description": "Runtime components for rendering Razor pages.", "description": "Runtime components for rendering Razor pages.",
"version": "4.0.0-*", "version": "4.0.0-*",
"compilationOptions": { "compilationOptions": {
"warningsAsErrors": true, "allowUnsafe": true,
"keyFile": "../../tools/Key.snk" "keyFile": "../../tools/Key.snk",
"warningsAsErrors": true
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -8,8 +8,8 @@ using System.Globalization;
using System.Linq; using System.Linq;
using Microsoft.AspNet.Razor.Chunks; using Microsoft.AspNet.Razor.Chunks;
using Microsoft.AspNet.Razor.CodeGenerators.Visitors; using Microsoft.AspNet.Razor.CodeGenerators.Visitors;
using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.AspNet.Razor.Compilation.TagHelpers; using Microsoft.AspNet.Razor.Compilation.TagHelpers;
using Microsoft.AspNet.Razor.TagHelpers;
namespace Microsoft.AspNet.Razor.CodeGenerators namespace Microsoft.AspNet.Razor.CodeGenerators
{ {
@ -659,7 +659,7 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
// Scopes are a runtime feature. // Scopes are a runtime feature.
if (!_designTimeMode) if (!_designTimeMode)
{ {
_writer.WriteMethodInvocation(_tagHelperContext.StartTagHelperWritingScopeMethodName); _writer.WriteMethodInvocation(_tagHelperContext.StartTagHelperWritingScopeMethodName, "null");
} }
var visitor = htmlEncodeValues ? _bodyVisitor : _literalBodyVisitor; var visitor = htmlEncodeValues ? _bodyVisitor : _literalBodyVisitor;
@ -668,8 +668,9 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
// Scopes are a runtime feature. // Scopes are a runtime feature.
if (!_designTimeMode) if (!_designTimeMode)
{ {
_writer.WriteStartAssignment(StringValueBufferVariableName) _writer
.WriteMethodInvocation(_tagHelperContext.EndTagHelperWritingScopeMethodName); .WriteStartAssignment(StringValueBufferVariableName)
.WriteMethodInvocation(_tagHelperContext.EndTagHelperWritingScopeMethodName);
} }
} }
finally finally
@ -708,7 +709,7 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
StringValueBufferVariableName, StringValueBufferVariableName,
_tagHelperContext.TagHelperContentGetContentMethodName, _tagHelperContext.TagHelperContentGetContentMethodName,
endLine: false, endLine: false,
parameters: new string[] { _tagHelperContext.HtmlEncoderPropertyName }); parameters: _tagHelperContext.HtmlEncoderPropertyName);
} }
} }

View File

@ -1,9 +1,9 @@
// 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.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.Encodings.Web;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Razor.TagHelpers; using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.Extensions.WebEncoders.Testing; using Microsoft.Extensions.WebEncoders.Testing;
@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
items: expectedItems, items: expectedItems,
uniqueId: string.Empty, uniqueId: string.Empty,
executeChildContentAsync: async () => await Task.FromResult(result: true), executeChildContentAsync: async () => await Task.FromResult(result: true),
startTagHelperWritingScope: () => { }, startTagHelperWritingScope: _ => { },
endTagHelperWritingScope: () => new DefaultTagHelperContent()); endTagHelperWritingScope: () => new DefaultTagHelperContent());
// Assert // Assert
@ -51,13 +51,47 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
Assert.Same(expectedItems, executionContext.Items); Assert.Same(expectedItems, executionContext.Items);
} }
[Fact] public static TheoryData<HtmlEncoder> HtmlEncoderData
public async Task GetChildContentAsync_CachesValue() {
get
{
return new TheoryData<HtmlEncoder>
{
null,
HtmlEncoder.Default,
NullHtmlEncoder.Default,
new HtmlTestEncoder(),
};
}
}
public static TheoryData<HtmlEncoder> HtmlEncoderDataLessNull
{
get
{
var data = new TheoryData<HtmlEncoder>();
foreach (var row in HtmlEncoderData)
{
var encoder = (HtmlEncoder)(row[0]);
if (encoder != null)
{
data.Add(encoder);
}
}
return data;
}
}
[Theory]
[MemberData(nameof(HtmlEncoderData))]
public async Task GetChildContentAsync_ReturnsExpectedContent(HtmlEncoder encoder)
{ {
// Arrange // Arrange
var defaultTagHelperContent = new DefaultTagHelperContent(); var tagHelperContent = new DefaultTagHelperContent();
var content = string.Empty; var executionCount = 0;
var expectedContent = string.Empty; var content = "Hello from child content";
var expectedContent = $"HtmlEncode[[{content}]]";
var executionContext = new TagHelperExecutionContext( var executionContext = new TagHelperExecutionContext(
"p", "p",
tagMode: TagMode.StartTagAndEndTag, tagMode: TagMode.StartTagAndEndTag,
@ -65,30 +99,46 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
uniqueId: string.Empty, uniqueId: string.Empty,
executeChildContentAsync: () => executeChildContentAsync: () =>
{ {
if (string.IsNullOrEmpty(expectedContent)) executionCount++;
{ tagHelperContent.SetContent(content);
content = "Hello from child content: " + Guid.NewGuid().ToString();
expectedContent = $"HtmlEncode[[{content}]]";
}
defaultTagHelperContent.SetContent(content);
return Task.FromResult(result: true); return Task.FromResult(result: true);
}, },
startTagHelperWritingScope: () => { }, startTagHelperWritingScope: _ => { },
endTagHelperWritingScope: () => defaultTagHelperContent); endTagHelperWritingScope: () => tagHelperContent);
// Act // Act
var content1 = await executionContext.GetChildContentAsync(useCachedResult: true); var actualContent = await executionContext.GetChildContentAsync(useCachedResult: true, encoder: encoder);
var content2 = await executionContext.GetChildContentAsync(useCachedResult: true);
// Assert // Assert
Assert.Equal(expectedContent, content1.GetContent(new HtmlTestEncoder())); Assert.Equal(expectedContent, actualContent.GetContent(new HtmlTestEncoder()));
Assert.Equal(expectedContent, content2.GetContent(new HtmlTestEncoder()));
} }
[Fact] [Theory]
public async Task GetChildContentAsync_CanExecuteChildrenMoreThanOnce() [MemberData(nameof(HtmlEncoderData))]
public async Task GetChildContentAsync_StartsWritingScopeWithGivenEncoder(HtmlEncoder encoder)
{
// Arrange
HtmlEncoder passedEncoder = null;
var executionContext = new TagHelperExecutionContext(
"p",
tagMode: TagMode.StartTagAndEndTag,
items: new Dictionary<object, object>(),
uniqueId: string.Empty,
executeChildContentAsync: () => Task.FromResult(result: true),
startTagHelperWritingScope: encoderArgument => passedEncoder = encoderArgument,
endTagHelperWritingScope: () => new DefaultTagHelperContent());
// Act
await executionContext.GetChildContentAsync(useCachedResult: true, encoder: encoder);
// Assert
Assert.Same(encoder, passedEncoder);
}
[Theory]
[MemberData(nameof(HtmlEncoderData))]
public async Task GetChildContentAsync_CachesValue(HtmlEncoder encoder)
{ {
// Arrange // Arrange
var executionCount = 0; var executionCount = 0;
@ -100,15 +150,98 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
executeChildContentAsync: () => executeChildContentAsync: () =>
{ {
executionCount++; executionCount++;
return Task.FromResult(result: true); return Task.FromResult(result: true);
}, },
startTagHelperWritingScope: () => { }, startTagHelperWritingScope: _ => { },
endTagHelperWritingScope: () => new DefaultTagHelperContent()); endTagHelperWritingScope: () => new DefaultTagHelperContent());
// Act // Act
await executionContext.GetChildContentAsync(useCachedResult: false); var content1 = await executionContext.GetChildContentAsync(useCachedResult: true, encoder: encoder);
await executionContext.GetChildContentAsync(useCachedResult: false); var content2 = await executionContext.GetChildContentAsync(useCachedResult: true, encoder: encoder);
// Assert
Assert.Equal(1, executionCount);
}
[Theory]
[MemberData(nameof(HtmlEncoderDataLessNull))]
public async Task GetChildContentAsync_CachesValuePerEncoder(HtmlEncoder encoder)
{
// Arrange
var executionCount = 0;
var executionContext = new TagHelperExecutionContext(
"p",
tagMode: TagMode.StartTagAndEndTag,
items: new Dictionary<object, object>(),
uniqueId: string.Empty,
executeChildContentAsync: () =>
{
executionCount++;
return Task.FromResult(result: true);
},
startTagHelperWritingScope: _ => { },
endTagHelperWritingScope: () => new DefaultTagHelperContent());
// Act
var content1 = await executionContext.GetChildContentAsync(useCachedResult: true, encoder: null);
var content2 = await executionContext.GetChildContentAsync(useCachedResult: true, encoder: encoder);
// Assert
Assert.Equal(2, executionCount);
}
[Theory]
[MemberData(nameof(HtmlEncoderData))]
public async Task GetChildContentAsync_CachesValuePerEncoderInstance(HtmlEncoder encoder)
{
// Arrange
var executionCount = 0;
var executionContext = new TagHelperExecutionContext(
"p",
tagMode: TagMode.StartTagAndEndTag,
items: new Dictionary<object, object>(),
uniqueId: string.Empty,
executeChildContentAsync: () =>
{
executionCount++;
return Task.FromResult(result: true);
},
startTagHelperWritingScope: _ => { },
endTagHelperWritingScope: () => new DefaultTagHelperContent());
// HtmlEncoderData includes another HtmlTestEncoder instance but method compares HtmlEncoder instances.
var firstEncoder = new HtmlTestEncoder();
// Act
var content1 = await executionContext.GetChildContentAsync(useCachedResult: true, encoder: firstEncoder);
var content2 = await executionContext.GetChildContentAsync(useCachedResult: true, encoder: encoder);
// Assert
Assert.Equal(2, executionCount);
}
[Theory]
[MemberData(nameof(HtmlEncoderData))]
public async Task GetChildContentAsync_CanExecuteChildrenMoreThanOnce(HtmlEncoder encoder)
{
// Arrange
var executionCount = 0;
var executionContext = new TagHelperExecutionContext(
"p",
tagMode: TagMode.StartTagAndEndTag,
items: new Dictionary<object, object>(),
uniqueId: string.Empty,
executeChildContentAsync: () =>
{
executionCount++;
return Task.FromResult(result: true);
},
startTagHelperWritingScope: _ => { },
endTagHelperWritingScope: () => new DefaultTagHelperContent());
// Act
await executionContext.GetChildContentAsync(useCachedResult: false, encoder: encoder);
await executionContext.GetChildContentAsync(useCachedResult: false, encoder: encoder);
// Assert // Assert
Assert.Equal(2, executionCount); Assert.Equal(2, executionCount);
@ -120,27 +253,21 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
public async Task GetChildContentAsync_ReturnsNewObjectEveryTimeItIsCalled(bool useCachedResult) public async Task GetChildContentAsync_ReturnsNewObjectEveryTimeItIsCalled(bool useCachedResult)
{ {
// Arrange // Arrange
var defaultTagHelperContent = new DefaultTagHelperContent();
var executionContext = new TagHelperExecutionContext( var executionContext = new TagHelperExecutionContext(
"p", "p",
tagMode: TagMode.StartTagAndEndTag, tagMode: TagMode.StartTagAndEndTag,
items: new Dictionary<object, object>(), items: new Dictionary<object, object>(),
uniqueId: string.Empty, uniqueId: string.Empty,
executeChildContentAsync: () => { return Task.FromResult(result: true); }, executeChildContentAsync: () => Task.FromResult(result: true),
startTagHelperWritingScope: () => { }, startTagHelperWritingScope: _ => { },
endTagHelperWritingScope: () => defaultTagHelperContent); endTagHelperWritingScope: () => new DefaultTagHelperContent());
// Act // Act
var content1 = await executionContext.GetChildContentAsync(useCachedResult); var content1 = await executionContext.GetChildContentAsync(useCachedResult, encoder: null);
content1.Append("Hello"); var content2 = await executionContext.GetChildContentAsync(useCachedResult, encoder: null);
var content2 = await executionContext.GetChildContentAsync(useCachedResult);
content2.Append("World!");
// Assert // Assert
Assert.NotSame(content1, content2); Assert.NotSame(content1, content2);
var content3 = await executionContext.GetChildContentAsync(useCachedResult);
Assert.Empty(content3.GetContent(new HtmlTestEncoder()));
} }
public static TheoryData<string, string> DictionaryCaseTestingData public static TheoryData<string, string> DictionaryCaseTestingData
@ -314,7 +441,6 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
Assert.Equal(2, tagHelpers.Length); Assert.Equal(2, tagHelpers.Length);
Assert.Same(tagHelper1, tagHelpers[0]); Assert.Same(tagHelper1, tagHelpers[0]);
Assert.Same(tagHelper2, tagHelpers[1]); Assert.Same(tagHelper2, tagHelpers[1]);
} }
private class PTagHelper : TagHelper private class PTagHelper : TagHelper

View File

@ -1,698 +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;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.Extensions.WebEncoders.Testing;
using Xunit;
namespace Microsoft.AspNet.Razor.Runtime.Test.Runtime.TagHelpers
{
public class TagHelperOutputTest
{
public static TheoryData<TagHelperOutput, string> WriteTagHelper_InputData
{
get
{
// parameters: TagHelperOutput, expectedOutput
return new TheoryData<TagHelperOutput, string>
{
{
// parameters: TagName, Attributes, SelfClosing, PreContent, Content, PostContent
GetTagHelperOutput(
tagName: "div",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<div>Hello World!</div>"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"Hello World!"
},
{
GetTagHelperOutput(
tagName: " ",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"Hello World!"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList() { { "test", "testVal" } },
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p test=\"HtmlEncode[[testVal]]\">Hello World!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList()
{
{ "test", "testVal" },
{ "something", " spaced " }
},
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p test=\"HtmlEncode[[testVal]]\" something=\"HtmlEncode[[ spaced ]]\">Hello World!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList()
{
["test"] = new TagHelperAttribute
{
Name = "test",
Minimized = true
},
},
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p test>Hello World!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList()
{
["test"] = new TagHelperAttribute
{
Name = "test",
Minimized = true
},
["test2"] = new TagHelperAttribute
{
Name = "test2",
Minimized = true
},
},
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p test test2>Hello World!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList()
{
["first"] = "unminimized",
["test"] = new TagHelperAttribute
{
Name = "test",
Minimized = true
},
},
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p first=\"HtmlEncode[[unminimized]]\" test>Hello World!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList()
{
["test"] = new TagHelperAttribute
{
Name = "test",
Minimized = true
},
["last"] = "unminimized",
},
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p test last=\"HtmlEncode[[unminimized]]\">Hello World!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList() { { "test", "testVal" } },
tagMode: TagMode.SelfClosing,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p test=\"HtmlEncode[[testVal]]\" />"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList()
{
{ "test", "testVal" },
{ "something", " spaced " }
},
tagMode: TagMode.SelfClosing,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p test=\"HtmlEncode[[testVal]]\" something=\"HtmlEncode[[ spaced ]]\" />"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList() { { "test", "testVal" } },
tagMode: TagMode.StartTagOnly,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p test=\"HtmlEncode[[testVal]]\">"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList()
{
{ "test", "testVal" },
{ "something", " spaced " }
},
tagMode: TagMode.StartTagOnly,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p test=\"HtmlEncode[[testVal]]\" something=\"HtmlEncode[[ spaced ]]\">"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: "Hello World!",
content: null,
postContent: null,
postElement: null),
"<p>Hello World!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p>Hello World!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: null,
postContent: "Hello World!",
postElement: null),
"<p>Hello World!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: null),
"<p>HelloTestWorld!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.SelfClosing,
preElement: null,
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: null),
"<p />"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagOnly,
preElement: null,
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: null),
"<p>"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: null),
"<custom>HelloTestWorld!</custom>"
},
{
GetTagHelperOutput(
tagName: "random",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.SelfClosing,
preElement: null,
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: null),
"<random />"
},
{
GetTagHelperOutput(
tagName: "random",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagOnly,
preElement: null,
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: null),
"<random>"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: "Before",
preContent: null,
content: null,
postContent: null,
postElement: null),
"Before<custom></custom>"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: "Before",
preContent: null,
content: null,
postContent: null,
postElement: null),
"Before"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.SelfClosing,
preElement: "Before",
preContent: null,
content: null,
postContent: null,
postElement: null),
"Before"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.SelfClosing,
preElement: "Before",
preContent: null,
content: null,
postContent: null,
postElement: null),
"Before<custom test=\"HtmlEncode[[testVal]]\" />"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.SelfClosing,
preElement: "Before",
preContent: null,
content: null,
postContent: null,
postElement: null),
"Before<custom />"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.StartTagOnly,
preElement: "Before",
preContent: null,
content: null,
postContent: null,
postElement: null),
"Before"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.StartTagOnly,
preElement: "Before",
preContent: null,
content: null,
postContent: null,
postElement: null),
"Before<custom test=\"HtmlEncode[[testVal]]\">"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagOnly,
preElement: "Before",
preContent: null,
content: null,
postContent: null,
postElement: null),
"Before<custom>"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: null,
postContent: null,
postElement: "After"),
"<custom></custom>After"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: null,
postContent: null,
postElement: "After"),
"After"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.SelfClosing,
preElement: null,
preContent: null,
content: null,
postContent: null,
postElement: "After"),
"After"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.SelfClosing,
preElement: null,
preContent: null,
content: null,
postContent: null,
postElement: "After"),
"<custom test=\"HtmlEncode[[testVal]]\" />After"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.SelfClosing,
preElement: null,
preContent: null,
content: null,
postContent: null,
postElement: "After"),
"<custom />After"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.StartTagOnly,
preElement: null,
preContent: null,
content: null,
postContent: null,
postElement: "After"),
"After"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.StartTagOnly,
preElement: null,
preContent: null,
content: null,
postContent: null,
postElement: "After"),
"<custom test=\"HtmlEncode[[testVal]]\">After"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagOnly,
preElement: null,
preContent: null,
content: null,
postContent: null,
postElement: "After"),
"<custom>After"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: "Before",
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: "After"),
"Before<custom>HelloTestWorld!</custom>After"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.StartTagAndEndTag,
preElement: "Before",
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: "After"),
"Before<custom test=\"HtmlEncode[[testVal]]\">HelloTestWorld!</custom>After"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.SelfClosing,
preElement: "Before",
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: "After"),
"Before<custom />After"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList(),
tagMode: TagMode.SelfClosing,
preElement: "Before",
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: "After"),
"BeforeHelloTestWorld!After"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagOnly,
preElement: "Before",
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: "After"),
"Before<custom>After"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagOnly,
preElement: "Before",
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: "After"),
"BeforeHelloTestWorld!After"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: "Before",
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: "After"),
"BeforeHelloTestWorld!After"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.StartTagAndEndTag,
preElement: "Before",
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: "After"),
"BeforeHelloTestWorld!After"
},
};
}
}
[Theory]
[MemberData(nameof(WriteTagHelper_InputData))]
public void TagHelperOutput_WritesFormattedTagHelper(TagHelperOutput output, string expected)
{
// Arrange
var writer = new StringWriter();
var tagHelperExecutionContext = new TagHelperExecutionContext(
tagName: output.TagName,
tagMode: output.TagMode,
items: new Dictionary<object, object>(),
uniqueId: string.Empty,
executeChildContentAsync: () => Task.FromResult(result: true),
startTagHelperWritingScope: () => { },
endTagHelperWritingScope: () => new DefaultTagHelperContent());
tagHelperExecutionContext.Output = output;
var testEncoder = new HtmlTestEncoder();
// Act
output.WriteTo(writer, testEncoder);
// Assert
Assert.Equal(expected, writer.ToString(), StringComparer.Ordinal);
}
private static TagHelperOutput GetTagHelperOutput(
string tagName,
TagHelperAttributeList attributes,
TagMode tagMode,
string preElement,
string preContent,
string content,
string postContent,
string postElement)
{
var output = new TagHelperOutput(
tagName,
attributes,
getChildContentAsync: (_) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()))
{
TagMode = tagMode
};
if (preElement != null)
{
output.PreElement.AppendHtml(preElement);
}
if (preContent != null)
{
output.PreContent.AppendHtml(preContent);
}
if (content != null)
{
output.Content.AppendHtml(content);
}
if (postContent != null)
{
output.PostContent.AppendHtml(postContent);
}
if (postElement != null)
{
output.PostElement.AppendHtml(postElement);
}
return output;
}
}
}

View File

@ -167,7 +167,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
Assert.Equal(tagMode, executionContext.TagMode); Assert.Equal(tagMode, executionContext.TagMode);
} }
[Fact] [Fact]
public void End_ReturnsParentExecutionContext() public void End_ReturnsParentExecutionContext()
{ {
// Arrange // Arrange
@ -228,7 +228,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
tagMode, tagMode,
uniqueId: string.Empty, uniqueId: string.Empty,
executeChildContentAsync: async () => await Task.FromResult(result: true), executeChildContentAsync: async () => await Task.FromResult(result: true),
startTagHelperWritingScope: () => { }, startTagHelperWritingScope: _ => { },
endTagHelperWritingScope: () => new DefaultTagHelperContent()); endTagHelperWritingScope: () => new DefaultTagHelperContent());
} }
} }

View File

@ -0,0 +1,158 @@
// 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;
using Xunit;
namespace Microsoft.AspNet.Razor.TagHelpers
{
public class NullHtmlEncoderTest
{
[Fact]
public void MaxOutputCharactersPerInputCharacter_Returns1()
{
// Arrange
var encoder = NullHtmlEncoder.Default;
// Act
var result = encoder.MaxOutputCharactersPerInputCharacter;
// Assert
Assert.Equal(1, result);
}
[Theory]
[InlineData("")]
[InlineData("abcd")]
[InlineData("<<''\"\">>")]
public void Encode_String_DoesNotEncode(string value)
{
// Arrange
var encoder = NullHtmlEncoder.Default;
// Act
var result = encoder.Encode(value);
// Assert
Assert.Equal(value, result);
}
[Theory]
[InlineData("")]
[InlineData("abcd")]
[InlineData("<<''\"\">>")]
public void Encode_StringToTextWriter_DoesNotEncode(string value)
{
// Arrange
var encoder = NullHtmlEncoder.Default;
// Act
string result;
using (var writer = new StringWriter())
{
encoder.Encode(writer, value);
result = writer.ToString();
}
// Assert
Assert.Equal(value, result);
}
[Theory]
[InlineData("", 0, 0, "")]
[InlineData("abcd", 0, 0, "")]
[InlineData("<<''\"\">>", 0, 0, "")]
[InlineData("abcd", 0, 1, "a")]
[InlineData("<<''\"\">>", 0, 1, "<")]
[InlineData("abcd", 0, 4, "abcd")]
[InlineData("<<''\"\">>", 0, 8, "<<''\"\">>")]
[InlineData("abcd", 2, 0, "")]
[InlineData("<<''\"\">>", 2, 0, "")]
[InlineData("abcd", 2, 2, "cd")]
[InlineData("<<''\"\">>", 2, 2, "''")]
[InlineData("abcd", 3, 0, "")]
[InlineData("<<''\"\">>", 7, 0, "")]
[InlineData("abcd", 3, 1, "d")]
[InlineData("<<''\"\">>", 7, 1, ">")]
public void Encode_StringToTextWriter_DoesNotEncodeSubstring(
string value,
int startIndex,
int characterCount,
string expectedResult)
{
// Arrange
var encoder = NullHtmlEncoder.Default;
// Act
string result;
using (var writer = new StringWriter())
{
encoder.Encode(writer, value, startIndex, characterCount);
result = writer.ToString();
}
// Assert
Assert.Equal(expectedResult, result);
}
[Theory]
[InlineData("", 0, 0, "")]
[InlineData("abcd", 0, 0, "")]
[InlineData("<<''\"\">>", 0, 0, "")]
[InlineData("abcd", 0, 1, "a")]
[InlineData("<<''\"\">>", 0, 1, "<")]
[InlineData("abcd", 0, 4, "abcd")]
[InlineData("<<''\"\">>", 0, 8, "<<''\"\">>")]
[InlineData("abcd", 2, 0, "")]
[InlineData("<<''\"\">>", 2, 0, "")]
[InlineData("abcd", 2, 2, "cd")]
[InlineData("<<''\"\">>", 2, 2, "''")]
[InlineData("abcd", 3, 0, "")]
[InlineData("<<''\"\">>", 7, 0, "")]
[InlineData("abcd", 3, 1, "d")]
[InlineData("<<''\"\">>", 7, 1, ">")]
public void Encode_CharsToTextWriter_DoesNotEncodeSubstring(
string value,
int startIndex,
int characterCount,
string expectedResult)
{
// Arrange
var encoder = NullHtmlEncoder.Default;
var chars = value.ToCharArray();
// Act
string result;
using (var writer = new StringWriter())
{
encoder.Encode(writer, chars, startIndex, characterCount);
result = writer.ToString();
}
// Assert
Assert.Equal(expectedResult, result);
}
[Theory]
[InlineData(int.MaxValue)]
[InlineData(int.MinValue)]
[InlineData(0)]
[InlineData((int)'\n')]
[InlineData((int)'\r')]
[InlineData((int)'<')]
[InlineData((int)'>')]
[InlineData((int)'\'')]
[InlineData((int)'"')]
public void WillEncode_ReturnsFalse(int unicodeScalar)
{
// Arrange
var encoder = NullHtmlEncoder.Default;
// Act
var result = encoder.WillEncode(unicodeScalar);
// Assert
Assert.False(result);
}
}
}

View File

@ -1,7 +1,12 @@
// 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.Collections.Generic;
using System.IO;
using System.Text.Encodings.Web;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.Extensions.WebEncoders.Testing; using Microsoft.Extensions.WebEncoders.Testing;
using Xunit; using Xunit;
@ -9,27 +14,148 @@ namespace Microsoft.AspNet.Razor.TagHelpers
{ {
public class TagHelperOutputTest public class TagHelperOutputTest
{ {
[Theory] [Fact]
[InlineData(true)] public async Task GetChildContentAsync_CallsGetChildContentAsync()
[InlineData(false)]
public async Task GetChildContentAsync_PassesUseCachedResultAsExpected(bool expectedUseCachedResultValue)
{ {
// Arrange // Arrange
bool? useCachedResultValue = null; bool? passedUseCacheResult = null;
HtmlEncoder passedEncoder = null;
var content = new DefaultTagHelperContent();
var output = new TagHelperOutput( var output = new TagHelperOutput(
tagName: "p", tagName: "tag",
attributes: new TagHelperAttributeList(), attributes: new TagHelperAttributeList(),
getChildContentAsync: useCachedResult => getChildContentAsync: (useCachedResult, encoder) =>
{ {
useCachedResultValue = useCachedResult; passedUseCacheResult = useCachedResult;
return Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()); passedEncoder = encoder;
return Task.FromResult<TagHelperContent>(content);
}); });
// Act // Act
await output.GetChildContentAsync(expectedUseCachedResultValue); var result = await output.GetChildContentAsync();
// Assert // Assert
Assert.Equal(expectedUseCachedResultValue, useCachedResultValue); Assert.True(passedUseCacheResult.HasValue);
Assert.True(passedUseCacheResult.Value);
Assert.Null(passedEncoder);
Assert.Same(content, result);
}
public static TheoryData<HtmlEncoder> HtmlEncoderData
{
get
{
return new TheoryData<HtmlEncoder>
{
null,
HtmlEncoder.Default,
NullHtmlEncoder.Default,
new HtmlTestEncoder(),
};
}
}
[Theory]
[MemberData(nameof(HtmlEncoderData))]
public async Task GetChildContentAsync_CallsGetChildContentAsync(HtmlEncoder encoder)
{
// Arrange
bool? passedUseCacheResult = null;
HtmlEncoder passedEncoder = null;
var content = new DefaultTagHelperContent();
var output = new TagHelperOutput(
tagName: "tag",
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoderArgument) =>
{
passedUseCacheResult = useCachedResult;
passedEncoder = encoderArgument;
return Task.FromResult<TagHelperContent>(content);
});
// Act
var result = await output.GetChildContentAsync(encoder);
// Assert
Assert.True(passedUseCacheResult.HasValue);
Assert.True(passedUseCacheResult.Value);
Assert.Same(encoder, passedEncoder);
Assert.Same(content, result);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task GetChildContentAsync_CallsGetChildContentAsync(bool useCachedResult)
{
// Arrange
bool? passedUseCacheResult = null;
HtmlEncoder passedEncoder = null;
var content = new DefaultTagHelperContent();
var output = new TagHelperOutput(
tagName: "tag",
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResultArgument, encoder) =>
{
passedUseCacheResult = useCachedResultArgument;
passedEncoder = encoder;
return Task.FromResult<TagHelperContent>(content);
});
// Act
var result = await output.GetChildContentAsync(useCachedResult);
// Assert
Assert.True(passedUseCacheResult.HasValue);
Assert.Equal(useCachedResult, passedUseCacheResult.Value);
Assert.Null(passedEncoder);
Assert.Same(content, result);
}
public static TheoryData<bool, HtmlEncoder> UseCachedResultAndHtmlEncoderData
{
get
{
var data = new TheoryData<bool, HtmlEncoder>();
foreach (var useCachedResult in new[] { false, true })
{
foreach (var encoderEntry in HtmlEncoderData)
{
var encoder = (HtmlEncoder)(encoderEntry[0]);
data.Add(useCachedResult, encoder);
}
}
return data;
}
}
[Theory]
[MemberData(nameof(UseCachedResultAndHtmlEncoderData))]
public async Task GetChildContentAsync_CallsGetChildContentAsync(bool useCachedResult, HtmlEncoder encoder)
{
// Arrange
bool? passedUseCacheResult = null;
HtmlEncoder passedEncoder = null;
var content = new DefaultTagHelperContent();
var output = new TagHelperOutput(
tagName: "tag",
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResultArgument, encoderArgument) =>
{
passedUseCacheResult = useCachedResultArgument;
passedEncoder = encoderArgument;
return Task.FromResult<TagHelperContent>(content);
});
// Act
var result = await output.GetChildContentAsync(useCachedResult, encoder);
// Assert
Assert.True(passedUseCacheResult.HasValue);
Assert.Equal(useCachedResult, passedUseCacheResult.Value);
Assert.Same(encoder, passedEncoder);
Assert.Same(content, result);
} }
[Fact] [Fact]
@ -175,13 +301,14 @@ namespace Microsoft.AspNet.Razor.TagHelpers
public void SuppressOutput_PreventsTagOutput() public void SuppressOutput_PreventsTagOutput()
{ {
// Arrange // Arrange
var tagHelperOutput = new TagHelperOutput("p", var tagHelperOutput = new TagHelperOutput(
"p",
new TagHelperAttributeList new TagHelperAttributeList
{ {
{ "class", "btn" }, { "class", "btn" },
{ "something", " spaced " } { "something", " spaced " }
}, },
(cachedResult) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent())); (cachedResult, encoder) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()));
tagHelperOutput.PreContent.Append("Pre Content"); tagHelperOutput.PreContent.Append("Pre Content");
tagHelperOutput.Content.Append("Content"); tagHelperOutput.Content.Append("Content");
tagHelperOutput.PostContent.Append("Post Content"); tagHelperOutput.PostContent.Append("Post Content");
@ -209,12 +336,13 @@ namespace Microsoft.AspNet.Razor.TagHelpers
public void Attributes_IgnoresCase(string originalName, string updateName) public void Attributes_IgnoresCase(string originalName, string updateName)
{ {
// Arrange // Arrange
var tagHelperOutput = new TagHelperOutput("p", var tagHelperOutput = new TagHelperOutput(
"p",
new TagHelperAttributeList new TagHelperAttributeList
{ {
{ originalName, "btn" }, { originalName, "btn" },
}, },
(cachedResult) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent())); (cachedResult, encoder) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()));
// Act // Act
tagHelperOutput.Attributes[updateName] = "super button"; tagHelperOutput.Attributes[updateName] = "super button";
@ -226,5 +354,687 @@ namespace Microsoft.AspNet.Razor.TagHelpers
attribute, attribute,
CaseSensitiveTagHelperAttributeComparer.Default); CaseSensitiveTagHelperAttributeComparer.Default);
} }
public static TheoryData<TagHelperOutput, string> WriteTagHelper_InputData
{
get
{
// parameters: TagHelperOutput, expectedOutput
return new TheoryData<TagHelperOutput, string>
{
{
// parameters: TagName, Attributes, SelfClosing, PreContent, Content, PostContent
GetTagHelperOutput(
tagName: "div",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<div>Hello World!</div>"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"Hello World!"
},
{
GetTagHelperOutput(
tagName: " ",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"Hello World!"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList() { { "test", "testVal" } },
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p test=\"HtmlEncode[[testVal]]\">Hello World!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList()
{
{ "test", "testVal" },
{ "something", " spaced " }
},
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p test=\"HtmlEncode[[testVal]]\" something=\"HtmlEncode[[ spaced ]]\">Hello World!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList()
{
["test"] = new TagHelperAttribute
{
Name = "test",
Minimized = true
},
},
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p test>Hello World!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList()
{
["test"] = new TagHelperAttribute
{
Name = "test",
Minimized = true
},
["test2"] = new TagHelperAttribute
{
Name = "test2",
Minimized = true
},
},
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p test test2>Hello World!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList()
{
["first"] = "unminimized",
["test"] = new TagHelperAttribute
{
Name = "test",
Minimized = true
},
},
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p first=\"HtmlEncode[[unminimized]]\" test>Hello World!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList()
{
["test"] = new TagHelperAttribute
{
Name = "test",
Minimized = true
},
["last"] = "unminimized",
},
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p test last=\"HtmlEncode[[unminimized]]\">Hello World!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList() { { "test", "testVal" } },
tagMode: TagMode.SelfClosing,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p test=\"HtmlEncode[[testVal]]\" />"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList()
{
{ "test", "testVal" },
{ "something", " spaced " }
},
tagMode: TagMode.SelfClosing,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p test=\"HtmlEncode[[testVal]]\" something=\"HtmlEncode[[ spaced ]]\" />"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList() { { "test", "testVal" } },
tagMode: TagMode.StartTagOnly,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p test=\"HtmlEncode[[testVal]]\">"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList()
{
{ "test", "testVal" },
{ "something", " spaced " }
},
tagMode: TagMode.StartTagOnly,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p test=\"HtmlEncode[[testVal]]\" something=\"HtmlEncode[[ spaced ]]\">"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: "Hello World!",
content: null,
postContent: null,
postElement: null),
"<p>Hello World!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: "Hello World!",
postContent: null,
postElement: null),
"<p>Hello World!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: null,
postContent: "Hello World!",
postElement: null),
"<p>Hello World!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: null),
"<p>HelloTestWorld!</p>"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.SelfClosing,
preElement: null,
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: null),
"<p />"
},
{
GetTagHelperOutput(
tagName: "p",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagOnly,
preElement: null,
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: null),
"<p>"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: null),
"<custom>HelloTestWorld!</custom>"
},
{
GetTagHelperOutput(
tagName: "random",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.SelfClosing,
preElement: null,
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: null),
"<random />"
},
{
GetTagHelperOutput(
tagName: "random",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagOnly,
preElement: null,
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: null),
"<random>"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: "Before",
preContent: null,
content: null,
postContent: null,
postElement: null),
"Before<custom></custom>"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: "Before",
preContent: null,
content: null,
postContent: null,
postElement: null),
"Before"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.SelfClosing,
preElement: "Before",
preContent: null,
content: null,
postContent: null,
postElement: null),
"Before"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.SelfClosing,
preElement: "Before",
preContent: null,
content: null,
postContent: null,
postElement: null),
"Before<custom test=\"HtmlEncode[[testVal]]\" />"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.SelfClosing,
preElement: "Before",
preContent: null,
content: null,
postContent: null,
postElement: null),
"Before<custom />"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.StartTagOnly,
preElement: "Before",
preContent: null,
content: null,
postContent: null,
postElement: null),
"Before"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.StartTagOnly,
preElement: "Before",
preContent: null,
content: null,
postContent: null,
postElement: null),
"Before<custom test=\"HtmlEncode[[testVal]]\">"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagOnly,
preElement: "Before",
preContent: null,
content: null,
postContent: null,
postElement: null),
"Before<custom>"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: null,
postContent: null,
postElement: "After"),
"<custom></custom>After"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: null,
preContent: null,
content: null,
postContent: null,
postElement: "After"),
"After"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.SelfClosing,
preElement: null,
preContent: null,
content: null,
postContent: null,
postElement: "After"),
"After"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.SelfClosing,
preElement: null,
preContent: null,
content: null,
postContent: null,
postElement: "After"),
"<custom test=\"HtmlEncode[[testVal]]\" />After"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.SelfClosing,
preElement: null,
preContent: null,
content: null,
postContent: null,
postElement: "After"),
"<custom />After"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.StartTagOnly,
preElement: null,
preContent: null,
content: null,
postContent: null,
postElement: "After"),
"After"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.StartTagOnly,
preElement: null,
preContent: null,
content: null,
postContent: null,
postElement: "After"),
"<custom test=\"HtmlEncode[[testVal]]\">After"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagOnly,
preElement: null,
preContent: null,
content: null,
postContent: null,
postElement: "After"),
"<custom>After"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: "Before",
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: "After"),
"Before<custom>HelloTestWorld!</custom>After"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.StartTagAndEndTag,
preElement: "Before",
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: "After"),
"Before<custom test=\"HtmlEncode[[testVal]]\">HelloTestWorld!</custom>After"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.SelfClosing,
preElement: "Before",
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: "After"),
"Before<custom />After"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList(),
tagMode: TagMode.SelfClosing,
preElement: "Before",
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: "After"),
"BeforeHelloTestWorld!After"
},
{
GetTagHelperOutput(
tagName: "custom",
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagOnly,
preElement: "Before",
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: "After"),
"Before<custom>After"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagOnly,
preElement: "Before",
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: "After"),
"BeforeHelloTestWorld!After"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList(),
tagMode: TagMode.StartTagAndEndTag,
preElement: "Before",
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: "After"),
"BeforeHelloTestWorld!After"
},
{
GetTagHelperOutput(
tagName: string.Empty,
attributes: new TagHelperAttributeList { { "test", "testVal" } },
tagMode: TagMode.StartTagAndEndTag,
preElement: "Before",
preContent: "Hello",
content: "Test",
postContent: "World!",
postElement: "After"),
"BeforeHelloTestWorld!After"
},
};
}
}
[Theory]
[MemberData(nameof(WriteTagHelper_InputData))]
public void WriteTo_WritesFormattedTagHelper(TagHelperOutput output, string expected)
{
// Arrange
var writer = new StringWriter();
var tagHelperExecutionContext = new TagHelperExecutionContext(
tagName: output.TagName,
tagMode: output.TagMode,
items: new Dictionary<object, object>(),
uniqueId: string.Empty,
executeChildContentAsync: () => Task.FromResult(result: true),
startTagHelperWritingScope: _ => { },
endTagHelperWritingScope: () => new DefaultTagHelperContent());
tagHelperExecutionContext.Output = output;
var testEncoder = new HtmlTestEncoder();
// Act
output.WriteTo(writer, testEncoder);
// Assert
Assert.Equal(expected, writer.ToString(), StringComparer.Ordinal);
}
private static TagHelperOutput GetTagHelperOutput(
string tagName,
TagHelperAttributeList attributes,
TagMode tagMode,
string preElement,
string preContent,
string content,
string postContent,
string postElement)
{
var output = new TagHelperOutput(
tagName,
attributes,
getChildContentAsync: (useCachedContent, encoder) => Task.FromResult<TagHelperContent>(
new DefaultTagHelperContent()))
{
TagMode = tagMode
};
if (preElement != null)
{
output.PreElement.AppendHtml(preElement);
}
if (preContent != null)
{
output.PreContent.AppendHtml(preContent);
}
if (content != null)
{
output.Content.AppendHtml(content);
}
if (postContent != null)
{
output.PostContent.AppendHtml(postContent);
}
if (postElement != null)
{
output.PostElement.AppendHtml(postElement);
}
return output;
}
} }
} }

View File

@ -16,5 +16,8 @@
<ItemGroup> <ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" /> <Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<DnxInvisibleFolder Include=".testPublish\" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project> </Project>

View File

@ -51,7 +51,7 @@ namespace TestOutput
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper); __tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper);
__TestNamespace_InputTagHelper2 = CreateTagHelper<global::TestNamespace.InputTagHelper2>(); __TestNamespace_InputTagHelper2 = CreateTagHelper<global::TestNamespace.InputTagHelper2>();
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper2); __tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper2);
StartTagHelperWritingScope(); StartTagHelperWritingScope(null);
WriteLiteral("2000 + "); WriteLiteral("2000 + ");
#line 6 "BasicTagHelpers.cshtml" #line 6 "BasicTagHelpers.cshtml"
Write(ViewBag.DefaultInterval); Write(ViewBag.DefaultInterval);

View File

@ -52,7 +52,7 @@ namespace TestOutput
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper); __tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper);
__TestNamespace_InputTagHelper2 = CreateTagHelper<global::TestNamespace.InputTagHelper2>(); __TestNamespace_InputTagHelper2 = CreateTagHelper<global::TestNamespace.InputTagHelper2>();
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper2); __tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper2);
StartTagHelperWritingScope(); StartTagHelperWritingScope(null);
WriteLiteral("2000 + "); WriteLiteral("2000 + ");
#line 6 "BasicTagHelpers.cshtml" #line 6 "BasicTagHelpers.cshtml"
Write(ViewBag.DefaultInterval); Write(ViewBag.DefaultInterval);

View File

@ -119,7 +119,7 @@ namespace TestOutput
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper); __tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper);
__TestNamespace_InputTagHelper2 = CreateTagHelper<global::TestNamespace.InputTagHelper2>(); __TestNamespace_InputTagHelper2 = CreateTagHelper<global::TestNamespace.InputTagHelper2>();
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper2); __tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper2);
StartTagHelperWritingScope(); StartTagHelperWritingScope(null);
#line 16 "ComplexTagHelpers.cshtml" #line 16 "ComplexTagHelpers.cshtml"
WriteLiteral(checkbox); WriteLiteral(checkbox);
@ -163,7 +163,7 @@ namespace TestOutput
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper); __tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper);
__TestNamespace_InputTagHelper2 = CreateTagHelper<global::TestNamespace.InputTagHelper2>(); __TestNamespace_InputTagHelper2 = CreateTagHelper<global::TestNamespace.InputTagHelper2>();
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper2); __tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper2);
StartTagHelperWritingScope(); StartTagHelperWritingScope(null);
#line 17 "ComplexTagHelpers.cshtml" #line 17 "ComplexTagHelpers.cshtml"
WriteLiteral(true ? "checkbox" : "anything"); WriteLiteral(true ? "checkbox" : "anything");
@ -188,7 +188,7 @@ namespace TestOutput
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper); __tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper);
__TestNamespace_InputTagHelper2 = CreateTagHelper<global::TestNamespace.InputTagHelper2>(); __TestNamespace_InputTagHelper2 = CreateTagHelper<global::TestNamespace.InputTagHelper2>();
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper2); __tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper2);
StartTagHelperWritingScope(); StartTagHelperWritingScope(null);
#line 18 "ComplexTagHelpers.cshtml" #line 18 "ComplexTagHelpers.cshtml"
if(true) { if(true) {

View File

@ -103,7 +103,7 @@ WriteTo(__razor_attribute_value_writer, string.Empty);
, StartTagHelperWritingScope, EndTagHelperWritingScope); , StartTagHelperWritingScope, EndTagHelperWritingScope);
__TestNamespace_InputTagHelper = CreateTagHelper<global::TestNamespace.InputTagHelper>(); __TestNamespace_InputTagHelper = CreateTagHelper<global::TestNamespace.InputTagHelper>();
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper); __tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper);
StartTagHelperWritingScope(); StartTagHelperWritingScope(null);
WriteLiteral("prefix "); WriteLiteral("prefix ");
#line 7 "DynamicAttributeTagHelpers.cshtml" #line 7 "DynamicAttributeTagHelpers.cshtml"
WriteLiteral(DateTime.Now); WriteLiteral(DateTime.Now);
@ -136,7 +136,7 @@ AddHtmlAttributeValue(" ", 210, DateTime.Now, 211, 14, false);
, StartTagHelperWritingScope, EndTagHelperWritingScope); , StartTagHelperWritingScope, EndTagHelperWritingScope);
__TestNamespace_InputTagHelper = CreateTagHelper<global::TestNamespace.InputTagHelper>(); __TestNamespace_InputTagHelper = CreateTagHelper<global::TestNamespace.InputTagHelper>();
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper); __tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper);
StartTagHelperWritingScope(); StartTagHelperWritingScope(null);
#line 9 "DynamicAttributeTagHelpers.cshtml" #line 9 "DynamicAttributeTagHelpers.cshtml"
WriteLiteral(long.MinValue); WriteLiteral(long.MinValue);

View File

@ -44,7 +44,7 @@ namespace TestOutput
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper); __tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper);
__TestNamespace_InputTagHelper2 = CreateTagHelper<global::TestNamespace.InputTagHelper2>(); __TestNamespace_InputTagHelper2 = CreateTagHelper<global::TestNamespace.InputTagHelper2>();
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper2); __tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper2);
StartTagHelperWritingScope(); StartTagHelperWritingScope(null);
#line 6 "EscapedTagHelpers.cshtml" #line 6 "EscapedTagHelpers.cshtml"
WriteLiteral(DateTime.Now); WriteLiteral(DateTime.Now);

View File

@ -54,7 +54,7 @@ namespace TestOutput
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper); __tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper);
__TestNamespace_InputTagHelper2 = CreateTagHelper<global::TestNamespace.InputTagHelper2>(); __TestNamespace_InputTagHelper2 = CreateTagHelper<global::TestNamespace.InputTagHelper2>();
__tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper2); __tagHelperExecutionContext.Add(__TestNamespace_InputTagHelper2);
StartTagHelperWritingScope(); StartTagHelperWritingScope(null);
WriteLiteral("2000 + "); WriteLiteral("2000 + ");
#line 8 "NestedScriptTagTagHelpers.cshtml" #line 8 "NestedScriptTagTagHelpers.cshtml"
Write(ViewBag.DefaultInterval); Write(ViewBag.DefaultInterval);

View File

@ -172,7 +172,7 @@ __TestNamespace_InputTagHelper2.IntDictionaryProperty["salt"] = 37;
__TestNamespace_InputTagHelper2.StringDictionaryProperty["paprika"] = "another string"; __TestNamespace_InputTagHelper2.StringDictionaryProperty["paprika"] = "another string";
__tagHelperExecutionContext.AddTagHelperAttribute("string-prefix-paprika", __TestNamespace_InputTagHelper2.StringDictionaryProperty["paprika"]); __tagHelperExecutionContext.AddTagHelperAttribute("string-prefix-paprika", __TestNamespace_InputTagHelper2.StringDictionaryProperty["paprika"]);
__TestNamespace_InputTagHelper1.StringDictionaryProperty["paprika"] = __TestNamespace_InputTagHelper2.StringDictionaryProperty["paprika"]; __TestNamespace_InputTagHelper1.StringDictionaryProperty["paprika"] = __TestNamespace_InputTagHelper2.StringDictionaryProperty["paprika"];
StartTagHelperWritingScope(); StartTagHelperWritingScope(null);
WriteLiteral("literate "); WriteLiteral("literate ");
#line 21 "PrefixedAttributeTagHelpers.cshtml" #line 21 "PrefixedAttributeTagHelpers.cshtml"
WriteLiteral(literate); WriteLiteral(literate);

View File

@ -172,7 +172,7 @@ __TestNamespace_InputTagHelper1.IntDictionaryProperty["salt"] = 37;
__TestNamespace_InputTagHelper1.StringDictionaryProperty["paprika"] = "another string"; __TestNamespace_InputTagHelper1.StringDictionaryProperty["paprika"] = "another string";
__tagHelperExecutionContext.AddTagHelperAttribute("string-prefix-paprika", __TestNamespace_InputTagHelper1.StringDictionaryProperty["paprika"]); __tagHelperExecutionContext.AddTagHelperAttribute("string-prefix-paprika", __TestNamespace_InputTagHelper1.StringDictionaryProperty["paprika"]);
__TestNamespace_InputTagHelper2.StringDictionaryProperty["paprika"] = __TestNamespace_InputTagHelper1.StringDictionaryProperty["paprika"]; __TestNamespace_InputTagHelper2.StringDictionaryProperty["paprika"] = __TestNamespace_InputTagHelper1.StringDictionaryProperty["paprika"];
StartTagHelperWritingScope(); StartTagHelperWritingScope(null);
WriteLiteral("literate "); WriteLiteral("literate ");
#line 21 "PrefixedAttributeTagHelpers.cshtml" #line 21 "PrefixedAttributeTagHelpers.cshtml"
WriteLiteral(literate); WriteLiteral(literate);

View File

@ -76,7 +76,7 @@ namespace TestOutput
, StartTagHelperWritingScope, EndTagHelperWritingScope); , StartTagHelperWritingScope, EndTagHelperWritingScope);
__TestNamespace_MyTagHelper = CreateTagHelper<global::TestNamespace.MyTagHelper>(); __TestNamespace_MyTagHelper = CreateTagHelper<global::TestNamespace.MyTagHelper>();
__tagHelperExecutionContext.Add(__TestNamespace_MyTagHelper); __tagHelperExecutionContext.Add(__TestNamespace_MyTagHelper);
StartTagHelperWritingScope(); StartTagHelperWritingScope(null);
WriteLiteral("Current Time: "); WriteLiteral("Current Time: ");
#line 9 "TagHelpersInSection.cshtml" #line 9 "TagHelpersInSection.cshtml"
WriteLiteral(DateTime.Now); WriteLiteral(DateTime.Now);

View File

@ -43,7 +43,7 @@ __TestNamespace_PTagHelper.Age = 1337;
#line default #line default
#line hidden #line hidden
__tagHelperExecutionContext.AddTagHelperAttribute("age", __TestNamespace_PTagHelper.Age); __tagHelperExecutionContext.AddTagHelperAttribute("age", __TestNamespace_PTagHelper.Age);
StartTagHelperWritingScope(); StartTagHelperWritingScope(null);
#line 7 "TagHelpersWithWeirdlySpacedAttributes.cshtml" #line 7 "TagHelpersWithWeirdlySpacedAttributes.cshtml"
Write(true); Write(true);