Adds support for 'Context' parameters on components (#1470)
Adds support for 'Context' parameters on components
This change allows you to set the parameter name of a parameterized child content by using the `Context` attribute on the component. The `Context` attribute will be defined (and shown by completion) when the component has one or more declared parameterized (`RenderFragment<>`) child content parameters.
This is nice for cases where you are using implicit child content:
```
<ol>
<Repeater Items="People" Context="person">
<li>@person.FirstName</li>
</Repeater>
</ol>
```
or, when you have multiple child content elements and want them all to have the same parameter name:
```
<MyComponent Items="People" Context="person">
<ChildContent1><div>@person.FirstName</div></ChildContent1>
<ChildContent2><div>@person.LastName</div></ChildContent2>
</Repeater>
```
The parameter name can be overridden by using the `Context` parameter on the child content element:
```
<MyComponent Items="People" Context="person">
<ChildContent1 Context="item"><div>@item.FirstName</div></ChildContent1>
<ChildContent2><div>@person.LastName</div></ChildContent2>
</Repeater>
```
If the component defines a `Context` parameter already then we won't synthesize one - your component's parameter will work exactly as it did before this feature.
This commit is contained in:
parent
984fabb89f
commit
6f383a0f0f
|
|
@ -308,6 +308,16 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
var attributesText = string.Join(", ", attributes.Select(a => $"'{a.Name}'"));
|
||||
return RazorDiagnostic.Create(GenericComponentTypeInferenceUnderspecified, source ?? SourceSpan.Undefined, component.TagName, attributesText);
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor ChildContentHasInvalidParameterOnComponent =
|
||||
new RazorDiagnosticDescriptor(
|
||||
"BL10002",
|
||||
() => "Invalid parameter name. The parameter name attribute '{0}' on component '{1}' can only include literal text.",
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic Create_ChildContentHasInvalidParameterOnComponent(SourceSpan? source, string attribute, string element)
|
||||
{
|
||||
return RazorDiagnostic.Create(ChildContentHasInvalidParameterOnComponent, source ?? SourceSpan.Undefined, attribute, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,12 +33,24 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
public static readonly string TagHelperKind = "Blazor.ChildContent";
|
||||
|
||||
public static readonly string ParameterNameBoundAttributeKind = "Blazor.ChildContentParameterName";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the synthesized attribute used to set a child content parameter.
|
||||
/// </summary>
|
||||
public static readonly string ParameterAttributeName = "Context";
|
||||
|
||||
/// <summary>
|
||||
/// The default name of the child content parameter (unless set by a Context attribute).
|
||||
/// </summary>
|
||||
public static readonly string DefaultParameterName = "context";
|
||||
}
|
||||
|
||||
public static class Component
|
||||
{
|
||||
public static readonly string ChildContentKey = "Blazor.ChildContent";
|
||||
|
||||
public static readonly string ChildContentParameterNameKey = "Blazor.ChildContentParameterName";
|
||||
|
||||
public static readonly string DelegateSignatureKey = "Blazor.DelegateSignature";
|
||||
|
||||
public static readonly string WeaklyTypedKey = "Blazor.IsWeaklyTyped";
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
|
||||
public bool IsParameterized => BoundAttribute?.IsParameterizedChildContentProperty() ?? false;
|
||||
|
||||
public string ParameterName { get; set; } = "context";
|
||||
public string ParameterName { get; set; }
|
||||
|
||||
public string TypeName { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
|
||||
public TagHelperDescriptor Component { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the child content parameter name (null if unset) that was applied at the component level.
|
||||
/// </summary>
|
||||
public string ChildContentParameterName { get; set; }
|
||||
|
||||
public IEnumerable<ComponentTypeArgumentExtensionNode> TypeArguments => Children.OfType<ComponentTypeArgumentExtensionNode>();
|
||||
|
||||
public string TagName { get; set; }
|
||||
|
|
|
|||
|
|
@ -80,6 +80,13 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
var visitor = new ComponentRewriteVisitor(component);
|
||||
visitor.Visit(node);
|
||||
|
||||
// Fixup the parameter names of child content elements. We can't do this during the rewrite
|
||||
// because we see the nodes in the wrong order.
|
||||
foreach (var childContent in component.ChildContents)
|
||||
{
|
||||
childContent.ParameterName = childContent.ParameterName ?? component.ChildContentParameterName ?? BlazorMetadata.ChildContent.DefaultParameterName;
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
|
|
@ -234,7 +241,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
}
|
||||
else if (child is TagHelperPropertyIntermediateNode property)
|
||||
{
|
||||
if (property.BoundAttribute.Kind == BlazorMetadata.ChildContent.TagHelperKind)
|
||||
if (property.BoundAttribute.IsChildContentParameterNameProperty())
|
||||
{
|
||||
// Check for each child content with a parameter name, that the parameter name is specified
|
||||
// with literal text. For instance, the following is not allowed and should generate a diagnostic.
|
||||
|
|
@ -347,6 +354,25 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
return;
|
||||
}
|
||||
|
||||
// Another special case here -- this might be a 'Context' parameter, which specifies the name
|
||||
// for lambda parameter for parameterized child content
|
||||
if (node.BoundAttribute.IsChildContentParameterNameProperty())
|
||||
{
|
||||
// Check for each child content with a parameter name, that the parameter name is specified
|
||||
// with literal text. For instance, the following is not allowed and should generate a diagnostic.
|
||||
//
|
||||
// <MyComponent Context="@Foo()">...</MyComponent>
|
||||
if (TryGetAttributeStringContent(node, out var parameterName))
|
||||
{
|
||||
_component.ChildContentParameterName = parameterName;
|
||||
return;
|
||||
}
|
||||
|
||||
// The parameter name is invalid.
|
||||
_component.Diagnostics.Add(BlazorDiagnosticFactory.Create_ChildContentHasInvalidParameterOnComponent(node.Source, node.AttributeName, _component.TagName));
|
||||
return;
|
||||
}
|
||||
|
||||
_children.Add(new ComponentAttributeExtensionNode(node));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -129,8 +129,16 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
CreateProperty(builder, property.property, property.kind);
|
||||
}
|
||||
|
||||
var descriptor = builder.Build();
|
||||
if (builder.BoundAttributes.Any(a => a.IsParameterizedChildContentProperty()) &&
|
||||
!builder.BoundAttributes.Any(a => string.Equals(a.Name, BlazorMetadata.ChildContent.ParameterAttributeName, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
// If we have any parameterized child content parameters, synthesize a 'Context' parameter to be
|
||||
// able to set the variable name (for all child content). If the developer defined a 'Context' parameter
|
||||
// already, then theirs wins.
|
||||
CreateContextParameter(builder, childContentName: null);
|
||||
}
|
||||
|
||||
var descriptor = builder.Build();
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
|
|
@ -257,12 +265,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
{
|
||||
// For child content attributes with a parameter, synthesize an attribute that allows you to name
|
||||
// the parameter.
|
||||
builder.BindAttribute(b =>
|
||||
{
|
||||
b.Name = "Context";
|
||||
b.TypeName = typeof(string).FullName;
|
||||
b.Documentation = string.Format(Resources.ChildContentParameterName_Documentation, attribute.Name);
|
||||
});
|
||||
CreateContextParameter(builder, attribute.Name);
|
||||
}
|
||||
|
||||
var descriptor = builder.Build();
|
||||
|
|
@ -270,6 +273,25 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
return descriptor;
|
||||
}
|
||||
|
||||
private void CreateContextParameter(TagHelperDescriptorBuilder builder, string childContentName)
|
||||
{
|
||||
builder.BindAttribute(b =>
|
||||
{
|
||||
b.Name = BlazorMetadata.ChildContent.ParameterAttributeName;
|
||||
b.TypeName = typeof(string).FullName;
|
||||
b.Metadata.Add(BlazorMetadata.Component.ChildContentParameterNameKey, bool.TrueString);
|
||||
|
||||
if (childContentName == null)
|
||||
{
|
||||
b.Documentation = Resources.ChildContentParameterName_TopLevelDocumentation;
|
||||
}
|
||||
else
|
||||
{
|
||||
b.Documentation = string.Format(Resources.ChildContentParameterName_Documentation, childContentName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Does a walk up the inheritance chain to determine the set of parameters by using
|
||||
// a dictionary keyed on property name.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Specifies the parameter name for the '{0}' lambda expression..
|
||||
/// Looks up a localized string similar to Specifies the parameter name for the '{0}' child content expression..
|
||||
/// </summary>
|
||||
internal static string ChildContentParameterName_Documentation {
|
||||
get {
|
||||
|
|
@ -114,6 +114,15 @@ namespace Microsoft.AspNetCore.Blazor.Razor {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Specifies the parameter name for all child content expressions..
|
||||
/// </summary>
|
||||
internal static string ChildContentParameterName_TopLevelDocumentation {
|
||||
get {
|
||||
return ResourceManager.GetString("ChildContentParameterName_TopLevelDocumentation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Specifies the type of the type parameter {0} for the {1} component..
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -133,7 +133,10 @@
|
|||
<value>Specifies a format to convert the value specified by the corresponding bind attribute. For example: <code>format-value="..."</code> will apply a format string to the value specified in <code>bind-value-...</code>. The format string can currently only be used with expressions of type <code>DateTime</code>.</value>
|
||||
</data>
|
||||
<data name="ChildContentParameterName_Documentation" xml:space="preserve">
|
||||
<value>Specifies the parameter name for the '{0}' lambda expression.</value>
|
||||
<value>Specifies the parameter name for the '{0}' child content expression.</value>
|
||||
</data>
|
||||
<data name="ChildContentParameterName_TopLevelDocumentation" xml:space="preserve">
|
||||
<value>Specifies the parameter name for all child content expressions.</value>
|
||||
</data>
|
||||
<data name="ComponentTypeParameter_Documentation" xml:space="preserve">
|
||||
<value>Specifies the type of the type parameter {0} for the {1} component.</value>
|
||||
|
|
|
|||
|
|
@ -78,6 +78,25 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
string.Equals(value, bool.TrueString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether the property is a child content property. Properties are
|
||||
/// considered child content if they have the type <c>RenderFragment</c> or <c>RenderFragment{T}</c>.
|
||||
/// </summary>
|
||||
/// <param name="attribute">The <see cref="BoundAttributeDescriptorBuilder"/>.</param>
|
||||
/// <returns>Returns <c>true</c> if the property is child content, otherwise <c>false</c>.</returns>
|
||||
public static bool IsChildContentProperty(this BoundAttributeDescriptorBuilder attribute)
|
||||
{
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
var key = BlazorMetadata.Component.ChildContentKey;
|
||||
return
|
||||
attribute.Metadata.TryGetValue(key, out var value) &&
|
||||
string.Equals(value, bool.TrueString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether the property is a parameterized child content property. Properties are
|
||||
/// considered parameterized child content if they have the type <c>RenderFragment{T}</c> (for some T).
|
||||
|
|
@ -94,5 +113,44 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
return attribute.IsChildContentProperty() &&
|
||||
!string.Equals(attribute.TypeName, BlazorApi.RenderFragment.FullTypeName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether the property is a parameterized child content property. Properties are
|
||||
/// considered parameterized child content if they have the type <c>RenderFragment{T}</c> (for some T).
|
||||
/// </summary>
|
||||
/// <param name="attribute">The <see cref="BoundAttributeDescriptor"/>.</param>
|
||||
/// <returns>Returns <c>true</c> if the property is parameterized child content, otherwise <c>false</c>.</returns>
|
||||
public static bool IsParameterizedChildContentProperty(this BoundAttributeDescriptorBuilder attribute)
|
||||
{
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
return attribute.IsChildContentProperty() &&
|
||||
!string.Equals(attribute.TypeName, BlazorApi.RenderFragment.FullTypeName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether the property is used to specify the name of the parameter
|
||||
/// for a parameterized child content property.
|
||||
/// </summary>
|
||||
/// <param name="attribute">The <see cref="BoundAttributeDescriptor"/>.</param>
|
||||
/// <returns>
|
||||
/// Returns <c>true</c> if the property specifies the name of a parameter for a parameterized child content,
|
||||
/// otherwise <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsChildContentParameterNameProperty(this BoundAttributeDescriptor attribute)
|
||||
{
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
var key = BlazorMetadata.Component.ChildContentParameterNameKey;
|
||||
return
|
||||
attribute.Metadata.TryGetValue(key, out var value) &&
|
||||
string.Equals(value, bool.TrueString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -344,6 +344,75 @@ namespace Test
|
|||
frame => AssertFrame.Text(frame, "Bye!", 11));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_MultipleChildContent_ContextParameterOnComponent()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderMultipleChildContent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
@addTagHelper *, TestAssembly
|
||||
<RenderMultipleChildContent Name=""billg"" Value=""HI"" Context=""item"">
|
||||
<Header><div>@item.ToLowerInvariant()</div></Header>
|
||||
<ChildContent Context=""Context"">Some @Context.ToLowerInvariant() Content</ChildContent>
|
||||
<Footer>Bye!</Footer>
|
||||
</RenderMultipleChildContent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderMultipleChildContent", 6, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Name", "billg", 1),
|
||||
frame => AssertFrame.Attribute(frame, "Value", "HI", 2),
|
||||
frame => AssertFrame.Attribute(frame, "Header", typeof(RenderFragment<string>), 3),
|
||||
frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, typeof(RenderFragment<string>), 6),
|
||||
frame => AssertFrame.Attribute(frame, "Footer", typeof(RenderFragment), 10),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 4),
|
||||
frame => AssertFrame.Text(frame, "billg", 5),
|
||||
frame => AssertFrame.Text(frame, "Some ", 7),
|
||||
frame => AssertFrame.Text(frame, "hi", 8),
|
||||
frame => AssertFrame.Text(frame, " Content", 9),
|
||||
frame => AssertFrame.Text(frame, "Bye!", 11));
|
||||
}
|
||||
|
||||
// Verifies that our check for reuse of parameter names isn't too aggressive.
|
||||
[Fact]
|
||||
public void Render_MultipleChildContent_ContextParameterOnComponent_SetsSameName()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderMultipleChildContent);
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
@addTagHelper *, TestAssembly
|
||||
<RenderMultipleChildContent Name=""billg"" Value=""HI"" Context=""item"">
|
||||
<Header><div>@item.ToLowerInvariant()</div></Header>
|
||||
<ChildContent Context=""item"">Some @item.ToLowerInvariant() Content</ChildContent>
|
||||
<Footer>Bye!</Footer>
|
||||
</RenderMultipleChildContent>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.RenderMultipleChildContent", 6, 0),
|
||||
frame => AssertFrame.Attribute(frame, "Name", "billg", 1),
|
||||
frame => AssertFrame.Attribute(frame, "Value", "HI", 2),
|
||||
frame => AssertFrame.Attribute(frame, "Header", typeof(RenderFragment<string>), 3),
|
||||
frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, typeof(RenderFragment<string>), 6),
|
||||
frame => AssertFrame.Attribute(frame, "Footer", typeof(RenderFragment), 10),
|
||||
frame => AssertFrame.Element(frame, "div", 2, 4),
|
||||
frame => AssertFrame.Text(frame, "billg", 5),
|
||||
frame => AssertFrame.Text(frame, "Some ", 7),
|
||||
frame => AssertFrame.Text(frame, "hi", 8),
|
||||
frame => AssertFrame.Text(frame, " Content", 9),
|
||||
frame => AssertFrame.Text(frame, "Bye!", 11));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_ChildContent_AttributeAndBody_ProducesDiagnostic()
|
||||
{
|
||||
|
|
@ -492,5 +561,25 @@ Some Content
|
|||
"element 'ChildContent' of component 'RenderChildContentString'. Specify the parameter name like: '<ChildContent Context=\"another_name\"> to resolve the ambiguity",
|
||||
diagnostic.GetMessage());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_ChildContent_ContextParameterNameOnComponent_Invalid_ProducesDiagnostic()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(RenderChildContentStringComponent);
|
||||
|
||||
// Act
|
||||
var generated = CompileToCSharp(@"
|
||||
@addTagHelper *, TestAssembly
|
||||
<RenderChildContentString Context=""@Foo()"">
|
||||
</RenderChildContentString>");
|
||||
|
||||
// Assert
|
||||
var diagnostic = Assert.Single(generated.Diagnostics);
|
||||
Assert.Same(BlazorDiagnosticFactory.ChildContentHasInvalidParameterOnComponent.Id, diagnostic.Id);
|
||||
Assert.Equal(
|
||||
"Invalid parameter name. The parameter name attribute 'Context' on component 'RenderChildContentString' can only include literal text.",
|
||||
diagnostic.GetMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -671,6 +671,42 @@ namespace Test
|
|||
CompileToAssembly(generated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChildComponent_WithGenericChildContent_SetsParameterNameOnComponent()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using Microsoft.AspNetCore.Blazor;
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : BlazorComponent
|
||||
{
|
||||
[Parameter]
|
||||
string MyAttr { get; set; }
|
||||
|
||||
[Parameter]
|
||||
RenderFragment<string> ChildContent { get; set; }
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
// Act
|
||||
var generated = CompileToCSharp(@"
|
||||
@addTagHelper *, TestAssembly
|
||||
<MyComponent MyAttr=""abc"" Context=""item"">
|
||||
<ChildContent>
|
||||
Some text<some-child a='1'>@item.ToLowerInvariant()</some-child>
|
||||
</ChildContent>
|
||||
</MyComponent>");
|
||||
|
||||
// Assert
|
||||
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
|
||||
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
||||
CompileToAssembly(generated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChildComponent_WithElementOnlyChildContent()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
// <auto-generated/>
|
||||
#pragma warning disable 1591
|
||||
namespace Test
|
||||
{
|
||||
#line hidden
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Blazor;
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||
{
|
||||
#pragma warning disable 219
|
||||
private void __RazorDirectiveTokenHelpers__() {
|
||||
((System.Action)(() => {
|
||||
global::System.Object __typeHelper = "*, TestAssembly";
|
||||
}
|
||||
))();
|
||||
}
|
||||
#pragma warning restore 219
|
||||
#pragma warning disable 0414
|
||||
private static System.Object __o = null;
|
||||
#pragma warning restore 0414
|
||||
#pragma warning disable 1998
|
||||
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||
{
|
||||
base.BuildRenderTree(builder);
|
||||
__o = "";
|
||||
builder.AddAttribute(-1, "ChildContent", (Microsoft.AspNetCore.Blazor.RenderFragment<System.String>)((item) => (builder2) => {
|
||||
#line 4 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
__o = item.ToLowerInvariant();
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
));
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
Document -
|
||||
NamespaceDeclaration - - Test
|
||||
UsingDirective - (3:1,1 [12] ) - System
|
||||
UsingDirective - (18:2,1 [32] ) - System.Collections.Generic
|
||||
UsingDirective - (53:3,1 [17] ) - System.Linq
|
||||
UsingDirective - (73:4,1 [28] ) - System.Threading.Tasks
|
||||
UsingDirective - (104:5,1 [33] ) - Microsoft.AspNetCore.Blazor
|
||||
UsingDirective - (140:6,1 [44] ) - Microsoft.AspNetCore.Blazor.Components
|
||||
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent -
|
||||
DesignTimeDirective -
|
||||
DirectiveToken - (14:0,14 [32] ) - "*, Microsoft.AspNetCore.Blazor"
|
||||
DirectiveToken - (14:0,14 [9] ) - "*, Test"
|
||||
DirectiveToken - (14:0,14 [15] x:\dir\subdir\Test\TestComponent.cshtml) - *, TestAssembly
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - #pragma warning disable 0414
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - private static System.Object __o = null;
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - #pragma warning restore 0414
|
||||
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
|
||||
HtmlContent - (29:0,29 [2] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (29:0,29 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n
|
||||
ComponentExtensionNode - (31:1,0 [164] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent - Test.MyComponent
|
||||
ComponentChildContent - (76:2,2 [103] x:\dir\subdir\Test\TestComponent.cshtml) - ChildContent
|
||||
HtmlContent - (90:2,16 [15] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (90:2,16 [15] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n Some text
|
||||
HtmlElement - (105:3,13 [55] x:\dir\subdir\Test\TestComponent.cshtml) - some-child
|
||||
HtmlAttribute - - -
|
||||
HtmlAttributeValue - -
|
||||
IntermediateToken - - Html - 1
|
||||
CSharpExpression - (124:3,32 [23] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (124:3,32 [23] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - item.ToLowerInvariant()
|
||||
HtmlContent - (160:3,68 [4] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (160:3,68 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n
|
||||
ComponentAttributeExtensionNode - (52:1,21 [3] x:\dir\subdir\Test\TestComponent.cshtml) - MyAttr - MyAttr
|
||||
HtmlContent - (52:1,21 [3] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (52:1,21 [3] x:\dir\subdir\Test\TestComponent.cshtml) - Html - abc
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
Source Location: (14:0,14 [15] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|*, TestAssembly|
|
||||
Generated Location: (559:16,38 [15] )
|
||||
|*, TestAssembly|
|
||||
|
||||
Source Location: (124:3,32 [23] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|item.ToLowerInvariant()|
|
||||
Generated Location: (1232:31,32 [23] )
|
||||
|item.ToLowerInvariant()|
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// <auto-generated/>
|
||||
#pragma warning disable 1591
|
||||
namespace Test
|
||||
{
|
||||
#line hidden
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Blazor;
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||
{
|
||||
#pragma warning disable 1998
|
||||
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||
{
|
||||
base.BuildRenderTree(builder);
|
||||
builder.OpenComponent<Test.MyComponent>(0);
|
||||
builder.AddAttribute(1, "MyAttr", "abc");
|
||||
builder.AddAttribute(2, "ChildContent", (Microsoft.AspNetCore.Blazor.RenderFragment<System.String>)((item) => (builder2) => {
|
||||
builder2.AddContent(3, "\n Some text");
|
||||
builder2.OpenElement(4, "some-child");
|
||||
builder2.AddAttribute(5, "a", "1");
|
||||
builder2.AddContent(6, item.ToLowerInvariant());
|
||||
builder2.CloseElement();
|
||||
builder2.AddContent(7, "\n ");
|
||||
}
|
||||
));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
Document -
|
||||
NamespaceDeclaration - - Test
|
||||
UsingDirective - (3:1,1 [14] ) - System
|
||||
UsingDirective - (18:2,1 [34] ) - System.Collections.Generic
|
||||
UsingDirective - (53:3,1 [19] ) - System.Linq
|
||||
UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks
|
||||
UsingDirective - (104:5,1 [35] ) - Microsoft.AspNetCore.Blazor
|
||||
UsingDirective - (140:6,1 [46] ) - Microsoft.AspNetCore.Blazor.Components
|
||||
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent -
|
||||
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
|
||||
ComponentExtensionNode - (31:1,0 [164] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent - Test.MyComponent
|
||||
ComponentChildContent - (76:2,2 [103] x:\dir\subdir\Test\TestComponent.cshtml) - ChildContent
|
||||
HtmlContent - (90:2,16 [15] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (90:2,16 [15] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n Some text
|
||||
HtmlElement - (105:3,13 [55] x:\dir\subdir\Test\TestComponent.cshtml) - some-child
|
||||
HtmlAttribute - - -
|
||||
HtmlAttributeValue - -
|
||||
IntermediateToken - - Html - 1
|
||||
CSharpExpression - (124:3,32 [23] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (124:3,32 [23] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - item.ToLowerInvariant()
|
||||
HtmlContent - (160:3,68 [4] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (160:3,68 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n
|
||||
ComponentAttributeExtensionNode - (52:1,21 [3] x:\dir\subdir\Test\TestComponent.cshtml) - MyAttr - MyAttr
|
||||
HtmlContent - (52:1,21 [3] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (52:1,21 [3] x:\dir\subdir\Test\TestComponent.cshtml) - Html - abc
|
||||
|
|
@ -672,18 +672,27 @@ namespace Test
|
|||
Assert.Equal("TestAssembly", component.AssemblyName);
|
||||
Assert.Equal("Test.MyComponent", component.Name);
|
||||
|
||||
var attribute = Assert.Single(component.BoundAttributes);
|
||||
Assert.Equal("ChildContent2", attribute.Name);
|
||||
Assert.Equal("Microsoft.AspNetCore.Blazor.RenderFragment<System.String>", attribute.TypeName);
|
||||
Assert.Collection(
|
||||
component.BoundAttributes,
|
||||
a =>
|
||||
{
|
||||
Assert.Equal("ChildContent2", a.Name);
|
||||
Assert.Equal("Microsoft.AspNetCore.Blazor.RenderFragment<System.String>", a.TypeName);
|
||||
|
||||
Assert.False(attribute.HasIndexer);
|
||||
Assert.False(attribute.IsBooleanProperty);
|
||||
Assert.False(attribute.IsEnum);
|
||||
Assert.False(attribute.IsStringProperty);
|
||||
Assert.False(attribute.IsDelegateProperty()); // We treat RenderFragment as separate from generalized delegates
|
||||
Assert.True(attribute.IsChildContentProperty());
|
||||
Assert.True(attribute.IsParameterizedChildContentProperty());
|
||||
Assert.False(attribute.IsGenericTypedProperty());
|
||||
Assert.False(a.HasIndexer);
|
||||
Assert.False(a.IsBooleanProperty);
|
||||
Assert.False(a.IsEnum);
|
||||
Assert.False(a.IsStringProperty);
|
||||
Assert.False(a.IsDelegateProperty()); // We treat RenderFragment as separate from generalized delegates
|
||||
Assert.True(a.IsChildContentProperty());
|
||||
Assert.True(a.IsParameterizedChildContentProperty());
|
||||
Assert.False(a.IsGenericTypedProperty());
|
||||
},
|
||||
a =>
|
||||
{
|
||||
Assert.Equal(BlazorMetadata.ChildContent.ParameterAttributeName, a.Name);
|
||||
Assert.True(a.IsChildContentParameterNameProperty());
|
||||
});
|
||||
|
||||
var childContent = Assert.Single(components, c => c.IsChildContentTagHelper());
|
||||
|
||||
|
|
@ -692,9 +701,85 @@ namespace Test
|
|||
|
||||
// A RenderFragment<T> tag helper has a parameter to allow you to set the lambda parameter name.
|
||||
var contextAttribute = Assert.Single(childContent.BoundAttributes);
|
||||
Assert.Equal("Context", contextAttribute.Name);
|
||||
Assert.Equal(BlazorMetadata.ChildContent.ParameterAttributeName, contextAttribute.Name);
|
||||
Assert.Equal("System.String", contextAttribute.TypeName);
|
||||
Assert.Equal("Specifies the parameter name for the 'ChildContent2' lambda expression.", contextAttribute.Documentation);
|
||||
Assert.Equal("Specifies the parameter name for the 'ChildContent2' child content expression.", contextAttribute.Documentation);
|
||||
Assert.True(contextAttribute.IsChildContentParameterNameProperty());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RenderFragmentOfTProperty_ComponentDefinesContextParameter()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||
using Microsoft.AspNetCore.Blazor;
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : BlazorComponent
|
||||
{
|
||||
[Parameter]
|
||||
RenderFragment<string> ChildContent2 { get; set; }
|
||||
|
||||
[Parameter]
|
||||
string Context { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
"));
|
||||
|
||||
Assert.Empty(compilation.GetDiagnostics());
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
var provider = new ComponentTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var components = ExcludeBuiltInComponents(context);
|
||||
var component = Assert.Single(components, c => c.IsComponentTagHelper());
|
||||
|
||||
Assert.Equal("TestAssembly", component.AssemblyName);
|
||||
Assert.Equal("Test.MyComponent", component.Name);
|
||||
|
||||
Assert.Collection(
|
||||
component.BoundAttributes,
|
||||
a =>
|
||||
{
|
||||
Assert.Equal("ChildContent2", a.Name);
|
||||
Assert.Equal("Microsoft.AspNetCore.Blazor.RenderFragment<System.String>", a.TypeName);
|
||||
|
||||
Assert.False(a.HasIndexer);
|
||||
Assert.False(a.IsBooleanProperty);
|
||||
Assert.False(a.IsEnum);
|
||||
Assert.False(a.IsStringProperty);
|
||||
Assert.False(a.IsDelegateProperty()); // We treat RenderFragment as separate from generalized delegates
|
||||
Assert.True(a.IsChildContentProperty());
|
||||
Assert.True(a.IsParameterizedChildContentProperty());
|
||||
Assert.False(a.IsGenericTypedProperty());
|
||||
},
|
||||
a =>
|
||||
{
|
||||
Assert.Equal(BlazorMetadata.ChildContent.ParameterAttributeName, a.Name);
|
||||
Assert.False(a.IsChildContentParameterNameProperty());
|
||||
});
|
||||
|
||||
var childContent = Assert.Single(components, c => c.IsChildContentTagHelper());
|
||||
|
||||
Assert.Equal("TestAssembly", childContent.AssemblyName);
|
||||
Assert.Equal("Test.MyComponent.ChildContent2", childContent.Name);
|
||||
|
||||
// A RenderFragment<T> tag helper has a parameter to allow you to set the lambda parameter name.
|
||||
var contextAttribute = Assert.Single(childContent.BoundAttributes);
|
||||
Assert.Equal(BlazorMetadata.ChildContent.ParameterAttributeName, contextAttribute.Name);
|
||||
Assert.Equal("System.String", contextAttribute.TypeName);
|
||||
Assert.Equal("Specifies the parameter name for the 'ChildContent2' child content expression.", contextAttribute.Documentation);
|
||||
Assert.True(contextAttribute.IsChildContentParameterNameProperty());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -752,6 +837,11 @@ namespace Test
|
|||
|
||||
},
|
||||
a =>
|
||||
{
|
||||
Assert.Equal(BlazorMetadata.ChildContent.ParameterAttributeName, a.Name);
|
||||
Assert.True(a.IsChildContentParameterNameProperty());
|
||||
},
|
||||
a =>
|
||||
{
|
||||
Assert.Equal("T", a.Name);
|
||||
Assert.Equal("T", a.GetPropertyName());
|
||||
|
|
@ -767,9 +857,10 @@ namespace Test
|
|||
|
||||
// A RenderFragment<T> tag helper has a parameter to allow you to set the lambda parameter name.
|
||||
var contextAttribute = Assert.Single(childContent.BoundAttributes);
|
||||
Assert.Equal("Context", contextAttribute.Name);
|
||||
Assert.Equal(BlazorMetadata.ChildContent.ParameterAttributeName, contextAttribute.Name);
|
||||
Assert.Equal("System.String", contextAttribute.TypeName);
|
||||
Assert.Equal("Specifies the parameter name for the 'ChildContent2' lambda expression.", contextAttribute.Documentation);
|
||||
Assert.Equal("Specifies the parameter name for the 'ChildContent2' child content expression.", contextAttribute.Documentation);
|
||||
Assert.True(contextAttribute.IsChildContentParameterNameProperty());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -828,6 +919,11 @@ namespace Test
|
|||
|
||||
},
|
||||
a =>
|
||||
{
|
||||
Assert.Equal(BlazorMetadata.ChildContent.ParameterAttributeName, a.Name);
|
||||
Assert.True(a.IsChildContentParameterNameProperty());
|
||||
},
|
||||
a =>
|
||||
{
|
||||
Assert.Equal("T", a.Name);
|
||||
Assert.Equal("T", a.GetPropertyName());
|
||||
|
|
@ -843,9 +939,10 @@ namespace Test
|
|||
|
||||
// A RenderFragment<T> tag helper has a parameter to allow you to set the lambda parameter name.
|
||||
var contextAttribute = Assert.Single(childContent.BoundAttributes);
|
||||
Assert.Equal("Context", contextAttribute.Name);
|
||||
Assert.Equal(BlazorMetadata.ChildContent.ParameterAttributeName, contextAttribute.Name);
|
||||
Assert.Equal("System.String", contextAttribute.TypeName);
|
||||
Assert.Equal("Specifies the parameter name for the 'ChildContent2' lambda expression.", contextAttribute.Documentation);
|
||||
Assert.Equal("Specifies the parameter name for the 'ChildContent2' child content expression.", contextAttribute.Documentation);
|
||||
Assert.True(contextAttribute.IsChildContentParameterNameProperty());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -904,6 +1001,11 @@ namespace Test
|
|||
|
||||
},
|
||||
a =>
|
||||
{
|
||||
Assert.Equal(BlazorMetadata.ChildContent.ParameterAttributeName, a.Name);
|
||||
Assert.True(a.IsChildContentParameterNameProperty());
|
||||
},
|
||||
a =>
|
||||
{
|
||||
Assert.Equal("T", a.Name);
|
||||
Assert.Equal("T", a.GetPropertyName());
|
||||
|
|
@ -919,9 +1021,10 @@ namespace Test
|
|||
|
||||
// A RenderFragment<T> tag helper has a parameter to allow you to set the lambda parameter name.
|
||||
var contextAttribute = Assert.Single(childContent.BoundAttributes);
|
||||
Assert.Equal("Context", contextAttribute.Name);
|
||||
Assert.Equal(BlazorMetadata.ChildContent.ParameterAttributeName, contextAttribute.Name);
|
||||
Assert.Equal("System.String", contextAttribute.TypeName);
|
||||
Assert.Equal("Specifies the parameter name for the 'ChildContent2' lambda expression.", contextAttribute.Documentation);
|
||||
Assert.Equal("Specifies the parameter name for the 'ChildContent2' child content expression.", contextAttribute.Documentation);
|
||||
Assert.True(contextAttribute.IsChildContentParameterNameProperty());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -984,6 +1087,11 @@ namespace Test
|
|||
|
||||
},
|
||||
a =>
|
||||
{
|
||||
Assert.Equal(BlazorMetadata.ChildContent.ParameterAttributeName, a.Name);
|
||||
Assert.True(a.IsChildContentParameterNameProperty());
|
||||
},
|
||||
a =>
|
||||
{
|
||||
Assert.Equal("T", a.Name);
|
||||
Assert.Equal("T", a.GetPropertyName());
|
||||
|
|
@ -999,9 +1107,9 @@ namespace Test
|
|||
|
||||
// A RenderFragment<T> tag helper has a parameter to allow you to set the lambda parameter name.
|
||||
var contextAttribute = Assert.Single(childContent.BoundAttributes);
|
||||
Assert.Equal("Context", contextAttribute.Name);
|
||||
Assert.Equal(BlazorMetadata.ChildContent.ParameterAttributeName, contextAttribute.Name);
|
||||
Assert.Equal("System.String", contextAttribute.TypeName);
|
||||
Assert.Equal("Specifies the parameter name for the 'ChildContent2' lambda expression.", contextAttribute.Documentation);
|
||||
Assert.Equal("Specifies the parameter name for the 'ChildContent2' child content expression.", contextAttribute.Documentation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1056,6 +1164,11 @@ namespace Test
|
|||
Assert.True(a.IsChildContentProperty());
|
||||
},
|
||||
a =>
|
||||
{
|
||||
Assert.Equal(BlazorMetadata.ChildContent.ParameterAttributeName, a.Name);
|
||||
Assert.True(a.IsChildContentParameterNameProperty());
|
||||
},
|
||||
a =>
|
||||
{
|
||||
Assert.Equal("Footer", a.Name);
|
||||
Assert.Equal("Microsoft.AspNetCore.Blazor.RenderFragment<System.String>", a.TypeName);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<TemplatedTable Items="@Items">
|
||||
<TemplatedTable Items="@Items" Context="row">
|
||||
<Header><tr><th>Col1</th><th>Col2</th><th>Col3</th></tr></Header>
|
||||
<Footer>
|
||||
@if (ShowFooter)
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
}
|
||||
</Footer>
|
||||
<ItemTemplate>
|
||||
<tr><td>@context.Col1</td><td>@context.Col2</td><td>@context.Col3</td></tr>
|
||||
<tr><td>@row.Col1</td><td>@row.Col2</td><td>@row.Col3</td></tr>
|
||||
</ItemTemplate>
|
||||
</TemplatedTable>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue