Add `Init` method to `TagHelper`s.
- The init method allows multiple `TagHelper`s to inject data into the `context.Items` bag to properly function when running in unison with other `TagHelper`s that need to communicate with children. - Transition `GetChildContentAsync` from `TagHelperContext` to `TagHelperOutput`. - Move `TagHelperContext.GetChildContentAsync` tests to `TagHelperOutputTest`. - Added `Init` test to ensure `TagHelperRunner` calls it in the correct order. #571
This commit is contained in:
parent
b1ad14fd46
commit
6d0b268440
|
|
@ -30,15 +30,21 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
var tagHelperContext = new TagHelperContext(
|
||||
executionContext.AllAttributes,
|
||||
executionContext.Items,
|
||||
executionContext.UniqueId,
|
||||
executionContext.GetChildContentAsync);
|
||||
executionContext.UniqueId);
|
||||
var orderedTagHelpers = executionContext.TagHelpers.OrderBy(tagHelper => tagHelper.Order);
|
||||
|
||||
foreach (var tagHelper in orderedTagHelpers)
|
||||
{
|
||||
tagHelper.Init(tagHelperContext);
|
||||
}
|
||||
|
||||
var tagHelperOutput = new TagHelperOutput(
|
||||
executionContext.TagName,
|
||||
executionContext.HTMLAttributes)
|
||||
executionContext.HTMLAttributes,
|
||||
executionContext.GetChildContentAsync)
|
||||
{
|
||||
TagMode = executionContext.TagMode,
|
||||
};
|
||||
var orderedTagHelpers = executionContext.TagHelpers.OrderBy(tagHelper => tagHelper.Order);
|
||||
|
||||
foreach (var tagHelper in orderedTagHelpers)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,11 +11,24 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
public interface ITagHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the execution order of this <see cref= "ITagHelper" /> relative to others targeting the same element.
|
||||
/// <see cref="ITagHelper"/> instances with lower values are executed first.
|
||||
/// When a set of<see cref= "ITagHelper" /> s are executed, their<see cref="Init(TagHelperContext)"/>'s
|
||||
/// are first invoked in the specified <see cref="Order"/>; then their
|
||||
/// <see cref="ProcessAsync(TagHelperContext, TagHelperOutput)"/>'s are invoked in the specified
|
||||
/// <see cref="Order"/>. Lower values are executed first.
|
||||
/// </summary>
|
||||
int Order { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="ITagHelper"/> with the given <paramref name="context"/>. Additions to
|
||||
/// <see cref="TagHelperContext.Items"/> should be done within this method to ensure they're added prior to
|
||||
/// executing the children.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains information associated with the current HTML tag.</param>
|
||||
/// <remarks>When more than one <see cref="ITagHelper"/> runs on the same element,
|
||||
/// <see cref="TagHelperOutput.GetChildContentAsync"/> may be invoked prior to <see cref="ProcessAsync"/>.
|
||||
/// </remarks>
|
||||
void Init(TagHelperContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously executes the <see cref="ITagHelper"/> with the given <paramref name="context"/> and
|
||||
/// <paramref name="output"/>.
|
||||
|
|
|
|||
|
|
@ -14,6 +14,11 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
/// <remarks>Default order is <c>0</c>.</remarks>
|
||||
public virtual int Order { get; } = 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Init(TagHelperContext context)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronously executes the <see cref="TagHelper"/> with the given <paramref name="context"/> and
|
||||
/// <paramref name="output"/>.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||
{
|
||||
|
|
@ -13,8 +12,6 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
/// </summary>
|
||||
public class TagHelperContext
|
||||
{
|
||||
private readonly Func<bool, Task<TagHelperContent>> _getChildContentAsync;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="TagHelperContext"/>.
|
||||
/// </summary>
|
||||
|
|
@ -22,13 +19,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
/// <param name="items">Collection of items used to communicate with other <see cref="ITagHelper"/>s.</param>
|
||||
/// <param name="uniqueId">The unique identifier for the source element this <see cref="TagHelperContext" />
|
||||
/// applies to.</param>
|
||||
/// <param name="getChildContentAsync">A delegate used to execute and retrieve the rendered child content
|
||||
/// asynchronously.</param>
|
||||
public TagHelperContext(
|
||||
IEnumerable<IReadOnlyTagHelperAttribute> allAttributes,
|
||||
IDictionary<object, object> items,
|
||||
string uniqueId,
|
||||
Func<bool, Task<TagHelperContent>> getChildContentAsync)
|
||||
string uniqueId)
|
||||
{
|
||||
if (allAttributes == null)
|
||||
{
|
||||
|
|
@ -45,16 +39,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
throw new ArgumentNullException(nameof(uniqueId));
|
||||
}
|
||||
|
||||
if (getChildContentAsync == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(getChildContentAsync));
|
||||
}
|
||||
|
||||
AllAttributes = new ReadOnlyTagHelperAttributeList<IReadOnlyTagHelperAttribute>(
|
||||
allAttributes.Select(attribute => new TagHelperAttribute(attribute.Name, attribute.Value)));
|
||||
Items = items;
|
||||
UniqueId = uniqueId;
|
||||
_getChildContentAsync = getChildContentAsync;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -75,26 +63,5 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
/// An identifier unique to the HTML element this context is for.
|
||||
/// </summary>
|
||||
public string UniqueId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <remarks>This method is memoized.</remarks>
|
||||
public Task<TagHelperContent> GetChildContentAsync()
|
||||
{
|
||||
return GetChildContentAsync(useCachedResult: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate used to execute and retrieve the rendered child content asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="useCachedResult">If <c>true</c> multiple calls to this method will not cause re-execution
|
||||
/// of child content; cached content will be returned.</param>
|
||||
/// <returns>A <see cref="Task"/> that when executed returns content rendered by children.</returns>
|
||||
public Task<TagHelperContent> GetChildContentAsync(bool useCachedResult)
|
||||
{
|
||||
return _getChildContentAsync(useCachedResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||
|
|
@ -11,9 +12,14 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
/// </summary>
|
||||
public class TagHelperOutput
|
||||
{
|
||||
private readonly Func<bool, Task<TagHelperContent>> _getChildContentAsync;
|
||||
|
||||
// Internal for testing
|
||||
internal TagHelperOutput(string tagName)
|
||||
: this(tagName, new TagHelperAttributeList())
|
||||
: this(
|
||||
tagName,
|
||||
new TagHelperAttributeList(),
|
||||
(cachedResult) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()))
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -22,17 +28,26 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
/// </summary>
|
||||
/// <param name="tagName">The HTML element's tag name.</param>
|
||||
/// <param name="attributes">The HTML attributes.</param>
|
||||
/// <param name="getChildContentAsync">A delegate used to execute and retrieve the rendered child content
|
||||
/// asynchronously.</param>
|
||||
public TagHelperOutput(
|
||||
string tagName,
|
||||
TagHelperAttributeList attributes)
|
||||
TagHelperAttributeList attributes,
|
||||
Func<bool, Task<TagHelperContent>> getChildContentAsync)
|
||||
{
|
||||
if (attributes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attributes));
|
||||
}
|
||||
|
||||
if (getChildContentAsync == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(getChildContentAsync));
|
||||
}
|
||||
|
||||
TagName = tagName;
|
||||
Attributes = new TagHelperAttributeList(attributes);
|
||||
_getChildContentAsync = getChildContentAsync;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -116,5 +131,26 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
PostContent.Clear();
|
||||
PostElement.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate used to execute children asynchronously.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> that on completion returns content rendered by children.</returns>
|
||||
/// <remarks>This method is memoized.</remarks>
|
||||
public Task<TagHelperContent> GetChildContentAsync()
|
||||
{
|
||||
return GetChildContentAsync(useCachedResult: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate used to execute children asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="useCachedResult">If <c>true</c> multiple calls to this method will not cause re-execution
|
||||
/// of child content; cached content will be returned.</param>
|
||||
/// <returns>A <see cref="Task"/> that on completion returns content rendered by children.</returns>
|
||||
public Task<TagHelperContent> GetChildContentAsync(bool useCachedResult)
|
||||
{
|
||||
return _getChildContentAsync(useCachedResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -394,6 +394,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
{
|
||||
public int Order { get; } = 0;
|
||||
|
||||
public void Init(TagHelperContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
|
|
|||
|
|
@ -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.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -11,6 +12,35 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
{
|
||||
public class TagHelperRunnerTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task RunAsync_CallsInitPriorToProcessAsync()
|
||||
{
|
||||
// Arrange
|
||||
var runner = new TagHelperRunner();
|
||||
var executionContext = new TagHelperExecutionContext("p", TagMode.StartTagAndEndTag);
|
||||
var incrementer = 0;
|
||||
var callbackTagHelper = new CallbackTagHelper(
|
||||
initCallback: () =>
|
||||
{
|
||||
Assert.Equal(0, incrementer);
|
||||
|
||||
incrementer++;
|
||||
},
|
||||
processAsyncCallback: () =>
|
||||
{
|
||||
Assert.Equal(1, incrementer);
|
||||
|
||||
incrementer++;
|
||||
});
|
||||
executionContext.Add(callbackTagHelper);
|
||||
|
||||
// Act
|
||||
await runner.RunAsync(executionContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, incrementer);
|
||||
}
|
||||
|
||||
public static TheoryData TagHelperOrderData
|
||||
{
|
||||
get
|
||||
|
|
@ -234,5 +264,29 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
ProcessOrderTracker.Add(Order);
|
||||
}
|
||||
}
|
||||
|
||||
private class CallbackTagHelper : TagHelper
|
||||
{
|
||||
private readonly Action _initCallback;
|
||||
private readonly Action _processAsyncCallback;
|
||||
|
||||
public CallbackTagHelper(Action initCallback, Action processAsyncCallback)
|
||||
{
|
||||
_initCallback = initCallback;
|
||||
_processAsyncCallback = processAsyncCallback;
|
||||
}
|
||||
|
||||
public override void Init(TagHelperContext context)
|
||||
{
|
||||
_initCallback();
|
||||
}
|
||||
|
||||
public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
_processAsyncCallback();
|
||||
|
||||
return base.ProcessAsync(context, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -134,6 +134,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
{
|
||||
public int Order { get { return 0; } }
|
||||
|
||||
public void Init(TagHelperContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
return Task.FromResult(result: true);
|
||||
|
|
@ -144,6 +148,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
{
|
||||
public int Order { get { return 0; } }
|
||||
|
||||
public void Init(TagHelperContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
return Task.FromResult(result: true);
|
||||
|
|
@ -154,6 +162,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
{
|
||||
public int Order { get { return 0; } }
|
||||
|
||||
public void Init(TagHelperContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
return Task.FromResult(result: true);
|
||||
|
|
@ -164,6 +176,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
{
|
||||
public int Order { get { return 0; } }
|
||||
|
||||
public void Init(TagHelperContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
return Task.FromResult(result: true);
|
||||
|
|
@ -177,6 +193,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
{
|
||||
public int Order { get { return 0; } }
|
||||
|
||||
public void Init(TagHelperContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
return Task.FromResult(result: true);
|
||||
|
|
@ -187,6 +207,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
{
|
||||
public int Order { get { return 0; } }
|
||||
|
||||
public void Init(TagHelperContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
return Task.FromResult(result: true);
|
||||
|
|
@ -197,6 +221,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
{
|
||||
public int Order { get { return 0; } }
|
||||
|
||||
public void Init(TagHelperContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
return Task.FromResult(result: true);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ namespace Microsoft.AspNet.Razor.Fake
|
|||
}
|
||||
}
|
||||
|
||||
public void Init(TagHelperContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
|
|
|||
|
|
@ -415,6 +415,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
{
|
||||
public int Order { get; } = 0;
|
||||
|
||||
public void Init(TagHelperContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
|
|
|||
|
|
@ -12,30 +12,6 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
{
|
||||
public class TagHelperContextTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task GetChildContentAsync_PassesUseCachedResultAsExpected(bool expectedUseCachedResultValue)
|
||||
{
|
||||
// Arrange
|
||||
bool? useCachedResultValue = null;
|
||||
var context = new TagHelperContext(
|
||||
allAttributes: Enumerable.Empty<IReadOnlyTagHelperAttribute>(),
|
||||
items: new Dictionary<object, object>(),
|
||||
uniqueId: string.Empty,
|
||||
getChildContentAsync: useCachedResult =>
|
||||
{
|
||||
useCachedResultValue = useCachedResult;
|
||||
return Task.FromResult<TagHelperContent>(new DefaultTagHelperContent());
|
||||
});
|
||||
|
||||
// Act
|
||||
await context.GetChildContentAsync(expectedUseCachedResultValue);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedUseCachedResultValue, useCachedResultValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_SetsProperties_AsExpected()
|
||||
{
|
||||
|
|
@ -49,9 +25,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
var context = new TagHelperContext(
|
||||
allAttributes: Enumerable.Empty<IReadOnlyTagHelperAttribute>(),
|
||||
items: expectedItems,
|
||||
uniqueId: string.Empty,
|
||||
getChildContentAsync: useCachedResult =>
|
||||
Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()));
|
||||
uniqueId: string.Empty);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(context.Items);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
using Microsoft.Extensions.WebEncoders.Testing;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -8,6 +10,29 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
{
|
||||
public class TagHelperOutputTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task GetChildContentAsync_PassesUseCachedResultAsExpected(bool expectedUseCachedResultValue)
|
||||
{
|
||||
// Arrange
|
||||
bool? useCachedResultValue = null;
|
||||
var output = new TagHelperOutput(
|
||||
tagName: "p",
|
||||
attributes: new TagHelperAttributeList(),
|
||||
getChildContentAsync: useCachedResult =>
|
||||
{
|
||||
useCachedResultValue = useCachedResult;
|
||||
return Task.FromResult<TagHelperContent>(new DefaultTagHelperContent());
|
||||
});
|
||||
|
||||
// Act
|
||||
await output.GetChildContentAsync(expectedUseCachedResultValue);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedUseCachedResultValue, useCachedResultValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreElement_SetContent_ChangesValue()
|
||||
{
|
||||
|
|
@ -156,7 +181,8 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
{
|
||||
{ "class", "btn" },
|
||||
{ "something", " spaced " }
|
||||
});
|
||||
},
|
||||
(cachedResult) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()));
|
||||
tagHelperOutput.PreContent.Append("Pre Content");
|
||||
tagHelperOutput.Content.Append("Content");
|
||||
tagHelperOutput.PostContent.Append("Post Content");
|
||||
|
|
@ -188,7 +214,8 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
new TagHelperAttributeList
|
||||
{
|
||||
{ originalName, "btn" },
|
||||
});
|
||||
},
|
||||
(cachedResult) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()));
|
||||
|
||||
// Act
|
||||
tagHelperOutput.Attributes[updateName] = "super button";
|
||||
|
|
|
|||
Loading…
Reference in New Issue