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

Commit f4b967c

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 e85beb1 commit f4b967c

File tree

6 files changed

+196
-61
lines changed

6 files changed

+196
-61
lines changed
Lines changed: 21 additions & 3 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
{
@@ -10,25 +12,41 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
1012
/// </summary>
1113
public class TagHelperContext
1214
{
15+
private readonly Func<Task<string>> _getChildContentAsync;
16+
1317
/// <summary>
1418
/// Instantiates a new <see cref="TagHelperContext"/>.
1519
/// </summary>
1620
/// <param name="allAttributes">Every attribute associated with the current HTML element.</param>
1721
/// <param name="uniqueId">The unique identifier for the source element this <see cref="TagHelperContext" /> applies to.</param>
18-
public TagHelperContext([NotNull] IDictionary<string, object> allAttributes, [NotNull] string uniqueId)
22+
/// <param name="getChildContentAsync">A delegate used to execute and retrieve the rendered child content
23+
/// asynchronously.</param>
24+
public TagHelperContext([NotNull] IDictionary<string, object> allAttributes,
25+
[NotNull] string uniqueId,
26+
[NotNull] Func<Task<string>> getChildContentAsync)
1927
{
2028
AllAttributes = allAttributes;
2129
UniqueId = uniqueId;
30+
_getChildContentAsync = getChildContentAsync;
2231
}
2332

2433
/// <summary>
2534
/// Every attribute associated with the current HTML element.
2635
/// </summary>
27-
public IDictionary<string, object> AllAttributes { get; private set; }
36+
public IDictionary<string, object> AllAttributes { get; }
2837

2938
/// <summary>
3039
/// An identifier unique to the HTML element this context is for.
3140
/// </summary>
32-
public string UniqueId { get; private set; }
41+
public string UniqueId { get; }
42+
43+
/// <summary>
44+
/// A delegate used to execute and retrieve the rendered child content asynchronously.
45+
/// </summary>
46+
/// <returns>A <see cref="Task"/> that when executed returns content rendered by children,</returns>
47+
public Task<string> GetChildContentAsync()
48+
{
49+
return _getChildContentAsync();
50+
}
3351
}
3452
}

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

Lines changed: 58 additions & 10 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,43 +14,61 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
1214
public class TagHelperExecutionContext
1315
{
1416
private readonly List<ITagHelper> _tagHelpers;
17+
private readonly Func<Task> _executeChildContentAsync;
18+
private readonly Action _startWritingScope;
19+
private readonly Func<TextWriter> _endWritingScope;
20+
private string _childContent;
1521

1622
/// <summary>
1723
/// Instantiates a new <see cref="TagHelperExecutionContext"/>.
1824
/// </summary>
1925
/// <param name="tagName">The HTML tag name in the Razor source.</param>
20-
public TagHelperExecutionContext([NotNull] string tagName, [NotNull] string uniqueId)
26+
/// <param name="uniqueId">An identifier unique to the HTML element this context is for.</param>
27+
/// <param name="executeChildContentAsync">A delegate used to execute the child content asynchronously.</param>
28+
/// <param name="startWritingScope">A delegate used to start a writing scope in a Razor page.</param>
29+
/// <param name="endWritingScope">A delegate used to end a writing scope in a Razor page.</param>
30+
public TagHelperExecutionContext([NotNull] string tagName,
31+
[NotNull] string uniqueId,
32+
[NotNull] Func<Task> executeChildContentAsync,
33+
[NotNull] Action startWritingScope,
34+
[NotNull] Func<TextWriter> endWritingScope)
2135
{
36+
_tagHelpers = new List<ITagHelper>();
37+
_executeChildContentAsync = executeChildContentAsync;
38+
_startWritingScope = startWritingScope;
39+
_endWritingScope = endWritingScope;
40+
2241
AllAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
2342
HTMLAttributes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
24-
_tagHelpers = new List<ITagHelper>();
2543
TagName = tagName;
2644
UniqueId = uniqueId;
2745
}
2846

2947
/// <summary>
30-
/// Internal for testing purposes only.
48+
/// Indicates if <see cref="GetChildContentAsync"/> has been called.
3149
/// </summary>
32-
internal TagHelperExecutionContext([NotNull] string tagName)
33-
: this(tagName, string.Empty)
50+
public bool ChildContentRetrieved
3451
{
35-
52+
get
53+
{
54+
return _childContent != null;
55+
}
3656
}
3757

3858
/// <summary>
3959
/// HTML attributes.
4060
/// </summary>
41-
public IDictionary<string, string> HTMLAttributes { get; private set; }
61+
public IDictionary<string, string> HTMLAttributes { get; }
4262

4363
/// <summary>
4464
/// <see cref="ITagHelper"/> bound attributes and HTML attributes.
4565
/// </summary>
46-
public IDictionary<string, object> AllAttributes { get; private set; }
66+
public IDictionary<string, object> AllAttributes { get; }
4767

4868
/// <summary>
4969
/// An identifier unique to the HTML element this context is for.
5070
/// </summary>
51-
public string UniqueId { get; private set; }
71+
public string UniqueId { get; }
5272

5373
/// <summary>
5474
/// <see cref="ITagHelper"/>s that should be run.
@@ -64,7 +84,7 @@ public IEnumerable<ITagHelper> TagHelpers
6484
/// <summary>
6585
/// The HTML tag name in the Razor source.
6686
/// </summary>
67-
public string TagName { get; private set; }
87+
public string TagName { get; }
6888

6989
/// <summary>
7090
/// The <see cref="ITagHelper"/>s' output.
@@ -100,5 +120,33 @@ public void AddTagHelperAttribute([NotNull] string name, object value)
100120
{
101121
AllAttributes.Add(name, value);
102122
}
123+
124+
/// <summary>
125+
/// Executes the child content asynchronously.
126+
/// </summary>
127+
/// <returns>A task that indicates when child content execution has completed.</returns>
128+
public Task ExecuteChildContentAsync()
129+
{
130+
return _executeChildContentAsync();
131+
}
132+
133+
/// <summary>
134+
/// Execute and retrieve the rendered child content asynchronously.
135+
/// </summary>
136+
/// <returns>The rendered child content.</returns>
137+
/// <remarks>
138+
/// Child content is only executed once. Successive calls to this method return a cached result.
139+
/// </remarks>
140+
public async Task<string> GetChildContentAsync()
141+
{
142+
if (_childContent == null)
143+
{
144+
_startWritingScope();
145+
await _executeChildContentAsync();
146+
_childContent = _endWritingScope().ToString();
147+
}
148+
149+
return _childContent;
150+
}
103151
}
104152
}

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: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
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.IO;
54
using System.Threading.Tasks;
65

76
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
@@ -14,33 +13,16 @@ public class TagHelperRunner
1413
/// <summary>
1514
/// Calls the <see cref="ITagHelper.ProcessAsync"/> method on <see cref="ITagHelper"/>s.
1615
/// </summary>
17-
/// <param name="context">Contains information associated with running <see cref="ITagHelper"/>s.</param>
16+
/// <param name="executionContext">Contains information associated with running <see cref="ITagHelper"/>s.</param>
1817
/// <returns>Resulting <see cref="TagHelperOutput"/> from processing all of the
19-
/// <paramref name="context"/>'s <see cref="ITagHelper"/>s.</returns>
20-
public async Task<TagHelperOutput> RunAsync([NotNull] TagHelperExecutionContext context)
18+
/// <paramref name="executionContext"/>'s <see cref="ITagHelper"/>s.</returns>
19+
public async Task<TagHelperOutput> RunAsync([NotNull] TagHelperExecutionContext executionContext)
2120
{
22-
return await RunAsyncCore(context, string.Empty);
23-
}
24-
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)
39-
{
40-
var tagHelperContext = new TagHelperContext(executionContext.AllAttributes, executionContext.UniqueId);
41-
var tagHelperOutput = new TagHelperOutput(executionContext.TagName,
42-
executionContext.HTMLAttributes,
43-
outputContent);
21+
var tagHelperContext = new TagHelperContext(
22+
executionContext.AllAttributes,
23+
executionContext.UniqueId,
24+
executionContext.GetChildContentAsync);
25+
var tagHelperOutput = new TagHelperOutput(executionContext.TagName, executionContext.HTMLAttributes);
4426

4527
foreach (var tagHelper in executionContext.TagHelpers)
4628
{

0 commit comments

Comments
 (0)