Add DesignTime code generation support for TagHelpers.

- We now generate a subset of the TagHelper runtime code during DesignTime. This enables users to see errors in the editor
- Added tests to validate design time code generation.
- Refactored runtime code generation tests (we now use a lot of their infrastructure for the design time pieces).

#208
This commit is contained in:
N. Taylor Mullen 2014-10-22 15:59:35 -07:00
parent 878be8922f
commit 54155e47e5
9 changed files with 521 additions and 90 deletions

View File

@ -26,6 +26,7 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
private readonly CodeBuilderContext _context;
private readonly IChunkVisitor _bodyVisitor;
private readonly GeneratedTagHelperContext _tagHelperContext;
private readonly bool _designTimeMode;
/// <summary>
/// Instantiates a new <see cref="CSharpTagHelperCodeRenderer"/>.
@ -42,6 +43,7 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
_writer = writer;
_context = context;
_tagHelperContext = context.Host.GeneratedClassContext.GeneratedTagHelperContext;
_designTimeMode = context.Host.DesignTimeMode;
AttributeValueCodeRenderer = new TagHelperAttributeValueCodeRenderer();
}
@ -53,12 +55,6 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
/// <param name="chunk">A <see cref="TagHelperChunk"/> to render.</param>
public void RenderTagHelper(TagHelperChunk chunk)
{
// TODO: Implement design time support for tag helpers in https://github.com/aspnet/Razor/issues/83
if (_context.Host.DesignTimeMode)
{
return;
}
var tagHelperDescriptors = chunk.Descriptors;
// Find the first content behavior that doesn't have a content behavior of None.
@ -128,6 +124,12 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
private void RenderBeginTagHelperScope(string tagName)
{
// Scopes/execution contexts are a runtime feature.
if (_designTimeMode)
{
return;
}
// Call into the tag helper scope manager to start a new tag helper scope.
// Also capture the value as the current execution context.
_writer.WriteStartAssignment(ExecutionContextVariableName)
@ -157,9 +159,13 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
tagHelperDescriptor.TagHelperName)
.WriteEndMethodInvocation();
_writer.WriteInstanceMethodInvocation(ExecutionContextVariableName,
_tagHelperContext.ExecutionContextAddMethodName,
tagHelperVariableName);
// Execution contexts are a runtime feature.
if (!_designTimeMode)
{
_writer.WriteInstanceMethodInvocation(ExecutionContextVariableName,
_tagHelperContext.ExecutionContextAddMethodName,
tagHelperVariableName);
}
// Render all of the bound attribute values for the tag helper.
RenderBoundHTMLAttributes(chunk.Attributes,
@ -250,6 +256,12 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
// End the assignment to the attribute.
_writer.WriteLine(";");
// Execution contexts are a runtime feature.
if (_designTimeMode)
{
continue;
}
// We need to inform the context of the attribute value.
_writer.WriteStartInstanceMethodInvocation(
ExecutionContextVariableName,
@ -286,8 +298,15 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
BuildBufferedWritingScope(attributeValue);
}
_writer.WriteStartInstanceMethodInvocation(ExecutionContextVariableName,
_tagHelperContext.ExecutionContextAddHtmlAttributeMethodName);
// Execution contexts are a runtime feature, therefore no need to add anything to them.
if (_designTimeMode)
{
continue;
}
_writer.WriteStartInstanceMethodInvocation(
ExecutionContextVariableName,
_tagHelperContext.ExecutionContextAddHtmlAttributeMethodName);
_writer.WriteStringLiteral(htmlAttribute.Key)
.WriteParameterSeparator();
@ -322,6 +341,12 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
private void RenderEndTagHelpersScope()
{
// Scopes/execution contexts are a runtime feature.
if (_designTimeMode)
{
return;
}
_writer.WriteStartAssignment(ExecutionContextVariableName)
.WriteInstanceMethodInvocation(ScopeManagerVariableName,
_tagHelperContext.ScopeManagerEndMethodName);
@ -329,6 +354,12 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
private void RenderTagOutput(string tagOutputMethodName)
{
// Rendering output is a runtime feature.
if (_designTimeMode)
{
return;
}
CSharpCodeVisitor.RenderPreWriteStart(_writer, _context);
_writer.Write(ExecutionContextVariableName)
@ -341,6 +372,12 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
private void RenderRunTagHelpers(bool bufferedBody)
{
// No need to run anything in design time mode.
if (_designTimeMode)
{
return;
}
_writer.Write(ExecutionContextVariableName)
.Write(".")
.Write(_tagHelperContext.ExecutionContextOutputPropertyName)
@ -411,12 +448,20 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
{
_context.Host.EnableInstrumentation = false;
_writer.WriteMethodInvocation(_tagHelperContext.StartWritingScopeMethodName);
// Scopes are a runtime feature.
if (!_designTimeMode)
{
_writer.WriteMethodInvocation(_tagHelperContext.StartWritingScopeMethodName);
}
_bodyVisitor.Accept(chunks);
_writer.WriteStartAssignment(StringValueBufferVariableName)
.WriteMethodInvocation(_tagHelperContext.EndWritingScopeMethodName);
// Scopes are a runtime feature.
if (!_designTimeMode)
{
_writer.WriteStartAssignment(StringValueBufferVariableName)
.WriteMethodInvocation(_tagHelperContext.EndWritingScopeMethodName);
}
}
finally
{
@ -434,11 +479,20 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
AttributeValueCodeRenderer.RenderAttributeValue(attributeDescriptor, _writer, _context, valueRenderer);
}
private static void RenderBufferedAttributeValueAccessor(CSharpCodeWriter writer)
private void RenderBufferedAttributeValueAccessor(CSharpCodeWriter writer)
{
writer.WriteInstanceMethodInvocation(StringValueBufferVariableName,
"ToString",
endLine: false);
if (_designTimeMode)
{
// There is no value buffer in design time mode but we still want to write out a value. We write a
// value to ensure the tag helper's property type is string.
writer.Write("string.Empty");
}
else
{
writer.WriteInstanceMethodInvocation(StringValueBufferVariableName,
"ToString",
endLine: false);
}
}
private static bool IsStringAttribute(TagHelperAttributeDescriptor attributeDescriptor)

View File

@ -28,23 +28,28 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
{
_foundTagHelpers = true;
// We want to hide declared TagHelper fields so they cannot be stepped over via a debugger.
Writer.WriteLineHiddenDirective();
WritePrivateField(typeof(TextWriter).FullName,
CSharpTagHelperCodeRenderer.StringValueBufferVariableName,
value: null);
// Runtime fields aren't useful during design time.
if (!Context.Host.DesignTimeMode)
{
WritePrivateField(typeof(TextWriter).FullName,
CSharpTagHelperCodeRenderer.StringValueBufferVariableName,
value: null);
WritePrivateField(_tagHelperContext.ExecutionContextTypeName,
CSharpTagHelperCodeRenderer.ExecutionContextVariableName,
value: null);
WritePrivateField(_tagHelperContext.ExecutionContextTypeName,
CSharpTagHelperCodeRenderer.ExecutionContextVariableName,
value: null);
WritePrivateField(_tagHelperContext.RunnerTypeName,
CSharpTagHelperCodeRenderer.RunnerVariableName,
"new " + _tagHelperContext.RunnerTypeName + "()");
WritePrivateField(_tagHelperContext.RunnerTypeName,
CSharpTagHelperCodeRenderer.RunnerVariableName,
"new " + _tagHelperContext.RunnerTypeName + "()");
WritePrivateField(_tagHelperContext.ScopeManagerTypeName,
CSharpTagHelperCodeRenderer.ScopeManagerVariableName,
"new " + _tagHelperContext.ScopeManagerTypeName + "()");
WritePrivateField(_tagHelperContext.ScopeManagerTypeName,
CSharpTagHelperCodeRenderer.ScopeManagerVariableName,
"new " + _tagHelperContext.ScopeManagerTypeName + "()");
}
}
foreach (var descriptor in chunk.Descriptors)

View File

@ -11,6 +11,168 @@ namespace Microsoft.AspNet.Razor.Test.Generator
{
public class CSharpTagHelperRenderingTest : TagHelperTestBase
{
private static IEnumerable<TagHelperDescriptor> PAndInputTagHelperDescriptors
{
get
{
var pAgePropertyInfo = typeof(TestType).GetProperty("Age");
var inputTypePropertyInfo = typeof(TestType).GetProperty("Type");
var checkedPropertyInfo = typeof(TestType).GetProperty("Checked");
return new[]
{
new TagHelperDescriptor("p",
"PTagHelper",
ContentBehavior.None,
new [] {
new TagHelperAttributeDescriptor("age", pAgePropertyInfo)
}),
new TagHelperDescriptor("input",
"InputTagHelper",
ContentBehavior.None,
new TagHelperAttributeDescriptor[] {
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo)
}),
new TagHelperDescriptor("input",
"InputTagHelper2",
ContentBehavior.None,
new TagHelperAttributeDescriptor[] {
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
new TagHelperAttributeDescriptor("checked", checkedPropertyInfo)
})
};
}
}
private static IEnumerable<TagHelperDescriptor> ContentBehaviorTagHelperDescriptors
{
get
{
return new[]
{
new TagHelperDescriptor("modify", "ModifyTagHelper", ContentBehavior.Modify),
new TagHelperDescriptor("none", "NoneTagHelper", ContentBehavior.None),
new TagHelperDescriptor("append", "AppendTagHelper", ContentBehavior.Append),
new TagHelperDescriptor("prepend", "PrependTagHelper", ContentBehavior.Prepend),
new TagHelperDescriptor("replace", "ReplaceTagHelper", ContentBehavior.Replace)
};
}
}
public static TheoryData DesignTimeTagHelperTestData
{
get
{
// Test resource name, baseline resource name, expected TagHelperDescriptors, expected LineMappings
return new TheoryData<string, string, IEnumerable<TagHelperDescriptor>, List<LineMapping>>
{
{
"SingleTagHelper",
"SingleTagHelper.DesignTime",
PAndInputTagHelperDescriptors,
new List<LineMapping>
{
BuildLineMapping(documentAbsoluteIndex: 14,
documentLineIndex: 0,
generatedAbsoluteIndex: 475,
generatedLineIndex: 15,
characterOffsetIndex: 14,
contentLength: 11)
}
},
{
"BasicTagHelpers",
"BasicTagHelpers.DesignTime",
PAndInputTagHelperDescriptors,
new List<LineMapping>
{
BuildLineMapping(documentAbsoluteIndex: 14,
documentLineIndex: 0,
generatedAbsoluteIndex: 475,
generatedLineIndex: 15,
characterOffsetIndex: 14,
contentLength: 11)
}
},
{
"ContentBehaviorTagHelpers",
"ContentBehaviorTagHelpers.DesignTime",
ContentBehaviorTagHelperDescriptors,
new List<LineMapping>
{
BuildLineMapping(documentAbsoluteIndex: 14,
documentLineIndex: 0,
generatedAbsoluteIndex: 495,
generatedLineIndex: 15,
characterOffsetIndex: 14,
contentLength: 11)
}
},
{
"ComplexTagHelpers",
"ComplexTagHelpers.DesignTime",
PAndInputTagHelperDescriptors,
new List<LineMapping>
{
BuildLineMapping(14, 0, 479, 15, 14, 11),
BuildLineMapping(30, 2, 1, 995, 35, 0, 48),
BuildLineMapping(157, 7, 32, 1177, 45, 6, 12),
BuildLineMapping(205, 9, 1260, 50, 0, 12),
BuildLineMapping(218, 9, 13, 1356, 56, 12, 27),
BuildLineMapping(346, 12, 1754, 68, 0, 48),
BuildLineMapping(440, 15, 46, 2004, 78, 6, 8),
BuildLineMapping(501, 16, 31, 2384, 88, 6, 30),
BuildLineMapping(568, 17, 30, 2733, 97, 0, 10),
BuildLineMapping(601, 17, 63, 2815, 103, 0, 8),
BuildLineMapping(632, 17, 94, 2895, 109, 0, 1),
BuildLineMapping(639, 18, 3149, 118, 0, 15),
BuildLineMapping(680, 21, 3234, 124, 0, 1)
}
}
};
}
}
[Theory]
[MemberData(nameof(DesignTimeTagHelperTestData))]
public void TagHelpers_GenerateExpectedDesignTimeOutput(string testName,
string baseLineName,
IEnumerable<TagHelperDescriptor> tagHelperDescriptors,
List<LineMapping> expectedDesignTimePragmas)
{
// Act & Assert
RunTagHelperTest(testName,
baseLineName,
designTimeMode: true,
tagHelperDescriptors: tagHelperDescriptors,
expectedDesignTimePragmas: expectedDesignTimePragmas);
}
public static TheoryData RuntimeTimeTagHelperTestData
{
get
{
// Test resource name, expected TagHelperDescriptors
// Note: The baseline resource name is equivalent to the test resource name.
return new TheoryData<string, IEnumerable<TagHelperDescriptor>>
{
{ "SingleTagHelper", PAndInputTagHelperDescriptors },
{ "BasicTagHelpers", PAndInputTagHelperDescriptors },
{ "BasicTagHelpers.RemoveTagHelper", PAndInputTagHelperDescriptors },
{ "ContentBehaviorTagHelpers", ContentBehaviorTagHelperDescriptors },
{ "ComplexTagHelpers", PAndInputTagHelperDescriptors },
};
}
}
[Theory]
[MemberData(nameof(RuntimeTimeTagHelperTestData))]
public void TagHelpers_GenerateExpectedRuntimeOutput(string testName,
IEnumerable<TagHelperDescriptor> tagHelperDescriptors)
{
// Act & Assert
RunTagHelperTest(testName, tagHelperDescriptors: tagHelperDescriptors);
}
[Fact]
public void CSharpCodeGenerator_CorrectlyGeneratesMappings_ForRemoveTagHelperDirective()
{
@ -79,64 +241,9 @@ namespace Microsoft.AspNet.Razor.Test.Generator
RunTagHelperTest(testType, tagHelperDescriptors: tagHelperDescriptors);
}
[Theory]
[InlineData("SingleTagHelper")]
[InlineData("BasicTagHelpers")]
[InlineData("BasicTagHelpers.RemoveTagHelper")]
[InlineData("ComplexTagHelpers")]
public void TagHelpers_GenerateExpectedOutput(string testType)
{
// Arrange
var pFooPropertyInfo = typeof(TestType).GetProperty("Foo");
var inputTypePropertyInfo = typeof(TestType).GetProperty("Type");
var checkedPropertyInfo = typeof(TestType).GetProperty("Checked");
var tagHelperDescriptors = new TagHelperDescriptor[]
{
new TagHelperDescriptor("p",
"PTagHelper",
ContentBehavior.None,
new [] {
new TagHelperAttributeDescriptor("foo", pFooPropertyInfo)
}),
new TagHelperDescriptor("input",
"InputTagHelper",
ContentBehavior.None,
new TagHelperAttributeDescriptor[] {
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo)
}),
new TagHelperDescriptor("input",
"InputTagHelper2",
ContentBehavior.None,
new TagHelperAttributeDescriptor[] {
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
new TagHelperAttributeDescriptor("checked", checkedPropertyInfo)
})
};
// Act & Assert
RunTagHelperTest(testType, tagHelperDescriptors: tagHelperDescriptors);
}
[Fact]
public void TagHelpers_WithContentBehaviors_GenerateExpectedOutput()
{
// Arrange
var tagHelperDescriptors = new TagHelperDescriptor[]
{
new TagHelperDescriptor("modify", "ModifyTagHelper", ContentBehavior.Modify),
new TagHelperDescriptor("none", "NoneTagHelper", ContentBehavior.None),
new TagHelperDescriptor("append", "AppendTagHelper", ContentBehavior.Append),
new TagHelperDescriptor("prepend", "PrependTagHelper", ContentBehavior.Prepend),
new TagHelperDescriptor("replace", "ReplaceTagHelper", ContentBehavior.Replace),
};
// Act & Assert
RunTagHelperTest("ContentBehaviorTagHelpers", tagHelperDescriptors: tagHelperDescriptors);
}
private class TestType
{
public int Foo { get; set; }
public int Age { get; set; }
public string Type { get; set; }

View File

@ -0,0 +1,49 @@
namespace TestOutput
{
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using System;
using System.Threading.Tasks;
public class BasicTagHelpers
{
private static object @__o;
private void @__RazorDesignTimeHelpers__()
{
#pragma warning disable 219
string __tagHelperDirectiveSyntaxHelper = null;
__tagHelperDirectiveSyntaxHelper =
#line 1 "BasicTagHelpers.cshtml"
"something"
#line default
#line hidden
;
#pragma warning restore 219
}
#line hidden
private PTagHelper __PTagHelper = null;
private InputTagHelper __InputTagHelper = null;
private InputTagHelper2 __InputTagHelper2 = null;
#line hidden
public BasicTagHelpers()
{
}
#pragma warning disable 1998
public override async Task ExecuteAsync()
{
__PTagHelper = CreateTagHelper<PTagHelper>();
__PTagHelper = CreateTagHelper<PTagHelper>();
__InputTagHelper = CreateTagHelper<InputTagHelper>();
__InputTagHelper.Type = "text";
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
__InputTagHelper2.Type = __InputTagHelper.Type;
__InputTagHelper = CreateTagHelper<InputTagHelper>();
__InputTagHelper.Type = "checkbox";
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
__InputTagHelper2.Type = __InputTagHelper.Type;
__InputTagHelper2.Checked = true;
}
#pragma warning restore 1998
}
}

View File

@ -0,0 +1,133 @@
namespace TestOutput
{
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using System;
using System.Threading.Tasks;
public class ComplexTagHelpers
{
private static object @__o;
private void @__RazorDesignTimeHelpers__()
{
#pragma warning disable 219
string __tagHelperDirectiveSyntaxHelper = null;
__tagHelperDirectiveSyntaxHelper =
#line 1 "ComplexTagHelpers.cshtml"
"something"
#line default
#line hidden
;
#pragma warning restore 219
}
#line hidden
private PTagHelper __PTagHelper = null;
private InputTagHelper __InputTagHelper = null;
private InputTagHelper2 __InputTagHelper2 = null;
#line hidden
public ComplexTagHelpers()
{
}
#pragma warning disable 1998
public override async Task ExecuteAsync()
{
#line 3 "ComplexTagHelpers.cshtml"
if (true)
{
var checkbox = "checkbox";
#line default
#line hidden
__PTagHelper = CreateTagHelper<PTagHelper>();
#line 8 "ComplexTagHelpers.cshtml"
__o = DateTime.Now;
#line default
#line hidden
#line 10 "ComplexTagHelpers.cshtml"
#line default
#line hidden
#line 10 "ComplexTagHelpers.cshtml"
if (false)
{
#line default
#line hidden
__PTagHelper = CreateTagHelper<PTagHelper>();
__InputTagHelper = CreateTagHelper<InputTagHelper>();
__InputTagHelper.Type = "text";
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
__InputTagHelper2.Type = __InputTagHelper.Type;
#line 13 "ComplexTagHelpers.cshtml"
}
else
{
#line default
#line hidden
__PTagHelper = CreateTagHelper<PTagHelper>();
__InputTagHelper = CreateTagHelper<InputTagHelper>();
#line 16 "ComplexTagHelpers.cshtml"
__o = checkbox;
#line default
#line hidden
__InputTagHelper.Type = string.Empty;
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
__InputTagHelper2.Type = __InputTagHelper.Type;
__InputTagHelper2.Checked = true;
__InputTagHelper = CreateTagHelper<InputTagHelper>();
#line 17 "ComplexTagHelpers.cshtml"
__o = true ? "checkbox" : "anything";
#line default
#line hidden
__InputTagHelper.Type = string.Empty;
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
__InputTagHelper2.Type = __InputTagHelper.Type;
__InputTagHelper = CreateTagHelper<InputTagHelper>();
#line 18 "ComplexTagHelpers.cshtml"
if(true) {
#line default
#line hidden
#line 18 "ComplexTagHelpers.cshtml"
} else {
#line default
#line hidden
#line 18 "ComplexTagHelpers.cshtml"
}
#line default
#line hidden
__InputTagHelper.Type = string.Empty;
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
__InputTagHelper2.Type = __InputTagHelper.Type;
#line 19 "ComplexTagHelpers.cshtml"
}
#line default
#line hidden
#line 22 "ComplexTagHelpers.cshtml"
}
#line default
#line hidden
}
#pragma warning restore 1998
}
}

View File

@ -0,0 +1,45 @@
namespace TestOutput
{
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using System;
using System.Threading.Tasks;
public class ContentBehaviorTagHelpers
{
private static object @__o;
private void @__RazorDesignTimeHelpers__()
{
#pragma warning disable 219
string __tagHelperDirectiveSyntaxHelper = null;
__tagHelperDirectiveSyntaxHelper =
#line 1 "ContentBehaviorTagHelpers.cshtml"
"something"
#line default
#line hidden
;
#pragma warning restore 219
}
#line hidden
private ModifyTagHelper __ModifyTagHelper = null;
private NoneTagHelper __NoneTagHelper = null;
private AppendTagHelper __AppendTagHelper = null;
private PrependTagHelper __PrependTagHelper = null;
private ReplaceTagHelper __ReplaceTagHelper = null;
#line hidden
public ContentBehaviorTagHelpers()
{
}
#pragma warning disable 1998
public override async Task ExecuteAsync()
{
__ModifyTagHelper = CreateTagHelper<ModifyTagHelper>();
__NoneTagHelper = CreateTagHelper<NoneTagHelper>();
__AppendTagHelper = CreateTagHelper<AppendTagHelper>();
__PrependTagHelper = CreateTagHelper<PrependTagHelper>();
__ReplaceTagHelper = CreateTagHelper<ReplaceTagHelper>();
}
#pragma warning restore 1998
}
}

View File

@ -0,0 +1,38 @@
namespace TestOutput
{
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using System;
using System.Threading.Tasks;
public class SingleTagHelper
{
private static object @__o;
private void @__RazorDesignTimeHelpers__()
{
#pragma warning disable 219
string __tagHelperDirectiveSyntaxHelper = null;
__tagHelperDirectiveSyntaxHelper =
#line 1 "SingleTagHelper.cshtml"
"something"
#line default
#line hidden
;
#pragma warning restore 219
}
#line hidden
private PTagHelper __PTagHelper = null;
#line hidden
public SingleTagHelper()
{
}
#pragma warning disable 1998
public override async Task ExecuteAsync()
{
__PTagHelper = CreateTagHelper<PTagHelper>();
__PTagHelper.Age = 1337;
}
#pragma warning restore 1998
}
}

View File

@ -1,4 +1,4 @@
#pragma checksum "SingleTagHelper.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "61bf4cc89584cdbbac4478b202fe04797ddeb68a"
#pragma checksum "SingleTagHelper.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "a4d3eab407a97d5beebc7d3a319223ece03f3733"
namespace TestOutput
{
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
@ -27,8 +27,8 @@ namespace TestOutput
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("p");
__PTagHelper = CreateTagHelper<PTagHelper>();
__tagHelperExecutionContext.Add(__PTagHelper);
__PTagHelper.Foo = 1337;
__tagHelperExecutionContext.AddTagHelperAttribute("foo", __PTagHelper.Foo);
__PTagHelper.Age = 1337;
__tagHelperExecutionContext.AddTagHelperAttribute("age", __PTagHelper.Age);
__tagHelperExecutionContext.AddHtmlAttribute("class", "Hello World");
__tagHelperExecutionContext.Output = __tagHelperRunner.RunAsync(__tagHelperExecutionContext).Result;
WriteLiteral(__tagHelperExecutionContext.Output.GenerateStartTag());

View File

@ -1,3 +1,3 @@
@addtaghelper "something"
<p class="Hello World" foo="1337">Body of Tag</p>
<p class="Hello World" age="1337">Body of Tag</p>