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

Commit 7890a1b

Browse files
committed
Added TagHelperContext.UniqueId:
- The ID is created at view compilation time and is unique per TagHelperExecutionContext and thus per HTML element in the source for which Tag Helpers will run - #241
1 parent 170b7a7 commit 7890a1b

File tree

16 files changed

+305
-40
lines changed

16 files changed

+305
-40
lines changed

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,21 @@ public class TagHelperContext
1414
/// Instantiates a new <see cref="TagHelperContext"/>.
1515
/// </summary>
1616
/// <param name="allAttributes">Every attribute associated with the current HTML element.</param>
17-
public TagHelperContext([NotNull] IDictionary<string, object> allAttributes)
17+
/// <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)
1819
{
1920
AllAttributes = allAttributes;
21+
UniqueId = uniqueId;
2022
}
2123

2224
/// <summary>
2325
/// Every attribute associated with the current HTML element.
2426
/// </summary>
2527
public IDictionary<string, object> AllAttributes { get; private set; }
28+
29+
/// <summary>
30+
/// An identifier unique to the HTML element this context is for.
31+
/// </summary>
32+
public string UniqueId { get; private set; }
2633
}
2734
}

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,22 @@ public class TagHelperExecutionContext
1717
/// Instantiates a new <see cref="TagHelperExecutionContext"/>.
1818
/// </summary>
1919
/// <param name="tagName">The HTML tag name in the Razor source.</param>
20-
public TagHelperExecutionContext([NotNull] string tagName)
20+
public TagHelperExecutionContext([NotNull] string tagName, [NotNull] string uniqueId)
2121
{
2222
AllAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
2323
HTMLAttributes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
2424
_tagHelpers = new List<ITagHelper>();
2525
TagName = tagName;
26+
UniqueId = uniqueId;
27+
}
28+
29+
/// <summary>
30+
/// Internal for testing purposes only.
31+
/// </summary>
32+
internal TagHelperExecutionContext([NotNull] string tagName)
33+
: this(tagName, string.Empty)
34+
{
35+
2636
}
2737

2838
/// <summary>
@@ -35,6 +45,11 @@ public TagHelperExecutionContext([NotNull] string tagName)
3545
/// </summary>
3646
public IDictionary<string, object> AllAttributes { get; private set; }
3747

48+
/// <summary>
49+
/// An identifier unique to the HTML element this context is for.
50+
/// </summary>
51+
public string UniqueId { get; private set; }
52+
3853
/// <summary>
3954
/// <see cref="ITagHelper"/>s that should be run.
4055
/// </summary>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public async Task<TagHelperOutput> RunAsync([NotNull] TagHelperExecutionContext
3737

3838
private async Task<TagHelperOutput> RunAsyncCore(TagHelperExecutionContext executionContext, string outputContent)
3939
{
40-
var tagHelperContext = new TagHelperContext(executionContext.AllAttributes);
40+
var tagHelperContext = new TagHelperContext(executionContext.AllAttributes, executionContext.UniqueId);
4141
var tagHelperOutput = new TagHelperOutput(executionContext.TagName,
4242
executionContext.HTMLAttributes,
4343
outputContent);

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ public TagHelperScopeManager()
2525
/// Starts a <see cref="TagHelperExecutionContext"/> scope.
2626
/// </summary>
2727
/// <param name="tagName">The HTML tag name that the scope is associated with.</param>
28+
/// <param name="uniqueId">An identifier unique to the HTML element this scope is for.</param>
2829
/// <returns>A <see cref="TagHelperExecutionContext"/> to use.</returns>
29-
public TagHelperExecutionContext Begin(string tagName)
30+
public TagHelperExecutionContext Begin(string tagName, string uniqueId)
3031
{
31-
var executionContext = new TagHelperExecutionContext(tagName);
32+
var executionContext = new TagHelperExecutionContext(tagName, uniqueId);
3233

3334
_executionScopes.Push(executionContext);
3435

src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpTagHelperCodeRenderer.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,26 @@ private void RenderBeginTagHelperScope(string tagName)
135135
_writer.WriteStartAssignment(ExecutionContextVariableName)
136136
.WriteStartInstanceMethodInvocation(ScopeManagerVariableName,
137137
_tagHelperContext.ScopeManagerBeginMethodName);
138+
139+
// Assign a unique ID for this instance of the source HTML tag. This must be unique
140+
// per call site, e.g. if the tag is on the view twice, there should be two IDs.
138141
_writer.WriteStringLiteral(tagName)
142+
.WriteParameterSeparator()
143+
.WriteStringLiteral(GenerateUniqueId())
139144
.WriteEndMethodInvocation();
140145
}
141146

147+
/// <summary>
148+
/// Generates a unique ID for an HTML element.
149+
/// </summary>
150+
/// <returns>
151+
/// A globally unique ID.
152+
/// </returns>
153+
protected virtual string GenerateUniqueId()
154+
{
155+
return Guid.NewGuid().ToString("N");
156+
}
157+
142158
private void RenderTagHelpersCreation(TagHelperChunk chunk)
143159
{
144160
var tagHelperDescriptors = chunk.Descriptors;

test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperScopeManagerTest.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public void Begin_CreatesContextWithAppropriateTagName()
1616
var scopeManager = new TagHelperScopeManager();
1717

1818
// Act
19-
var executionContext = scopeManager.Begin("p");
19+
var executionContext = scopeManager.Begin("p", string.Empty);
2020

2121
// Assert
2222
Assert.Equal("p", executionContext.TagName);
@@ -29,8 +29,8 @@ public void Begin_CanNest()
2929
var scopeManager = new TagHelperScopeManager();
3030

3131
// Act
32-
var executionContext = scopeManager.Begin("p");
33-
executionContext = scopeManager.Begin("div");
32+
var executionContext = scopeManager.Begin("p", string.Empty);
33+
executionContext = scopeManager.Begin("div", string.Empty);
3434

3535
// Assert
3636
Assert.Equal("div", executionContext.TagName);
@@ -43,8 +43,8 @@ public void End_ReturnsParentExecutionContext()
4343
var scopeManager = new TagHelperScopeManager();
4444

4545
// Act
46-
var executionContext = scopeManager.Begin("p");
47-
executionContext = scopeManager.Begin("div");
46+
var executionContext = scopeManager.Begin("p", string.Empty);
47+
executionContext = scopeManager.Begin("div", string.Empty);
4848
executionContext = scopeManager.End();
4949

5050
// Assert
@@ -58,8 +58,8 @@ public void End_ReturnsNullIfNoNestedContext()
5858
var scopeManager = new TagHelperScopeManager();
5959

6060
// Act
61-
var executionContext = scopeManager.Begin("p");
62-
executionContext = scopeManager.Begin("div");
61+
var executionContext = scopeManager.Begin("p", string.Empty);
62+
executionContext = scopeManager.Begin("div", string.Empty);
6363
executionContext = scopeManager.End();
6464
executionContext = scopeManager.End();
6565

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Microsoft.AspNet.Razor.Generator;
4+
using Microsoft.AspNet.Razor.Generator.Compiler;
5+
using Microsoft.AspNet.Razor.Generator.Compiler.CSharp;
6+
using Microsoft.AspNet.Razor.TagHelpers;
7+
using Xunit;
8+
9+
namespace Microsoft.AspNet.Razor.Test.Generator
10+
{
11+
public class CSharpTagHelperRenderingUnitTest
12+
{
13+
[Fact]
14+
public void CreatesAUniqueIdForSingleTagHelperChunk()
15+
{
16+
// Arrange
17+
var chunk = CreateTagHelperChunk("div", new[] {
18+
new TagHelperDescriptor("div", "DivTagHelper", "FakeAssemblyName", ContentBehavior.None)
19+
});
20+
var codeRenderer = CreateCodeRenderer();
21+
22+
// Act
23+
codeRenderer.RenderTagHelper(chunk);
24+
25+
// Assert
26+
Assert.Equal(1, codeRenderer.GenerateUniqueIdCount);
27+
}
28+
29+
[Fact]
30+
public void UsesTheSameUniqueIdForTagHelperChunkWithMultipleTagHelpers()
31+
{
32+
// Arrange
33+
var chunk = CreateTagHelperChunk("div", new[] {
34+
new TagHelperDescriptor("div", "DivTagHelper", "FakeAssemblyName", ContentBehavior.None),
35+
new TagHelperDescriptor("div", "Div2TagHelper", "FakeAssemblyName", ContentBehavior.None)
36+
});
37+
var codeRenderer = CreateCodeRenderer();
38+
39+
// Act
40+
codeRenderer.RenderTagHelper(chunk);
41+
42+
// Assert
43+
Assert.Equal(1, codeRenderer.GenerateUniqueIdCount);
44+
}
45+
46+
[Fact]
47+
public void UsesDifferentUniqueIdForMultipleTagHelperChunksForSameTagHelper()
48+
{
49+
// Arrange
50+
var chunk1 = CreateTagHelperChunk("div", new[] {
51+
new TagHelperDescriptor("div", "DivTagHelper", "FakeAssemblyName", ContentBehavior.None)
52+
});
53+
var chunk2 = CreateTagHelperChunk("div", new[] {
54+
new TagHelperDescriptor("div", "DivTagHelper", "FakeAssemblyName", ContentBehavior.None)
55+
});
56+
var codeRenderer = CreateCodeRenderer();
57+
58+
// Act
59+
codeRenderer.RenderTagHelper(chunk1);
60+
codeRenderer.RenderTagHelper(chunk2);
61+
62+
// Assert
63+
Assert.Equal(2, codeRenderer.GenerateUniqueIdCount);
64+
}
65+
66+
[Fact]
67+
public void UsesDifferentUniqueIdForNestedTagHelperChunksForSameTagHelper()
68+
{
69+
// Arrange
70+
var parentChunk = CreateTagHelperChunk("div", new[] {
71+
new TagHelperDescriptor("div", "DivTagHelper", "FakeAssemblyName", ContentBehavior.None)
72+
});
73+
var childChunk = CreateTagHelperChunk("div", new[] {
74+
new TagHelperDescriptor("div", "DivTagHelper", "FakeAssemblyName", ContentBehavior.None)
75+
});
76+
parentChunk.Children.Add(childChunk);
77+
var codeRenderer = CreateCodeRenderer();
78+
79+
// Act
80+
codeRenderer.RenderTagHelper(parentChunk);
81+
82+
// Assert
83+
Assert.Equal(2, codeRenderer.GenerateUniqueIdCount);
84+
}
85+
86+
[Fact]
87+
public void UsesDifferentUniqueIdForMultipleTagHelperChunksForDifferentTagHelpers()
88+
{
89+
// Arrange
90+
var divChunk = CreateTagHelperChunk("div", new[] {
91+
new TagHelperDescriptor("div", "DivTagHelper", "FakeAssemblyName", ContentBehavior.None)
92+
});
93+
var spanChunk = CreateTagHelperChunk("span", new[] {
94+
new TagHelperDescriptor("span", "SpanTagHelper", "FakeAssemblyName", ContentBehavior.None)
95+
});
96+
var codeRenderer = CreateCodeRenderer();
97+
98+
// Act
99+
codeRenderer.RenderTagHelper(divChunk);
100+
codeRenderer.RenderTagHelper(spanChunk);
101+
102+
// Assert
103+
Assert.Equal(2, codeRenderer.GenerateUniqueIdCount);
104+
}
105+
106+
[Fact]
107+
public void UsesCorrectUniqueIdForMultipleTagHelperChunksSomeWithSameSameTagHelpersSomeWithDifferentTagHelpers()
108+
{
109+
// Arrange
110+
var chunk1 = CreateTagHelperChunk("div", new[] {
111+
new TagHelperDescriptor("div", "DivTagHelper", "FakeAssemblyName", ContentBehavior.None),
112+
new TagHelperDescriptor("div", "Div2TagHelper", "FakeAssemblyName", ContentBehavior.None)
113+
});
114+
var chunk2 = CreateTagHelperChunk("span", new[] {
115+
new TagHelperDescriptor("span", "SpanTagHelper", "FakeAssemblyName", ContentBehavior.None)
116+
});
117+
var chunk3 = CreateTagHelperChunk("span", new[] {
118+
new TagHelperDescriptor("span", "SpanTagHelper", "FakeAssemblyName", ContentBehavior.None),
119+
new TagHelperDescriptor("span", "Span2TagHelper", "FakeAssemblyName", ContentBehavior.None)
120+
});
121+
var codeRenderer = CreateCodeRenderer();
122+
123+
// Act
124+
codeRenderer.RenderTagHelper(chunk1);
125+
codeRenderer.RenderTagHelper(chunk2);
126+
codeRenderer.RenderTagHelper(chunk3);
127+
128+
// Assert
129+
Assert.Equal(3, codeRenderer.GenerateUniqueIdCount);
130+
}
131+
132+
private static TagHelperChunk CreateTagHelperChunk(string tagName, IEnumerable<TagHelperDescriptor> tagHelperDescriptors)
133+
{
134+
return new TagHelperChunk
135+
{
136+
TagName = tagName,
137+
Descriptors = tagHelperDescriptors,
138+
Children = new List<Chunk>(),
139+
Attributes = new Dictionary<string, Chunk>()
140+
};
141+
}
142+
143+
private static TrackingUniqueIdsTagHelperCodeRenderer CreateCodeRenderer()
144+
{
145+
var writer = new CSharpCodeWriter();
146+
var codeBuilderContext = CreateContext();
147+
var visitor = new CSharpCodeVisitor(writer, codeBuilderContext);
148+
var codeRenderer = new TrackingUniqueIdsTagHelperCodeRenderer(
149+
visitor,
150+
writer,
151+
codeBuilderContext);
152+
visitor.TagHelperRenderer = codeRenderer;
153+
return codeRenderer;
154+
}
155+
156+
private static CodeBuilderContext CreateContext()
157+
{
158+
return new CodeBuilderContext(
159+
new CodeGeneratorContext(
160+
new RazorEngineHost(new CSharpRazorCodeLanguage()),
161+
"MyClass",
162+
"MyNamespace",
163+
string.Empty,
164+
shouldGenerateLinePragmas: true));
165+
}
166+
167+
private class TrackingUniqueIdsTagHelperCodeRenderer : CSharpTagHelperCodeRenderer
168+
{
169+
public TrackingUniqueIdsTagHelperCodeRenderer(
170+
IChunkVisitor bodyVisitor,
171+
CSharpCodeWriter writer,
172+
CodeBuilderContext context)
173+
: base(bodyVisitor, writer, context)
174+
{
175+
176+
}
177+
178+
protected override string GenerateUniqueId()
179+
{
180+
GenerateUniqueIdCount++;
181+
return "test";
182+
}
183+
184+
public int GenerateUniqueIdCount { get; private set; }
185+
}
186+
}
187+
}

test/Microsoft.AspNet.Razor.Test/Generator/TagHelperAttributeValueCodeRendererTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public override CodeBuilder DecorateCodeBuilder(CodeBuilder incomingBuilder, Cod
6565
}
6666
}
6767

68-
private class AttributeCodeGeneratorReplacingCodeBuilder : CSharpCodeBuilder
68+
private class AttributeCodeGeneratorReplacingCodeBuilder : TestCSharpCodeBuilder
6969
{
7070
public AttributeCodeGeneratorReplacingCodeBuilder(CodeBuilderContext context)
7171
: base(context)

0 commit comments

Comments
 (0)