diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorIntermediateNodeWriter.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorIntermediateNodeWriter.cs
index 35128dafb2..106efb70cd 100644
--- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorIntermediateNodeWriter.cs
+++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorIntermediateNodeWriter.cs
@@ -246,6 +246,11 @@ namespace Microsoft.AspNetCore.Blazor.Razor
.WriteEndMethodInvocation();
}
+ if (isComponent && nextTag.Attributes.Count > 0)
+ {
+ ThrowTemporaryComponentSyntaxError(node, nextTag, tagNameOriginalCase);
+ }
+
foreach (var attribute in nextTag.Attributes)
{
WriteAttribute(codeWriter, attribute.Key, attribute.Value);
@@ -316,6 +321,13 @@ namespace Microsoft.AspNetCore.Blazor.Razor
}
}
+ private void ThrowTemporaryComponentSyntaxError(HtmlContentIntermediateNode node, HtmlTagToken tag, string componentName)
+ => throw new RazorCompilerException(
+ $"Wrong syntax for '{tag.Attributes[0].Key}' on '{componentName}': As a temporary " +
+ $"limitation, component attributes must be expressed with C# syntax. For example, " +
+ $"SomeParam=@(\"Some value\") is allowed, but SomeParam=\"Some value\" is not.",
+ CalculateSourcePosition(node.Source, tag.Position));
+
private SourceSpan? CalculateSourcePosition(
SourceSpan? razorTokenPosition,
TextPosition htmlNodePosition)
diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorCompilerTest.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorCompilerTest.cs
index ef5745aa00..f552281240 100644
--- a/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorCompilerTest.cs
+++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorCompilerTest.cs
@@ -381,40 +381,71 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
// Arrange/Act
var testComponentTypeName = typeof(TestComponent).FullName.Replace('+', '.');
var testObjectTypeName = typeof(SomeType).FullName.Replace('+', '.');
+ // TODO: Once we have the improved component tooling and can allow syntax
+ // like StringProperty="My string" or BoolProperty=true, update this
+ // test to use that syntax.
var component = CompileToComponent($"");
var frames = GetRenderTree(component);
// Assert
- // TODO: Fix this.
- // * Currently the attribute names are lowercased if they were
- // parsed by AngleSharp as HTML, and left in their original case if they
- // were parsed by the Razor compiler as a C# expression. They should all
- // retain their original case when the target element represents a component.
- // * Similarly, unquoted values are interpreted as strings if they were parsed
- // by AngleSharp (e.g., intproperty=123 passes a string). The values should
- // always be treated as C# expressions if the target represents a component.
- // This problem will probably go away on its own when we have new component
- // tooling.
Assert.Collection(frames,
- frame => AssertFrame.Component(frame, 4, 0),
- frame => AssertFrame.Attribute(frame, "intproperty", "123", 1),
- frame => AssertFrame.Attribute(frame, "stringproperty", "My string", 2),
+ frame => AssertFrame.Component(frame, 5, 0),
+ frame => AssertFrame.Attribute(frame, "IntProperty", 123, 1),
+ frame => AssertFrame.Attribute(frame, "BoolProperty", true, 2),
+ frame => AssertFrame.Attribute(frame, "StringProperty", "My string", 3),
frame =>
{
- AssertFrame.Attribute(frame, "ObjectProperty");
+ AssertFrame.Attribute(frame, "ObjectProperty", 4);
Assert.IsType(frame.AttributeValue);
});
}
+ [Fact]
+ public void TemporaryComponentSyntaxRejectsParametersExpressedAsPlainHtmlAttributes()
+ {
+ // This is a temporary syntax restriction. Currently you can write:
+ //
+ // ... but are *not* allowed to write:
+ //
+ // This is because until we get the improved taghelper-based tooling,
+ // we're using AngleSharp to parse the plain HTML attributes, and it
+ // suffers from limitations:
+ // * Loses the casing of attribute names (MyParam becomes myparam)
+ // * Doesn't recognize MyBool=true as an bool (becomes mybool="true"),
+ // plus equivalent for other primitives like enum values
+ // So to avoid people getting runtime errors, we're currently imposing
+ // the compile-time restriction that component params have to be given
+ // as C# expressions, e.g., MyBool=@true and MyString=@("Hello")
+
+ // Arrange/Act
+ var result = CompileToCSharp(
+ $"Line 1\n" +
+ $"Some text ");
+
+ // Assert
+ Assert.Collection(result.Diagnostics,
+ item =>
+ {
+ Assert.Equal(RazorCompilerDiagnostic.DiagnosticType.Error, item.Type);
+ Assert.StartsWith($"Wrong syntax for 'myparam' on 'c:MyComponent': As a temporary " +
+ $"limitation, component attributes must be expressed with C# syntax. For " +
+ $"example, SomeParam=@(\"Some value\") is allowed, but SomeParam=\"Some value\" " +
+ $"is not.", item.Message);
+ Assert.Equal(2, item.Line);
+ Assert.Equal(11, item.Column);
+ });
+ }
+
[Fact]
public void CanIncludeChildrenInComponents()
{
// Arrange/Act
var testComponentTypeName = typeof(TestComponent).FullName.Replace('+', '.');
- var component = CompileToComponent($"" +
+ var component = CompileToComponent($"" +
$"Some text" +
$"Nested text" +
$"");
@@ -423,7 +454,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
// Assert: component frames are correct
Assert.Collection(frames,
frame => AssertFrame.Component(frame, 3, 0),
- frame => AssertFrame.Attribute(frame, "attr", "abc", 1),
+ frame => AssertFrame.Attribute(frame, "MyAttr", "abc", 1),
frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, 2));
// Assert: Captured ChildContent frames are correct