diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs
index 24472fe115..64d71834e1 100644
--- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs
@@ -92,15 +92,13 @@ namespace Microsoft.AspNet.Mvc.Razor
ScopeManagerBeginMethodName = nameof(TagHelperScopeManager.Begin),
ScopeManagerEndMethodName = nameof(TagHelperScopeManager.End),
- OutputGenerateStartTagMethodName = nameof(TagHelperOutput.GenerateStartTag),
- OutputGenerateContentMethodName = nameof(TagHelperOutput.GenerateContent),
- OutputGenerateEndTagMethodName = nameof(TagHelperOutput.GenerateEndTag),
-
// Can't use nameof because RazorPage is not accessible here.
CreateTagHelperMethodName = "CreateTagHelper",
StartTagHelperWritingScopeMethodName = "StartTagHelperWritingScope",
EndTagHelperWritingScopeMethodName = "EndTagHelperWritingScope",
- HtmlEncoderPropertyName = "HtmlEncoder",
+
+ WriteTagHelperAsyncMethodName = "WriteTagHelperAsync",
+ WriteTagHelperToAsyncMethodName = "WriteTagHelperToAsync",
})
{
ResolveUrlMethodName = "Href",
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
index b332d106d0..59a0810fdd 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Claims;
@@ -250,25 +251,88 @@ namespace Microsoft.AspNet.Mvc.Razor
}
///
- /// Writes an to the .
+ /// Writes the content of a specified .
///
- /// Contains the data to be written.
- public void Write(ITextWriterCopyable copyableTextWriter)
+ /// The execution context containing the content.
+ ///
+ /// A that on completion writes the content.
+ ///
+ public async Task WriteTagHelperAsync([NotNull] TagHelperExecutionContext tagHelperExecutionContext)
{
- WriteTo(Output, copyableTextWriter);
+ await WriteTagHelperToAsync(Output, tagHelperExecutionContext);
}
///
- /// Writes an to the .
+ /// Writes the content of a specified to the specified
+ /// .
///
- /// The to which the
- /// is written.
- /// Contains the data to be written.
- public void WriteTo([NotNull] TextWriter writer, ITextWriterCopyable copyableTextWriter)
+ /// The instance to write to.
+ /// The execution context containing the content.
+ ///
+ /// A that on completion writes the content
+ /// to the .
+ ///
+ public async Task WriteTagHelperToAsync(
+ [NotNull] TextWriter writer,
+ [NotNull] TagHelperExecutionContext tagHelperExecutionContext)
{
- if (copyableTextWriter != null)
+ var tagHelperOutput = tagHelperExecutionContext.Output;
+ var isTagNameNullOrWhitespace = string.IsNullOrWhiteSpace(tagHelperOutput.TagName);
+
+ if (!isTagNameNullOrWhitespace)
{
- copyableTextWriter.CopyTo(writer);
+ writer.Write('<');
+ writer.Write(tagHelperOutput.TagName);
+
+ foreach (var attribute in tagHelperOutput.Attributes)
+ {
+ var value = HtmlEncoder.HtmlEncode(attribute.Value);
+ writer.Write(' ');
+ writer.Write(attribute.Key);
+ writer.Write("=\"");
+ writer.Write(value);
+ writer.Write('"');
+ }
+
+ if (tagHelperOutput.SelfClosing)
+ {
+ writer.Write(" /");
+ }
+
+ writer.Write('>');
+ }
+
+ if (isTagNameNullOrWhitespace || !tagHelperOutput.SelfClosing)
+ {
+ WriteTagHelperContentTo(writer, tagHelperOutput.PreContent);
+ if (tagHelperOutput.IsContentModified)
+ {
+ WriteTagHelperContentTo(writer, tagHelperOutput.Content);
+ }
+ else if (tagHelperExecutionContext.ChildContentRetrieved)
+ {
+ var childContent = await tagHelperExecutionContext.GetChildContentAsync();
+ WriteTagHelperContentTo(writer, childContent);
+ }
+ else
+ {
+ await tagHelperExecutionContext.ExecuteChildContentAsync();
+ }
+
+ WriteTagHelperContentTo(writer, tagHelperOutput.PostContent);
+ }
+
+ if (!isTagNameNullOrWhitespace && !tagHelperOutput.SelfClosing)
+ {
+ writer.Write(string.Format(CultureInfo.InvariantCulture, "{0}>", tagHelperOutput.TagName));
+ }
+ }
+
+ private void WriteTagHelperContentTo(TextWriter writer, TagHelperContent content)
+ {
+ foreach (var entry in content)
+ {
+ writer.Write(entry);
}
}
@@ -310,20 +374,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
else
{
- // This path is called when GetChildContentAsync() is called in tag helper
- // and content is not set.
- var tagHelperContent = value as TagHelperContent;
- if (tagHelperContent != null)
- {
- foreach (var entry in tagHelperContent)
- {
- writer.Write(entry);
- }
- }
- else
- {
- WriteTo(writer, value.ToString());
- }
+ WriteTo(writer, value.ToString());
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/OptionTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/OptionTagHelper.cs
index aca4a87b85..10529cc6e8 100644
--- a/src/Microsoft.AspNet.Mvc.TagHelpers/OptionTagHelper.cs
+++ b/src/Microsoft.AspNet.Mvc.TagHelpers/OptionTagHelper.cs
@@ -85,7 +85,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Select this element if value attribute or content matches a selected value. Callers
// encode values as-needed while executing child content. But TagHelperOutput itself
- // encodes attribute values later, when GenerateStartTag() is called.
+ // encodes attribute values later, when start tag is generated.
bool selected;
if (Value != null)
{
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/ModelExpressionTagHelper.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/ModelExpressionTagHelper.cs
index 70b6e4c8e1..9d304cae6d 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/ModelExpressionTagHelper.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/ModelExpressionTagHelper.cs
@@ -42,7 +42,7 @@ namespace Asp
#pragma warning disable 1998
public override async Task ExecuteAsync()
{
- __tagHelperRunner = __tagHelperRunner ?? new Microsoft.AspNet.Razor.Runtime.TagHelpers.TagHelperRunner(HtmlEncoder);
+ __tagHelperRunner = __tagHelperRunner ?? new Microsoft.AspNet.Razor.Runtime.TagHelpers.TagHelperRunner();
BeginContext(120, 2, true);
WriteLiteral("\r\n");
EndContext();
@@ -58,22 +58,7 @@ __Microsoft_AspNet_Mvc_Razor_InputTestTagHelper.For = CreateModelExpression(__mo
#line hidden
__tagHelperExecutionContext.AddTagHelperAttribute("for", __Microsoft_AspNet_Mvc_Razor_InputTestTagHelper.For);
__tagHelperExecutionContext.Output = __tagHelperRunner.RunAsync(__tagHelperExecutionContext).Result;
- WriteLiteral(__tagHelperExecutionContext.Output.GenerateStartTag());
- Write(__tagHelperExecutionContext.Output.GeneratePreContent());
- if (__tagHelperExecutionContext.Output.IsContentModified)
- {
- Write(__tagHelperExecutionContext.Output.GenerateContent());
- }
- else if (__tagHelperExecutionContext.ChildContentRetrieved)
- {
- Write(__tagHelperExecutionContext.GetChildContentAsync().Result);
- }
- else
- {
- __tagHelperExecutionContext.ExecuteChildContentAsync().Wait();
- }
- Write(__tagHelperExecutionContext.Output.GeneratePostContent());
- WriteLiteral(__tagHelperExecutionContext.Output.GenerateEndTag());
+ WriteTagHelperAsync(__tagHelperExecutionContext).Wait();
__tagHelperExecutionContext = __tagHelperScopeManager.End();
BeginContext(146, 2, true);
WriteLiteral("\r\n");
@@ -90,22 +75,7 @@ __Microsoft_AspNet_Mvc_Razor_InputTestTagHelper.For = CreateModelExpression(__mo
#line hidden
__tagHelperExecutionContext.AddTagHelperAttribute("for", __Microsoft_AspNet_Mvc_Razor_InputTestTagHelper.For);
__tagHelperExecutionContext.Output = __tagHelperRunner.RunAsync(__tagHelperExecutionContext).Result;
- WriteLiteral(__tagHelperExecutionContext.Output.GenerateStartTag());
- Write(__tagHelperExecutionContext.Output.GeneratePreContent());
- if (__tagHelperExecutionContext.Output.IsContentModified)
- {
- Write(__tagHelperExecutionContext.Output.GenerateContent());
- }
- else if (__tagHelperExecutionContext.ChildContentRetrieved)
- {
- Write(__tagHelperExecutionContext.GetChildContentAsync().Result);
- }
- else
- {
- __tagHelperExecutionContext.ExecuteChildContentAsync().Wait();
- }
- Write(__tagHelperExecutionContext.Output.GeneratePostContent());
- WriteLiteral(__tagHelperExecutionContext.Output.GenerateEndTag());
+ WriteTagHelperAsync(__tagHelperExecutionContext).Wait();
__tagHelperExecutionContext = __tagHelperScopeManager.End();
}
#pragma warning restore 1998
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs
index bdecdf37db..1804fe480a 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs
@@ -687,96 +687,245 @@ namespace Microsoft.AspNet.Mvc.Razor
Assert.Same(stringCollectionWriter.Buffer.BufferEntries, buffer.BufferEntries[1]);
}
- [Fact]
- public async Task Write_ITextWriterCopyable_WritesContent()
+ public static TheoryData WriteTagHelper_InputData
+ {
+ get
+ {
+ // parameters: TagHelperOutput, expectedOutput
+ return new TheoryData
+ {
+ {
+ // parameters: TagName, Attributes, SelfClosing, PreContent, Content, PostContent
+ GetTagHelperOutput("div", new Dictionary(), false, null, "Hello World!", null),
+ "Hello World!
"
+ },
+ {
+ GetTagHelperOutput(null, new Dictionary(), false, null, "Hello World!", null),
+ "Hello World!"
+ },
+ {
+ GetTagHelperOutput(" ", new Dictionary(), false, null, "Hello World!", null),
+ "Hello World!"
+ },
+ {
+ GetTagHelperOutput(
+ "p",
+ new Dictionary() { { "test", "testVal" } },
+ false,
+ null,
+ "Hello World!",
+ null),
+ "Hello World!
"
+ },
+ {
+ GetTagHelperOutput(
+ "p",
+ new Dictionary() { { "test", "testVal" }, { "something", " spaced " } },
+ false,
+ null,
+ "Hello World!",
+ null),
+ "Hello World!
"
+ },
+ {
+ GetTagHelperOutput(
+ "p",
+ new Dictionary() { { "test", "testVal" } },
+ true,
+ null,
+ "Hello World!",
+ null),
+ ""
+ },
+ {
+ GetTagHelperOutput(
+ "p",
+ new Dictionary() { { "test", "testVal" }, { "something", " spaced " } },
+ true,
+ null,
+ "Hello World!",
+ null),
+ ""
+ },
+ {
+ GetTagHelperOutput("p", new Dictionary(), false, "Hello World!", null, null),
+ "Hello World!
"
+ },
+ {
+ GetTagHelperOutput("p", new Dictionary(), false, null, "Hello World!", null),
+ "Hello World!
"
+ },
+ {
+ GetTagHelperOutput("p", new Dictionary(), false, null, null, "Hello World!"),
+ "Hello World!
"
+ },
+ {
+ GetTagHelperOutput("p", new Dictionary(), false, "Hello", "Test", "World!"),
+ "HelloTestWorld!
"
+ },
+ {
+ GetTagHelperOutput("p", new Dictionary(), true, "Hello", "Test", "World!"),
+ ""
+ },
+ {
+ GetTagHelperOutput("custom", new Dictionary(), false, "Hello", "Test", "World!"),
+ "HelloTestWorld!"
+ },
+ {
+ GetTagHelperOutput("random", new Dictionary(), true, "Hello", "Test", "World!"),
+ ""
+ }
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(WriteTagHelper_InputData))]
+ public async Task WriteTagHelperAsync_WritesFormattedTagHelper(TagHelperOutput output, string expected)
{
// Arrange
- // This writer uses BufferEntryCollection underneath and so can copy the buffer.
var writer = new StringCollectionTextWriter(Encoding.UTF8);
var context = CreateViewContext(writer);
- var expectedContent = "Hello World!";
- var contentToBeCopied = new DefaultTagHelperContent().SetContent("Hello ").Append("World!");
+ var tagHelperExecutionContext = new TagHelperExecutionContext(
+ tagName: output.TagName,
+ selfClosing: output.SelfClosing,
+ items: new Dictionary