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

Commit b7aea79

Browse files
author
N. Taylor Mullen
committed
Modify TagHelpers to use new content mode design.
- React to aspnet/Razor#221 - Modified existing TagHelpers to no longer rely on ContentBehavior and to instead utilize GetChildContentAsync, PreContent, Content and PostContent.
1 parent 8529ea2 commit b7aea79

File tree

13 files changed

+46
-37
lines changed

13 files changed

+46
-37
lines changed

samples/TagHelperSample.Web/Views/Home/Create.cshtml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<form asp-anti-forgery="false" asp-action="Create">
1414
<div class="form-horizontal">
1515
@* validation summary tag helper will target just <div/> elements and append the list of errors *@
16-
@* - i.e. this helper, like <select/> helper, has ContentBehavior.Append *@
16+
@* - i.e. this helper, like <select/> helper appends content via *@
1717
@* helper does nothing if model is valid and (client-side validation is disabled or asp-validation-summary="ModelOnly") *@
1818
@* don't need a bound attribute to match Html.ValidationSummary()'s headerTag parameter; users wrap message as they wish *@
1919
@* initially at least, will not remove the <div/> if list isn't generated *@
@@ -47,7 +47,7 @@
4747
<div class="form-group">
4848
<label asp-for="YearsEmployeed" class="control-label col-md-2" />
4949
<div class="col-md-10">
50-
@* <select/> tag helper has ContentBehavior.Append -- items render after static options *@
50+
@* <select/> items render after static options *@
5151
<select asp-for="YearsEmployeed" asp-items="(IEnumerable<SelectListItem>)ViewBag.Items" size="2" class="form-control">
5252
@* Static use of "selected" attribute may cause HTML errors if in a single-selection <select/> *@
5353
@* - @NTaylorMullen thinks <option/> tag helper could tell <select/> helper not to select anything from "items" *@

src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,18 @@ private ITagHelperActivator TagHelperActivator
155155
/// be buffered until <see cref="EndWritingScope"/> is called.
156156
/// </remarks>
157157
public void StartWritingScope()
158+
{
159+
StartWritingScope(new StringWriter());
160+
}
161+
162+
/// <summary>
163+
/// Starts a new writing scope with the given <paramref name="writer"/>.
164+
/// </summary>
165+
/// <remarks>
166+
/// All writes to the <see cref="Output"/> or <see cref="ViewContext.Writer"/> after calling this method will
167+
/// be buffered until <see cref="EndWritingScope"/> is called.
168+
/// </remarks>
169+
public void StartWritingScope(TextWriter writer)
158170
{
159171
// If there isn't a base writer take the ViewContext.Writer
160172
if (_originalWriter == null)
@@ -164,7 +176,7 @@ public void StartWritingScope()
164176

165177
// We need to replace the ViewContext's Writer to ensure that all content (including content written
166178
// from HTML helpers) is redirected.
167-
ViewContext.Writer = new StringWriter();
179+
ViewContext.Writer = writer;
168180

169181
_writerScopes.Push(ViewContext.Writer);
170182
}

src/Microsoft.AspNet.Mvc.TagHelpers/FormTagHelper.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@
66
using System.Linq;
77
using Microsoft.AspNet.Mvc.Rendering;
88
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
9-
using Microsoft.AspNet.Razor.TagHelpers;
109

1110
namespace Microsoft.AspNet.Mvc.TagHelpers
1211
{
1312
/// <summary>
1413
/// <see cref="ITagHelper"/> implementation targeting &lt;form&gt; elements.
1514
/// </summary>
16-
[ContentBehavior(ContentBehavior.Append)]
1715
public class FormTagHelper : TagHelper
1816
{
1917
private const string ActionAttributeName = "asp-action";
@@ -106,7 +104,7 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
106104
if (tagBuilder != null)
107105
{
108106
output.MergeAttributes(tagBuilder);
109-
output.Content += tagBuilder.InnerHtml;
107+
output.PostContent += tagBuilder.InnerHtml;
110108
output.SelfClosing = false;
111109
}
112110
}
@@ -116,7 +114,7 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
116114
var antiForgeryTagBuilder = Generator.GenerateAntiForgery(ViewContext);
117115
if (antiForgeryTagBuilder != null)
118116
{
119-
output.Content += antiForgeryTagBuilder.ToString(TagRenderMode.SelfClosing);
117+
output.PostContent += antiForgeryTagBuilder.ToString(TagRenderMode.SelfClosing);
120118
}
121119
}
122120
}

src/Microsoft.AspNet.Mvc.TagHelpers/InputTagHelper.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
1515
/// <summary>
1616
/// <see cref="ITagHelper"/> implementation targeting &lt;input&gt; elements with an <c>asp-for</c> attribute.
1717
/// </summary>
18-
[ContentBehavior(ContentBehavior.Replace)]
1918
public class InputTagHelper : TagHelper
2019
{
2120
private const string ForAttributeName = "asp-for";

src/Microsoft.AspNet.Mvc.TagHelpers/LabelTagHelper.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
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.Threading.Tasks;
45
using Microsoft.AspNet.Mvc.Rendering;
56
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
67
using Microsoft.AspNet.Razor.TagHelpers;
@@ -10,7 +11,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
1011
/// <summary>
1112
/// <see cref="ITagHelper"/> implementation targeting &lt;label&gt; elements with an <c>asp-for</c> attribute.
1213
/// </summary>
13-
[ContentBehavior(ContentBehavior.Modify)]
1414
public class LabelTagHelper : TagHelper
1515
{
1616
private const string ForAttributeName = "asp-for";
@@ -31,7 +31,7 @@ public class LabelTagHelper : TagHelper
3131

3232
/// <inheritdoc />
3333
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
34-
public override void Process(TagHelperContext context, TagHelperOutput output)
34+
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
3535
{
3636
if (For != null)
3737
{
@@ -45,10 +45,12 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
4545
{
4646
output.MergeAttributes(tagBuilder);
4747

48+
var childContent = await context.GetChildContentAsync();
49+
4850
// We check for whitespace to detect scenarios such as:
4951
// <label for="Name">
5052
// </label>
51-
if (string.IsNullOrWhiteSpace(output.Content))
53+
if (string.IsNullOrWhiteSpace(childContent) && string.IsNullOrEmpty(output.Content))
5254
{
5355
output.Content = tagBuilder.InnerHtml;
5456
}

src/Microsoft.AspNet.Mvc.TagHelpers/OptionTagHelper.cs

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

44
using System;
55
using System.Collections.Generic;
6+
using System.Threading.Tasks;
67
using Microsoft.AspNet.Mvc.Rendering;
78
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
89
using Microsoft.AspNet.Razor.TagHelpers;
@@ -13,11 +14,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
1314
/// <see cref="ITagHelper"/> implementation targeting &lt;option&gt; elements.
1415
/// </summary>
1516
/// <remarks>
16-
/// This <see cref="ITagHelper"/> works in conjunction with <see cref="SelectTagHelper"/>. It has
17-
/// <see cref="ContentBehavior.Modify"/> in order to read element's content but does not modify that content. The
18-
/// only modification it makes is to add a <c>selected</c> attribute in some cases.
17+
/// This <see cref="ITagHelper"/> works in conjunction with <see cref="SelectTagHelper"/>. It reads elements
18+
/// content but does not modify. The only modification it makes is to add a <c>selected</c> attribute in some
19+
/// cases.
1920
/// </remarks>
20-
[ContentBehavior(ContentBehavior.Modify)]
2121
public class OptionTagHelper : TagHelper
2222
{
2323
// Protected to ensure subclasses are correctly activated. Internal for ease of use when testing.
@@ -51,7 +51,7 @@ public class OptionTagHelper : TagHelper
5151
/// <see cref="ICollection{string}"/> instance. Also does nothing if the associated &lt;option&gt; is already
5252
/// selected.
5353
/// </remarks>
54-
public override void Process(TagHelperContext context, TagHelperOutput output)
54+
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
5555
{
5656
// Pass through attributes that are also well-known HTML attributes.
5757
if (Value != null)
@@ -84,9 +84,9 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
8484
}
8585

8686
// Select this <option/> element if value attribute or content matches a selected value. Callers
87-
// encode values as-needed before setting TagHelperOutput.Content. But TagHelperOutput itself
87+
// encode values as-needed while executing child content. But TagHelperOutput itself
8888
// encodes attribute values later, when GenerateStartTag() is called.
89-
var text = output.Content;
89+
var text = await context.GetChildContentAsync();
9090
var selected = (Value != null) ? selectedValues.Contains(Value) : encodedValues.Contains(text);
9191
if (selected)
9292
{

src/Microsoft.AspNet.Mvc.TagHelpers/SelectTagHelper.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
1515
/// <summary>
1616
/// <see cref="ITagHelper"/> implementation targeting &lt;select&gt; elements with an <c>asp-for</c> attribute.
1717
/// </summary>
18-
[ContentBehavior(ContentBehavior.Append)]
1918
public class SelectTagHelper : TagHelper
2019
{
2120
private const string ForAttributeName = "asp-for";
@@ -146,7 +145,7 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
146145
if (tagBuilder != null)
147146
{
148147
output.MergeAttributes(tagBuilder);
149-
output.Content += tagBuilder.InnerHtml;
148+
output.PostContent += tagBuilder.InnerHtml;
150149
output.SelfClosing = false;
151150
}
152151

src/Microsoft.AspNet.Mvc.TagHelpers/TextAreaTagHelper.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
1010
/// <summary>
1111
/// <see cref="ITagHelper"/> implementation targeting &lt;textarea&gt; elements with an <c>asp-for</c> attribute.
1212
/// </summary>
13-
[ContentBehavior(ContentBehavior.Replace)]
1413
[HtmlElementName("textarea")]
1514
public class TextAreaTagHelper : TagHelper
1615
{

src/Microsoft.AspNet.Mvc.TagHelpers/ValidationMessageTagHelper.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
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.Threading.Tasks;
45
using Microsoft.AspNet.Mvc.Rendering;
56
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
67
using Microsoft.AspNet.Razor.TagHelpers;
@@ -12,7 +13,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
1213
/// attribute.
1314
/// </summary>
1415
[HtmlElementName("span")]
15-
[ContentBehavior(ContentBehavior.Modify)]
1616
public class ValidationMessageTagHelper : TagHelper
1717
{
1818
private const string ValidationForAttributeName = "asp-validation-for";
@@ -33,7 +33,7 @@ public class ValidationMessageTagHelper : TagHelper
3333

3434
/// <inheritdoc />
3535
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
36-
public override void Process(TagHelperContext context, TagHelperOutput output)
36+
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
3737
{
3838
if (For != null)
3939
{
@@ -47,10 +47,12 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
4747
{
4848
output.MergeAttributes(tagBuilder);
4949

50+
var childContent = await context.GetChildContentAsync();
51+
5052
// We check for whitespace to detect scenarios such as:
5153
// <span validation-for="Name">
5254
// </span>
53-
if (string.IsNullOrWhiteSpace(output.Content))
55+
if (string.IsNullOrWhiteSpace(childContent) && string.IsNullOrEmpty(output.Content))
5456
{
5557
output.Content = tagBuilder.InnerHtml;
5658
}

src/Microsoft.AspNet.Mvc.TagHelpers/ValidationSummaryTagHelper.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
1313
/// attribute.
1414
/// </summary>
1515
[HtmlElementName("div")]
16-
[ContentBehavior(ContentBehavior.Append)]
1716
public class ValidationSummaryTagHelper : TagHelper
1817
{
1918
private const string ValidationSummaryAttributeName = "asp-validation-summary";
@@ -70,7 +69,7 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
7069
if (tagBuilder != null)
7170
{
7271
output.MergeAttributes(tagBuilder);
73-
output.Content += tagBuilder.InnerHtml;
72+
output.PostContent += tagBuilder.InnerHtml;
7473
}
7574
}
7675
}

test/WebSites/ActivatorWebSite/TagHelpers/HiddenTagHelper.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,23 @@
44
using Microsoft.AspNet.Mvc;
55
using Microsoft.AspNet.Mvc.Rendering;
66
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
7-
using Microsoft.AspNet.Razor.TagHelpers;
7+
using System.Threading.Tasks;
88

99
namespace ActivatorWebSite.TagHelpers
1010
{
1111
[HtmlElementName("span")]
12-
[ContentBehavior(ContentBehavior.Modify)]
1312
public class HiddenTagHelper : TagHelper
1413
{
1514
public string Name { get; set; }
1615

1716
[Activate]
1817
public IHtmlHelper HtmlHelper { get; set; }
1918

20-
public override void Process(TagHelperContext context, TagHelperOutput output)
19+
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
2120
{
22-
output.Content = HtmlHelper.Hidden(Name, output.Content).ToString();
21+
var content = await context.GetChildContentAsync();
22+
23+
output.Content = HtmlHelper.Hidden(Name, content).ToString();
2324
}
2425
}
2526
}

test/WebSites/ActivatorWebSite/TagHelpers/RepeatContentTagHelper.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44
using Microsoft.AspNet.Mvc;
55
using Microsoft.AspNet.Mvc.Rendering;
66
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
7-
using Microsoft.AspNet.Razor.TagHelpers;
7+
using System.Threading.Tasks;
88

99
namespace ActivatorWebSite.TagHelpers
1010
{
1111
[HtmlElementName("div")]
12-
[ContentBehavior(ContentBehavior.Modify)]
1312
public class RepeatContentTagHelper : TagHelper
1413
{
1514
public int RepeatContent { get; set; }
@@ -19,14 +18,14 @@ public class RepeatContentTagHelper : TagHelper
1918
[Activate]
2019
public IHtmlHelper HtmlHelper { get; set; }
2120

22-
public override void Process(TagHelperContext context, TagHelperOutput output)
21+
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
2322
{
23+
var content = await context.GetChildContentAsync();
2424
var repeatContent = HtmlHelper.Encode(Expression.Metadata.Model.ToString());
2525

2626
if (string.IsNullOrEmpty(repeatContent))
2727
{
28-
repeatContent = output.Content;
29-
output.Content = string.Empty;
28+
repeatContent = content;
3029
}
3130

3231
for (int i = 0; i < RepeatContent; i++)

test/WebSites/ActivatorWebSite/TagHelpers/TitleTagHelper.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
namespace ActivatorWebSite.TagHelpers
1010
{
1111
[HtmlElementName("body")]
12-
[ContentBehavior(ContentBehavior.Prepend)]
1312
public class TitleTagHelper : TagHelper
1413
{
1514
[Activate]
@@ -23,7 +22,7 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
2322
var builder = new TagBuilder("h2");
2423
var title = ViewContext.ViewBag.Title;
2524
builder.InnerHtml = HtmlHelper.Encode(title);
26-
output.Content = builder.ToString();
25+
output.PreContent = builder.ToString();
2726
}
2827
}
2928
}

0 commit comments

Comments
 (0)