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

Commit 81a8a77

Browse files
author
N. Taylor Mullen
committed
Re-design TagHelperOutput and runtime dependencies to allow all content modes.
- Added PreContent, PostContent and ContentSet properties to TagHelperOutput. - Added GeneratePreContent, GeneratePostContent and SupressOutput methods to TagHelperOutput. - Added multile ExecuteChildContentAsync and GetChildContentAsync to the rendering phase, ultimately only exposing GetChildContentAsync to a TagHelper author. - Added more knowledge of StartWritingScope and EndWritingScope to the TagHelper runtime components. This is to enable the runtime components to utilize the RazorPage's infrastructure to render a delegate to a writer and retrieve its value to ultimately expose it to the user. #221
1 parent 2f1bcf8 commit 81a8a77

File tree

6 files changed

+174
-47
lines changed

6 files changed

+174
-47
lines changed

src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperContext.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.Collections.Generic;
6+
using System.Threading.Tasks;
57

68
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
79
{
@@ -14,14 +16,26 @@ public class TagHelperContext
1416
/// Instantiates a new <see cref="TagHelperContext"/>.
1517
/// </summary>
1618
/// <param name="allAttributes">Every attribute associated with the current HTML element.</param>
17-
public TagHelperContext([NotNull] IDictionary<string, object> allAttributes)
19+
/// <param name="getChildContentAsync">A delegate used to execute and retrieve the rendered child content
20+
/// asynchronously.</param>
21+
public TagHelperContext([NotNull] IDictionary<string, object> allAttributes,
22+
[NotNull] Func<Task<string>> getChildContentAsync)
1823
{
1924
AllAttributes = allAttributes;
25+
GetChildContentAsync = getChildContentAsync;
2026
}
2127

2228
/// <summary>
2329
/// Every attribute associated with the current HTML element.
2430
/// </summary>
25-
public IDictionary<string, object> AllAttributes { get; private set; }
31+
public IDictionary<string, object> AllAttributes { get; }
32+
33+
/// <summary>
34+
/// A delegate used to execute and retrieve the rendered child content asynchronously.
35+
/// </summary>
36+
/// <remarks>
37+
/// Child content is only executed once. Successive calls to this delegate return a cached result.
38+
/// </remarks>
39+
public Func<Task<string>> GetChildContentAsync { get; }
2640
}
2741
}

src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperExecutionContext.cs

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.IO;
7+
using System.Threading.Tasks;
68

79
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
810
{
@@ -12,28 +14,71 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
1214
public class TagHelperExecutionContext
1315
{
1416
private readonly List<ITagHelper> _tagHelpers;
17+
private string _childContent;
1518

1619
/// <summary>
1720
/// Instantiates a new <see cref="TagHelperExecutionContext"/>.
1821
/// </summary>
1922
/// <param name="tagName">The HTML tag name in the Razor source.</param>
20-
public TagHelperExecutionContext([NotNull] string tagName)
23+
/// <param name="executeChildContentAsync">A delegate used to execute the child content asynchronously.</param>
24+
/// <param name="startWritingScope">A delegate used to start a writing scope in a Razor page.</param>
25+
/// <param name="endWritingScope">A delegate used to end a writing scope in a Razor page.</param>
26+
public TagHelperExecutionContext([NotNull] string tagName,
27+
[NotNull] Func<Task> executeChildContentAsync,
28+
[NotNull] Action startWritingScope,
29+
[NotNull] Func<TextWriter> endWritingScope)
2130
{
31+
ExecuteChildContentAsync = executeChildContentAsync;
32+
GetChildContentAsync = async () =>
33+
{
34+
if (_childContent == null)
35+
{
36+
startWritingScope();
37+
await executeChildContentAsync();
38+
_childContent = endWritingScope().ToString();
39+
}
40+
41+
return _childContent;
42+
};
2243
AllAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
2344
HTMLAttributes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
2445
_tagHelpers = new List<ITagHelper>();
2546
TagName = tagName;
2647
}
2748

49+
/// <summary>
50+
/// A delegate used to execute the child content asynchronously.
51+
/// </summary>
52+
public Func<Task> ExecuteChildContentAsync { get; }
53+
54+
/// <summary>
55+
/// A delegate used to execute and retrieve the rendered child content asynchronously.
56+
/// </summary>
57+
/// <remarks>
58+
/// Child content is only executed once. Successive calls to this delegate return a cached result.
59+
/// </remarks>
60+
public Func<Task<string>> GetChildContentAsync { get; }
61+
62+
/// <summary>
63+
/// Indicates if <see cref="GetChildContentAsync"/> has been called.
64+
/// </summary>
65+
public bool ChildContentRetrieved
66+
{
67+
get
68+
{
69+
return _childContent != null;
70+
}
71+
}
72+
2873
/// <summary>
2974
/// HTML attributes.
3075
/// </summary>
31-
public IDictionary<string, string> HTMLAttributes { get; private set; }
76+
public IDictionary<string, string> HTMLAttributes { get; }
3277

3378
/// <summary>
3479
/// <see cref="ITagHelper"/> bound attributes and HTML attributes.
3580
/// </summary>
36-
public IDictionary<string, object> AllAttributes { get; private set; }
81+
public IDictionary<string, object> AllAttributes { get; }
3782

3883
/// <summary>
3984
/// <see cref="ITagHelper"/>s that should be run.
@@ -49,7 +94,7 @@ public IEnumerable<ITagHelper> TagHelpers
4994
/// <summary>
5095
/// The HTML tag name in the Razor source.
5196
/// </summary>
52-
public string TagName { get; private set; }
97+
public string TagName { get; }
5398

5499
/// <summary>
55100
/// The <see cref="ITagHelper"/>s' output.

src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperOutput.cs

Lines changed: 70 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
1515
public class TagHelperOutput
1616
{
1717
private string _content;
18-
private string _tagName;
18+
private bool _contentSet;
1919

2020
// Internal for testing
2121
internal TagHelperOutput(string tagName)
@@ -35,33 +35,27 @@ internal TagHelperOutput(string tagName, [NotNull] IDictionary<string, string> a
3535
/// </summary>
3636
/// <param name="tagName">The HTML element's tag name.</param>
3737
/// <param name="attributes">The HTML attributes.</param>
38-
/// <param name="content">The HTML element's content.</param>
39-
public TagHelperOutput(string tagName,
40-
[NotNull] IDictionary<string, string> attributes,
41-
string content)
38+
public TagHelperOutput(string tagName, [NotNull] IDictionary<string, string> attributes)
4239
{
4340
TagName = tagName;
44-
Content = content;
4541
Attributes = new Dictionary<string, string>(attributes, StringComparer.OrdinalIgnoreCase);
42+
PreContent = string.Empty;
43+
_content = string.Empty;
44+
PostContent = string.Empty;
4645
}
4746

4847
/// <summary>
4948
/// The HTML element's tag name.
5049
/// </summary>
5150
/// <remarks>
52-
/// A whitespace value results in no start or end tag being rendered.
51+
/// A whitespace or <c>null</c> value results in no start or end tag being rendered.
5352
/// </remarks>
54-
public string TagName
55-
{
56-
get
57-
{
58-
return _tagName;
59-
}
60-
set
61-
{
62-
_tagName = value ?? string.Empty;
63-
}
64-
}
53+
public string TagName { get; set; }
54+
55+
/// <summary>
56+
/// The HTML element's pre content.
57+
/// </summary>
58+
public string PreContent { get; set; }
6559

6660
/// <summary>
6761
/// The HTML element's content.
@@ -74,10 +68,21 @@ public string Content
7468
}
7569
set
7670
{
77-
_content = value ?? string.Empty;
71+
_contentSet = true;
72+
_content = value;
7873
}
7974
}
8075

76+
/// <summary>
77+
/// The HTML element's post content.
78+
/// </summary>
79+
public string PostContent { get; set; }
80+
81+
/// <summary>
82+
/// <c>true</c> if <see cref="Content"/> has been set, <c>false</c> otherwise.
83+
/// </summary>
84+
public bool ContentSet { get { return _contentSet; } }
85+
8186
/// <summary>
8287
/// Indicates whether or not the tag is self closing.
8388
/// </summary>
@@ -86,7 +91,7 @@ public string Content
8691
/// <summary>
8792
/// The HTML element's attributes.
8893
/// </summary>
89-
public IDictionary<string, string> Attributes { get; private set; }
94+
public IDictionary<string, string> Attributes { get; }
9095

9196
/// <summary>
9297
/// Generates the <see cref="TagHelperOutput"/>'s start tag.
@@ -126,6 +131,21 @@ public string GenerateStartTag()
126131
return sb.ToString();
127132
}
128133

134+
/// <summary>
135+
/// Generates the <see cref="TagHelperOutput"/>'s <see cref="PreContent"/>.
136+
/// </summary>
137+
/// <returns><c>string.Empty</c> if <see cref="SelfClosing"/> is <c>true</c>. <see cref="PreContent"/> otherwise.
138+
/// </returns>
139+
public string GeneratePreContent()
140+
{
141+
if (SelfClosing)
142+
{
143+
return string.Empty;
144+
}
145+
146+
return PreContent;
147+
}
148+
129149
/// <summary>
130150
/// Generates the <see cref="TagHelperOutput"/>'s body.
131151
/// </summary>
@@ -141,6 +161,21 @@ public string GenerateContent()
141161
return Content;
142162
}
143163

164+
/// <summary>
165+
/// Generates the <see cref="TagHelperOutput"/>'s <see cref="PostContent"/>.
166+
/// </summary>
167+
/// <returns><c>string.Empty</c> if <see cref="SelfClosing"/> is <c>true</c>. <see cref="PostContent"/> otherwise.
168+
/// </returns>
169+
public string GeneratePostContent()
170+
{
171+
if (SelfClosing)
172+
{
173+
return string.Empty;
174+
}
175+
176+
return PostContent;
177+
}
178+
144179
/// <summary>
145180
/// Generates the <see cref="TagHelperOutput"/>'s end tag.
146181
/// </summary>
@@ -155,5 +190,20 @@ public string GenerateEndTag()
155190

156191
return string.Format(CultureInfo.InvariantCulture, "</{0}>", TagName);
157192
}
193+
194+
/// <summary>
195+
/// Changes the output of the <see cref="TagHelperOutput"/> to generate nothing.
196+
/// </summary>
197+
/// <remarks>
198+
/// Sets <see cref="TagName"/>, <see cref="PreContent"/>, <see cref="Content"/> and <see cref="PostContent"/>
199+
/// to <c>null</c> to supress output.
200+
/// </remarks>
201+
public void SupressOutput()
202+
{
203+
TagName = null;
204+
PreContent = null;
205+
Content = null;
206+
PostContent = null;
207+
}
158208
}
159209
}

src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperRunner.cs

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,13 @@ public class TagHelperRunner
1919
/// <paramref name="context"/>'s <see cref="ITagHelper"/>s.</returns>
2020
public async Task<TagHelperOutput> RunAsync([NotNull] TagHelperExecutionContext context)
2121
{
22-
return await RunAsyncCore(context, string.Empty);
22+
return await RunAsyncCore(context);
2323
}
2424

25-
/// <summary>
26-
/// Calls the <see cref="ITagHelper.ProcessAsync"/> method on <see cref="ITagHelper"/>s.
27-
/// </summary>
28-
/// <param name="context">Contains information associated with running <see cref="ITagHelper"/>s.</param>
29-
/// <param name="bufferedBody">Contains the buffered content of the current HTML tag.</param>
30-
/// <returns>Resulting <see cref="TagHelperOutput"/> from processing all of the
31-
/// <paramref name="context"/>'s <see cref="ITagHelper"/>s.</returns>
32-
public async Task<TagHelperOutput> RunAsync([NotNull] TagHelperExecutionContext context,
33-
[NotNull] TextWriter bufferedBody)
34-
{
35-
return await RunAsyncCore(context, bufferedBody.ToString());
36-
}
37-
38-
private async Task<TagHelperOutput> RunAsyncCore(TagHelperExecutionContext executionContext, string outputContent)
25+
private async Task<TagHelperOutput> RunAsyncCore(TagHelperExecutionContext executionContext)
3926
{
40-
var tagHelperContext = new TagHelperContext(executionContext.AllAttributes);
41-
var tagHelperOutput = new TagHelperOutput(executionContext.TagName,
42-
executionContext.HTMLAttributes,
43-
outputContent);
27+
var tagHelperContext = new TagHelperContext(executionContext.AllAttributes, executionContext.GetChildContentAsync);
28+
var tagHelperOutput = new TagHelperOutput(executionContext.TagName, executionContext.HTMLAttributes);
4429

4530
foreach (var tagHelper in executionContext.TagHelpers)
4631
{

src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperScopeManager.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.IO;
7+
using System.Threading.Tasks;
68

79
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
810
{
@@ -25,10 +27,17 @@ public TagHelperScopeManager()
2527
/// Starts a <see cref="TagHelperExecutionContext"/> scope.
2628
/// </summary>
2729
/// <param name="tagName">The HTML tag name that the scope is associated with.</param>
30+
/// <param name="executeChildContentAsync">A delegate used to execute the child content asynchronously.</param>
31+
/// <param name="startWritingScope">A delegate used to start a writing scope in a Razor page.</param>
32+
/// <param name="endWritingScope">A delegate used to end a writing scope in a Razor page.</param>
2833
/// <returns>A <see cref="TagHelperExecutionContext"/> to use.</returns>
29-
public TagHelperExecutionContext Begin(string tagName)
34+
public TagHelperExecutionContext Begin([NotNull] string tagName,
35+
[NotNull] Func<Task> executeChildContentAsync,
36+
[NotNull] Action startWritingScope,
37+
[NotNull] Func<TextWriter> endWritingScope)
3038
{
31-
var executionContext = new TagHelperExecutionContext(tagName);
39+
var executionContext =
40+
new TagHelperExecutionContext(tagName, executeChildContentAsync, startWritingScope, endWritingScope);
3241

3342
_executionScopes.Push(executionContext);
3443

0 commit comments

Comments
 (0)