diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperContext.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperContext.cs index 8fabb89a4b..2f751e3ddb 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperContext.cs +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperContext.cs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace Microsoft.AspNet.Razor.Runtime.TagHelpers { @@ -10,25 +12,42 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers /// public class TagHelperContext { + private readonly Func> _getChildContentAsync; + /// /// Instantiates a new . /// /// Every attribute associated with the current HTML element. - /// The unique identifier for the source element this applies to. - public TagHelperContext([NotNull] IDictionary allAttributes, [NotNull] string uniqueId) + /// The unique identifier for the source element this + /// applies to. + /// A delegate used to execute and retrieve the rendered child content + /// asynchronously. + public TagHelperContext([NotNull] IDictionary allAttributes, + [NotNull] string uniqueId, + [NotNull] Func> getChildContentAsync) { AllAttributes = allAttributes; UniqueId = uniqueId; + _getChildContentAsync = getChildContentAsync; } /// /// Every attribute associated with the current HTML element. /// - public IDictionary AllAttributes { get; private set; } + public IDictionary AllAttributes { get; } /// /// An identifier unique to the HTML element this context is for. /// - public string UniqueId { get; private set; } + public string UniqueId { get; } + + /// + /// A delegate used to execute and retrieve the rendered child content asynchronously. + /// + /// A that when executed returns content rendered by children. + public Task GetChildContentAsync() + { + return _getChildContentAsync(); + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperExecutionContext.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperExecutionContext.cs index 36ff1a1fde..930359cc04 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperExecutionContext.cs +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperExecutionContext.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; namespace Microsoft.AspNet.Razor.Runtime.TagHelpers { @@ -12,43 +14,61 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers public class TagHelperExecutionContext { private readonly List _tagHelpers; + private readonly Func _executeChildContentAsync; + private readonly Action _startWritingScope; + private readonly Func _endWritingScope; + private string _childContent; /// /// Instantiates a new . /// /// The HTML tag name in the Razor source. - public TagHelperExecutionContext([NotNull] string tagName, [NotNull] string uniqueId) + /// An identifier unique to the HTML element this context is for. + /// A delegate used to execute the child content asynchronously. + /// A delegate used to start a writing scope in a Razor page. + /// A delegate used to end a writing scope in a Razor page. + public TagHelperExecutionContext([NotNull] string tagName, + [NotNull] string uniqueId, + [NotNull] Func executeChildContentAsync, + [NotNull] Action startWritingScope, + [NotNull] Func endWritingScope) { + _tagHelpers = new List(); + _executeChildContentAsync = executeChildContentAsync; + _startWritingScope = startWritingScope; + _endWritingScope = endWritingScope; + AllAttributes = new Dictionary(StringComparer.OrdinalIgnoreCase); HTMLAttributes = new Dictionary(StringComparer.OrdinalIgnoreCase); - _tagHelpers = new List(); TagName = tagName; UniqueId = uniqueId; } /// - /// Internal for testing purposes only. + /// Indicates if has been called. /// - internal TagHelperExecutionContext([NotNull] string tagName) - : this(tagName, string.Empty) + public bool ChildContentRetrieved { - + get + { + return _childContent != null; + } } /// /// HTML attributes. /// - public IDictionary HTMLAttributes { get; private set; } + public IDictionary HTMLAttributes { get; } /// /// bound attributes and HTML attributes. /// - public IDictionary AllAttributes { get; private set; } + public IDictionary AllAttributes { get; } /// /// An identifier unique to the HTML element this context is for. /// - public string UniqueId { get; private set; } + public string UniqueId { get; } /// /// s that should be run. @@ -64,7 +84,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers /// /// The HTML tag name in the Razor source. /// - public string TagName { get; private set; } + public string TagName { get; } /// /// The s' output. @@ -100,5 +120,34 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers { AllAttributes.Add(name, value); } + + /// + /// Executes the child content asynchronously. + /// + /// A which on completion executes all child content. + public Task ExecuteChildContentAsync() + { + return _executeChildContentAsync(); + } + + /// + /// Execute and retrieve the rendered child content asynchronously. + /// + /// A that on completion returns the rendered child content. + /// + /// Child content is only executed once. Successive calls to this method or successive executions of the + /// returned return a cached result. + /// + public async Task GetChildContentAsync() + { + if (_childContent == null) + { + _startWritingScope(); + await _executeChildContentAsync(); + _childContent = _endWritingScope().ToString(); + } + + return _childContent; + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperOutput.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperOutput.cs index 4cab64f12d..9c82848f8b 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperOutput.cs +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperOutput.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers public class TagHelperOutput { private string _content; - private string _tagName; + private bool _contentSet; // Internal for testing internal TagHelperOutput(string tagName) @@ -35,37 +35,34 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers /// /// The HTML element's tag name. /// The HTML attributes. - /// The HTML element's content. - public TagHelperOutput(string tagName, - [NotNull] IDictionary attributes, - string content) + public TagHelperOutput(string tagName, [NotNull] IDictionary attributes) { TagName = tagName; - Content = content; Attributes = new Dictionary(attributes, StringComparer.OrdinalIgnoreCase); + PreContent = string.Empty; + _content = string.Empty; + PostContent = string.Empty; } /// /// The HTML element's tag name. /// /// - /// A whitespace value results in no start or end tag being rendered. + /// A whitespace or null value results in no start or end tag being rendered. /// - public string TagName - { - get - { - return _tagName; - } - set - { - _tagName = value ?? string.Empty; - } - } + public string TagName { get; set; } /// - /// The HTML element's content. + /// The HTML element's pre content. /// + /// Value is prepended to the 's final output. + public string PreContent { get; set; } + + /// + /// The HTML element's main content. + /// + /// Value occurs in the 's final output after and + /// before public string Content { get @@ -74,7 +71,25 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers } set { - _content = value ?? string.Empty; + _contentSet = true; + _content = value; + } + } + + /// + /// The HTML element's post content. + /// + /// Value is appended to the 's final output. + public string PostContent { get; set; } + + /// + /// true if has been set, false otherwise. + /// + public bool ContentSet + { + get + { + return _contentSet; } } @@ -86,7 +101,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers /// /// The HTML element's attributes. /// - public IDictionary Attributes { get; private set; } + public IDictionary Attributes { get; } /// /// Generates the 's start tag. @@ -126,6 +141,22 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers return sb.ToString(); } + /// + /// Generates the 's . + /// + /// string.Empty if is true. + /// otherwise. + /// + public string GeneratePreContent() + { + if (SelfClosing) + { + return string.Empty; + } + + return PreContent; + } + /// /// Generates the 's body. /// @@ -141,6 +172,22 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers return Content; } + /// + /// Generates the 's . + /// + /// string.Empty if is true. + /// otherwise. + /// + public string GeneratePostContent() + { + if (SelfClosing) + { + return string.Empty; + } + + return PostContent; + } + /// /// Generates the 's end tag. /// @@ -155,5 +202,20 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers return string.Format(CultureInfo.InvariantCulture, "", TagName); } + + /// + /// Changes to generate nothing. + /// + /// + /// Sets , , , and + /// to null to suppress output. + /// + public void SuppressOutput() + { + TagName = null; + PreContent = null; + Content = null; + PostContent = null; + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperRunner.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperRunner.cs index cf5531a83a..8da31ff45f 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperRunner.cs +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperRunner.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.IO; using System.Threading.Tasks; namespace Microsoft.AspNet.Razor.Runtime.TagHelpers @@ -14,33 +13,17 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers /// /// Calls the method on s. /// - /// Contains information associated with running s. + /// Contains information associated with running s. + /// /// Resulting from processing all of the - /// 's s. - public async Task RunAsync([NotNull] TagHelperExecutionContext context) + /// 's s. + public async Task RunAsync([NotNull] TagHelperExecutionContext executionContext) { - return await RunAsyncCore(context, string.Empty); - } - - /// - /// Calls the method on s. - /// - /// Contains information associated with running s. - /// Contains the buffered content of the current HTML tag. - /// Resulting from processing all of the - /// 's s. - public async Task RunAsync([NotNull] TagHelperExecutionContext context, - [NotNull] TextWriter bufferedBody) - { - return await RunAsyncCore(context, bufferedBody.ToString()); - } - - private async Task RunAsyncCore(TagHelperExecutionContext executionContext, string outputContent) - { - var tagHelperContext = new TagHelperContext(executionContext.AllAttributes, executionContext.UniqueId); - var tagHelperOutput = new TagHelperOutput(executionContext.TagName, - executionContext.HTMLAttributes, - outputContent); + var tagHelperContext = new TagHelperContext( + executionContext.AllAttributes, + executionContext.UniqueId, + executionContext.GetChildContentAsync); + var tagHelperOutput = new TagHelperOutput(executionContext.TagName, executionContext.HTMLAttributes); foreach (var tagHelper in executionContext.TagHelpers) { diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperScopeManager.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperScopeManager.cs index 5501ec6c97..b89eb89eda 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperScopeManager.cs +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperScopeManager.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; namespace Microsoft.AspNet.Razor.Runtime.TagHelpers { @@ -26,10 +28,21 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers /// /// The HTML tag name that the scope is associated with. /// An identifier unique to the HTML element this scope is for. + /// A delegate used to execute the child content asynchronously. + /// A delegate used to start a writing scope in a Razor page. + /// A delegate used to end a writing scope in a Razor page. /// A to use. - public TagHelperExecutionContext Begin(string tagName, string uniqueId) + public TagHelperExecutionContext Begin([NotNull] string tagName, + [NotNull] string uniqueId, + [NotNull] Func executeChildContentAsync, + [NotNull] Action startWritingScope, + [NotNull] Func endWritingScope) { - var executionContext = new TagHelperExecutionContext(tagName, uniqueId); + var executionContext = new TagHelperExecutionContext(tagName, + uniqueId, + executeChildContentAsync, + startWritingScope, + endWritingScope); _executionScopes.Push(executionContext); diff --git a/src/Microsoft.AspNet.Razor/Generator/GeneratedTagHelperContext.cs b/src/Microsoft.AspNet.Razor/Generator/GeneratedTagHelperContext.cs index be427b916b..e2929205dc 100644 --- a/src/Microsoft.AspNet.Razor/Generator/GeneratedTagHelperContext.cs +++ b/src/Microsoft.AspNet.Razor/Generator/GeneratedTagHelperContext.cs @@ -17,9 +17,13 @@ namespace Microsoft.AspNet.Razor.Generator RunnerRunAsyncMethodName = "RunAsync"; ScopeManagerBeginMethodName = "Begin"; ScopeManagerEndMethodName = "End"; + OutputContentSetPropertyName = "ContentSet"; OutputGenerateStartTagMethodName = "GenerateStartTag"; + OutputGeneratePreContentMethodName = "GeneratePreContent"; OutputGenerateContentMethodName = "GenerateContent"; + OutputGeneratePostContentMethodName = "GeneratePostContent"; OutputGenerateEndTagMethodName = "GenerateEndTag"; + ExecutionContextExecuteChildContentAsyncMethodName = "ExecuteChildContentAsync"; ExecutionContextAddMethodName = "Add"; ExecutionContextAddTagHelperAttributeMethodName = "AddTagHelperAttribute"; ExecutionContextAddHtmlAttributeMethodName = "AddHtmlAttribute"; @@ -51,21 +55,42 @@ namespace Microsoft.AspNet.Razor.Generator /// public string ScopeManagerEndMethodName { get; set; } + /// + /// The name of the property used to determine if a tag helper output's content was set. + /// + public string OutputContentSetPropertyName { get; set; } + /// /// The name of the method used to generate a tag helper output's start tag. /// public string OutputGenerateStartTagMethodName { get; set; } + /// + /// The name of the method used to generate a tag helper output's pre content. + /// + public string OutputGeneratePreContentMethodName { get; set; } + /// /// The name of the method used to generate a tag helper output's content. /// public string OutputGenerateContentMethodName { get; set; } + /// + /// The name of the method used to generate a tag helper output's post-content. + /// + public string OutputGeneratePostContentMethodName { get; set; } + /// /// The name of the method used to generate a tag helper output's end tag. /// public string OutputGenerateEndTagMethodName { get; set; } + /// + /// The name of the method used to get a + /// that executes tag helper child content. + /// + public string ExecutionContextExecuteChildContentAsyncMethodName { get; set; } + /// /// The name of the method used to add tag helper attributes. ///