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

React to aspnet/Razor#221: Modify TagHelpers to utilize new ContentMode design. #1731

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,18 @@ private ITagHelperActivator TagHelperActivator
/// be buffered until <see cref="EndWritingScope"/> is called.
/// </remarks>
public void StartWritingScope()
{
StartWritingScope(new StringWriter());
}

/// <summary>
/// Starts a new writing scope with the given <paramref name="writer"/>.
/// </summary>
/// <remarks>
/// All writes to the <see cref="Output"/> or <see cref="ViewContext.Writer"/> after calling this method will
/// be buffered until <see cref="EndWritingScope"/> is called.
/// </remarks>
public void StartWritingScope(TextWriter writer)
{
// If there isn't a base writer take the ViewContext.Writer
if (_originalWriter == null)
Expand All @@ -162,7 +174,7 @@ public void StartWritingScope()

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

_writerScopes.Push(ViewContext.Writer);
}
Expand Down
6 changes: 2 additions & 4 deletions src/Microsoft.AspNet.Mvc.TagHelpers/FormTagHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@
using System.Linq;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Razor.TagHelpers;

namespace Microsoft.AspNet.Mvc.TagHelpers
{
/// <summary>
/// <see cref="ITagHelper"/> implementation targeting &lt;form&gt; elements.
/// </summary>
[ContentBehavior(ContentBehavior.Append)]
public class FormTagHelper : TagHelper
{
private const string ActionAttributeName = "asp-action";
Expand Down Expand Up @@ -106,7 +104,7 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
if (tagBuilder != null)
{
output.MergeAttributes(tagBuilder);
output.Content += tagBuilder.InnerHtml;
output.PostContent += tagBuilder.InnerHtml;
output.SelfClosing = false;
}
}
Expand All @@ -116,7 +114,7 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
var antiForgeryTagBuilder = Generator.GenerateAntiForgery(ViewContext);
if (antiForgeryTagBuilder != null)
{
output.Content += antiForgeryTagBuilder.ToString(TagRenderMode.SelfClosing);
output.PostContent += antiForgeryTagBuilder.ToString(TagRenderMode.SelfClosing);
}
}
}
Expand Down
1 change: 0 additions & 1 deletion src/Microsoft.AspNet.Mvc.TagHelpers/InputTagHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <summary>
/// <see cref="ITagHelper"/> implementation targeting &lt;input&gt; elements with an <c>asp-for</c> attribute.
/// </summary>
[ContentBehavior(ContentBehavior.Replace)]
public class InputTagHelper : TagHelper
{
private const string ForAttributeName = "asp-for";
Expand Down
8 changes: 5 additions & 3 deletions src/Microsoft.AspNet.Mvc.TagHelpers/LabelTagHelper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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.Threading.Tasks;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Razor.TagHelpers;
Expand All @@ -10,7 +11,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <summary>
/// <see cref="ITagHelper"/> implementation targeting &lt;label&gt; elements with an <c>asp-for</c> attribute.
/// </summary>
[ContentBehavior(ContentBehavior.Modify)]
public class LabelTagHelper : TagHelper
{
private const string ForAttributeName = "asp-for";
Expand All @@ -31,7 +31,7 @@ public class LabelTagHelper : TagHelper

/// <inheritdoc />
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
public override void Process(TagHelperContext context, TagHelperOutput output)
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (For != null)
{
Expand All @@ -45,10 +45,12 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.MergeAttributes(tagBuilder);

var childContent = await context.GetChildContentAsync();

// We check for whitespace to detect scenarios such as:
// <label for="Name">
// </label>
if (string.IsNullOrWhiteSpace(output.Content))
if (string.IsNullOrWhiteSpace(childContent) && string.IsNullOrEmpty(output.Content))
{
output.Content = tagBuilder.InnerHtml;
}
Expand Down
8 changes: 4 additions & 4 deletions src/Microsoft.AspNet.Mvc.TagHelpers/OptionTagHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Razor.TagHelpers;
Expand All @@ -17,7 +18,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <see cref="ContentBehavior.Modify"/> in order to read element's content but does not modify that content. The
/// only modification it makes is to add a <c>selected</c> attribute in some cases.
/// </remarks>
[ContentBehavior(ContentBehavior.Modify)]
public class OptionTagHelper : TagHelper
{
// Protected to ensure subclasses are correctly activated. Internal for ease of use when testing.
Expand Down Expand Up @@ -51,7 +51,7 @@ public class OptionTagHelper : TagHelper
/// <see cref="ICollection{string}"/> instance. Also does nothing if the associated &lt;option&gt; is already
/// selected.
/// </remarks>
public override void Process(TagHelperContext context, TagHelperOutput output)
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
// Pass through attributes that are also well-known HTML attributes.
if (Value != null)
Expand Down Expand Up @@ -84,9 +84,9 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
}

// Select this <option/> element if value attribute or content matches a selected value. Callers
// encode values as-needed before setting TagHelperOutput.Content. But TagHelperOutput itself
// encode values as-needed while executing child content. But TagHelperOutput itself
// encodes attribute values later, when GenerateStartTag() is called.
var text = output.Content;
var text = await context.GetChildContentAsync();
var selected = (Value != null) ? selectedValues.Contains(Value) : encodedValues.Contains(text);
if (selected)
{
Expand Down
3 changes: 1 addition & 2 deletions src/Microsoft.AspNet.Mvc.TagHelpers/SelectTagHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <summary>
/// <see cref="ITagHelper"/> implementation targeting &lt;select&gt; elements with an <c>asp-for</c> attribute.
/// </summary>
[ContentBehavior(ContentBehavior.Append)]
public class SelectTagHelper : TagHelper
{
private const string ForAttributeName = "asp-for";
Expand Down Expand Up @@ -146,7 +145,7 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
if (tagBuilder != null)
{
output.MergeAttributes(tagBuilder);
output.Content += tagBuilder.InnerHtml;
output.PostContent += tagBuilder.InnerHtml;
output.SelfClosing = false;
}

Expand Down
1 change: 0 additions & 1 deletion src/Microsoft.AspNet.Mvc.TagHelpers/TextAreaTagHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <summary>
/// <see cref="ITagHelper"/> implementation targeting &lt;textarea&gt; elements with an <c>asp-for</c> attribute.
/// </summary>
[ContentBehavior(ContentBehavior.Replace)]
public class TextAreaTagHelper : TagHelper
{
private const string ForAttributeName = "asp-for";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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.Threading.Tasks;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Razor.TagHelpers;
Expand All @@ -12,7 +13,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// attribute.
/// </summary>
[TagName("span")]
[ContentBehavior(ContentBehavior.Modify)]
public class ValidationMessageTagHelper : TagHelper
{
private const string ValidationForAttributeName = "asp-validation-for";
Expand All @@ -33,7 +33,7 @@ public class ValidationMessageTagHelper : TagHelper

/// <inheritdoc />
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
public override void Process(TagHelperContext context, TagHelperOutput output)
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (For != null)
{
Expand All @@ -47,10 +47,12 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.MergeAttributes(tagBuilder);

var childContent = await context.GetChildContentAsync();

// We check for whitespace to detect scenarios such as:
// <span validation-for="Name">
// </span>
if (string.IsNullOrWhiteSpace(output.Content))
if (string.IsNullOrWhiteSpace(childContent) && string.IsNullOrEmpty(output.Content))
{
output.Content = tagBuilder.InnerHtml;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// attribute.
/// </summary>
[TagName("div")]
[ContentBehavior(ContentBehavior.Append)]
public class ValidationSummaryTagHelper : TagHelper
{
private const string ValidationSummaryAttributeName = "asp-validation-summary";
Expand Down Expand Up @@ -71,7 +70,7 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
if (tagBuilder != null)
{
output.MergeAttributes(tagBuilder);
output.Content += tagBuilder.InnerHtml;
output.PostContent += tagBuilder.InnerHtml;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,29 @@ public override async Task ExecuteAsync()
BeginContext(120, 2, true);
WriteLiteral("\r\n");
EndContext();
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("inputTest", "test");
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("inputTest", "test", async() => {
}
, StartWritingScope, EndWritingScope);
__Microsoft_AspNet_Mvc_Razor_InputTestTagHelper = CreateTagHelper<Microsoft.AspNet.Mvc.Razor.InputTestTagHelper>();
__tagHelperExecutionContext.Add(__Microsoft_AspNet_Mvc_Razor_InputTestTagHelper);
__Microsoft_AspNet_Mvc_Razor_InputTestTagHelper.For = CreateModelExpression(__model => __model.Now);
__tagHelperExecutionContext.AddTagHelperAttribute("For", __Microsoft_AspNet_Mvc_Razor_InputTestTagHelper.For);
__tagHelperExecutionContext.Output = __tagHelperRunner.RunAsync(__tagHelperExecutionContext).Result;
WriteLiteral(__tagHelperExecutionContext.Output.GenerateStartTag());
WriteLiteral(__tagHelperExecutionContext.Output.GeneratePreContent());
if (__tagHelperExecutionContext.Output.ContentSet)
{
WriteLiteral(__tagHelperExecutionContext.Output.GenerateContent());
}
else if (__tagHelperExecutionContext.ChildContentRetrieved)
{
WriteLiteral(__tagHelperExecutionContext.GetChildContentAsync().Result);
}
else
{
__tagHelperExecutionContext.ExecuteChildContentAsync().Wait();
}
WriteLiteral(__tagHelperExecutionContext.Output.GeneratePostContent());
WriteLiteral(__tagHelperExecutionContext.Output.GenerateEndTag());
__tagHelperExecutionContext = __tagHelperScopeManager.End();
}
Expand Down
35 changes: 22 additions & 13 deletions test/Microsoft.AspNet.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,18 @@ public async Task ProcessAsync_GeneratesExpectedOutput()
{ "asp-host", "contoso.com" },
{ "asp-protocol", "http" }
},
uniqueId: "test");
uniqueId: "test",
getChildContentAsync: () => Task.FromResult("Something Else"));
var output = new TagHelperOutput(
expectedTagName,
attributes: new Dictionary<string, string>
{
{ "id", "myanchor" },
{ "asp-route-foo", "bar" },
},
content: "Something");
})
{
Content = "Something"
};

var urlHelper = new Mock<IUrlHelper>();
urlHelper
Expand Down Expand Up @@ -85,11 +88,15 @@ public async Task ProcessAsync_CallsIntoRouteLinkWithExpectedParameters()
{
// Arrange
var context = new TagHelperContext(
allAttributes: new Dictionary<string, object>(), uniqueId: "test");
allAttributes: new Dictionary<string, object>(),
uniqueId: "test",
getChildContentAsync: () => Task.FromResult("Something"));
var output = new TagHelperOutput(
"a",
attributes: new Dictionary<string, string>(),
content: string.Empty);
attributes: new Dictionary<string, string>())
{
Content = string.Empty
};

var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
generator
Expand Down Expand Up @@ -119,11 +126,15 @@ public async Task ProcessAsync_CallsIntoActionLinkWithExpectedParameters()
{
// Arrange
var context = new TagHelperContext(
allAttributes: new Dictionary<string, object>(), uniqueId: "test");
allAttributes: new Dictionary<string, object>(),
uniqueId: "test",
getChildContentAsync: () => Task.FromResult("Something"));
var output = new TagHelperOutput(
"a",
attributes: new Dictionary<string, string>(),
content: string.Empty);
attributes: new Dictionary<string, string>())
{
Content = string.Empty
};

var generator = new Mock<IHtmlGenerator>();
generator
Expand Down Expand Up @@ -166,8 +177,7 @@ public async Task ProcessAsync_ThrowsIfHrefConflictsWithBoundAttributes(string p
attributes: new Dictionary<string, string>()
{
{ "href", "http://www.contoso.com" }
},
content: string.Empty);
});
if (propertyName == "asp-route-")
{
output.Attributes.Add("asp-route-foo", "bar");
Expand Down Expand Up @@ -202,8 +212,7 @@ public async Task ProcessAsync_ThrowsIfRouteAndActionOrControllerProvided(string
typeof(AnchorTagHelper).GetProperty(propertyName).SetValue(anchorTagHelper, "Home");
var output = new TagHelperOutput(
"a",
attributes: new Dictionary<string, string>(),
content: string.Empty);
attributes: new Dictionary<string, string>());
var expectedErrorMessage = "Cannot determine an 'href' attribute for <a>. An <a> with a specified " +
"'asp-route' must not have an 'asp-action' or 'asp-controller' attribute.";

Expand Down
Loading