diff --git a/src/Microsoft.AspNet.Razor.Runtime/Runtime/TagHelpers/TagHelperRunner.cs b/src/Microsoft.AspNet.Razor.Runtime/Runtime/TagHelpers/TagHelperRunner.cs
index 5ec7453277..c6ebeddef4 100644
--- a/src/Microsoft.AspNet.Razor.Runtime/Runtime/TagHelpers/TagHelperRunner.cs
+++ b/src/Microsoft.AspNet.Razor.Runtime/Runtime/TagHelpers/TagHelperRunner.cs
@@ -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)
{
diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/ITagHelper.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/ITagHelper.cs
index ec0a64f41e..5cb6151f22 100644
--- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/ITagHelper.cs
+++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/ITagHelper.cs
@@ -11,11 +11,24 @@ namespace Microsoft.AspNet.Razor.TagHelpers
public interface ITagHelper
{
///
- /// Gets the execution order of this relative to others targeting the same element.
- /// instances with lower values are executed first.
+ /// When a set of s are executed, their's
+ /// are first invoked in the specified ; then their
+ /// 's are invoked in the specified
+ /// . Lower values are executed first.
///
int Order { get; }
+ ///
+ /// Initializes the with the given . Additions to
+ /// should be done within this method to ensure they're added prior to
+ /// executing the children.
+ ///
+ /// Contains information associated with the current HTML tag.
+ /// When more than one runs on the same element,
+ /// may be invoked prior to .
+ ///
+ void Init(TagHelperContext context);
+
///
/// Asynchronously executes the with the given and
/// .
diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelper.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelper.cs
index ef051505b5..267a243deb 100644
--- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelper.cs
+++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelper.cs
@@ -14,6 +14,11 @@ namespace Microsoft.AspNet.Razor.TagHelpers
/// Default order is 0.
public virtual int Order { get; } = 0;
+ ///
+ public virtual void Init(TagHelperContext context)
+ {
+ }
+
///
/// Synchronously executes the with the given and
/// .
diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperContext.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperContext.cs
index ed6ef0491b..272ec5a48c 100644
--- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperContext.cs
+++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperContext.cs
@@ -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
///
public class TagHelperContext
{
- private readonly Func> _getChildContentAsync;
-
///
/// Instantiates a new .
///
@@ -22,13 +19,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
/// Collection of items used to communicate with other s.
/// The unique identifier for the source element this
/// applies to.
- /// A delegate used to execute and retrieve the rendered child content
- /// asynchronously.
public TagHelperContext(
IEnumerable allAttributes,
IDictionary
public class TagHelperOutput
{
+ private readonly Func> _getChildContentAsync;
+
// Internal for testing
internal TagHelperOutput(string tagName)
- : this(tagName, new TagHelperAttributeList())
+ : this(
+ tagName,
+ new TagHelperAttributeList(),
+ (cachedResult) => Task.FromResult(new DefaultTagHelperContent()))
{
}
@@ -22,17 +28,26 @@ namespace Microsoft.AspNet.Razor.TagHelpers
///
/// The HTML element's tag name.
/// The HTML attributes.
+ /// A delegate used to execute and retrieve the rendered child content
+ /// asynchronously.
public TagHelperOutput(
string tagName,
- TagHelperAttributeList attributes)
+ TagHelperAttributeList attributes,
+ Func> 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;
}
///
@@ -116,5 +131,26 @@ namespace Microsoft.AspNet.Razor.TagHelpers
PostContent.Clear();
PostElement.Clear();
}
+
+ ///
+ /// A delegate used to execute children asynchronously.
+ ///
+ /// A that on completion returns content rendered by children.
+ /// This method is memoized.
+ public Task GetChildContentAsync()
+ {
+ return GetChildContentAsync(useCachedResult: true);
+ }
+
+ ///
+ /// A delegate used to execute children asynchronously.
+ ///
+ /// If true multiple calls to this method will not cause re-execution
+ /// of child content; cached content will be returned.
+ /// A that on completion returns content rendered by children.
+ public Task GetChildContentAsync(bool useCachedResult)
+ {
+ return _getChildContentAsync(useCachedResult);
+ }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/RuntimeTypeInfoTest.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/RuntimeTypeInfoTest.cs
index c3280fb45b..0c9210d344 100644
--- a/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/RuntimeTypeInfoTest.cs
+++ b/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/RuntimeTypeInfoTest.cs
@@ -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();
diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/TagHelperRunnerTest.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/TagHelperRunnerTest.cs
index 2500e4c265..3411185438 100644
--- a/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/TagHelperRunnerTest.cs
+++ b/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/TagHelperRunnerTest.cs
@@ -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);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/TagHelperTypeResolverTest.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/TagHelperTypeResolverTest.cs
index bcdd56662f..10800f6d66 100644
--- a/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/TagHelperTypeResolverTest.cs
+++ b/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/TagHelperTypeResolverTest.cs
@@ -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);
diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/TestTagHelpers/ImplementsRealTagHelper.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/TestTagHelpers/ImplementsRealTagHelper.cs
index 15a2f9f345..e1a9536cfc 100644
--- a/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/TestTagHelpers/ImplementsRealTagHelper.cs
+++ b/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/TestTagHelpers/ImplementsRealTagHelper.cs
@@ -17,6 +17,10 @@ namespace Microsoft.AspNet.Razor.Fake
}
}
+ public void Init(TagHelperContext context)
+ {
+ }
+
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
throw new NotImplementedException();
diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/TestTagHelpers/TagHelperDescriptorFactoryTagHelpers.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/TestTagHelpers/TagHelperDescriptorFactoryTagHelpers.cs
index ff2901de7b..78b0c6c73a 100644
--- a/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/TestTagHelpers/TagHelperDescriptorFactoryTagHelpers.cs
+++ b/test/Microsoft.AspNet.Razor.Runtime.Test/Runtime/TagHelpers/TestTagHelpers/TagHelperDescriptorFactoryTagHelpers.cs
@@ -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();
diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperContextTest.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperContextTest.cs
index 8ba9269c77..537fc343da 100644
--- a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperContextTest.cs
+++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperContextTest.cs
@@ -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(),
- items: new Dictionary