From c05657c7f2f7c669138fd3f06faabd5251c97c82 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 8 Mar 2018 23:24:18 -0800 Subject: [PATCH] Add support general delegate types This builds upon existing support for UIEventHandler-typed component properties and applies the same principle to any delegate type. We try to help by generating the LSH of the lambda `=>` allowing you to write `OnClick="Foo()"` rather than `OnClick="(e) => Foo()"`. You can of course use @ as an escape. The only rough edge here is that if the parameter names aren't memorable for the delgate type, it's not super helpful. --- .../BlazorDesignTimeNodeWriter.cs | 6 ++++-- .../BlazorRuntimeNodeWriter.cs | 5 +++-- .../ComponentTagHelperDescriptorProvider.cs | 15 +++++++------- ...elperBoundAttributeDescriptorExtensions.cs | 20 ++++++++++++++----- ...nTimeCodeGenerationRazorIntegrationTest.cs | 2 +- ...ntimeCodeGenerationRazorIntegrationTest.cs | 2 +- ...omponentTagHelperDescriptorProviderTest.cs | 6 +++--- 7 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDesignTimeNodeWriter.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDesignTimeNodeWriter.cs index 2d42519b9d..ff10295a09 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDesignTimeNodeWriter.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDesignTimeNodeWriter.cs @@ -423,7 +423,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor // We don't expect this to happen, we just want to know if it can. throw new InvalidOperationException("Attribute nodes should either be minimized or a single content node."); } - else if (node.BoundAttribute.IsUIEventHandlerProperty()) + else if (node.BoundAttribute.IsDelegateProperty()) { // See the runtime version of this code for a thorough description of what we're doing here if ((cSharpNode = node.Children[0] as CSharpExpressionIntermediateNode) != null) @@ -445,7 +445,9 @@ namespace Microsoft.AspNetCore.Blazor.Razor context.CodeWriter.Write(" = "); context.CodeWriter.Write("new "); context.CodeWriter.Write(node.BoundAttribute.TypeName); - context.CodeWriter.Write("(e => "); + context.CodeWriter.Write("("); + context.CodeWriter.Write(node.BoundAttribute.GetDelegateSignature()); + context.CodeWriter.Write(" => "); WriteCSharpToken(context, ((IntermediateToken)node.Children[0])); context.CodeWriter.Write(");"); context.CodeWriter.WriteLine(); diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRuntimeNodeWriter.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRuntimeNodeWriter.cs index 3cd88967a5..d9765cbe10 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRuntimeNodeWriter.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRuntimeNodeWriter.cs @@ -470,7 +470,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor // We don't expect this to happen, we just want to know if it can. throw new InvalidOperationException("Attribute nodes should either be minimized or a single content node."); } - else if (node.BoundAttribute.IsUIEventHandlerProperty()) + else if (node.BoundAttribute.IsDelegateProperty()) { // This is a UIEventHandler property. We do some special code generation for this // case so that it's easier to write for common cases. @@ -502,7 +502,8 @@ namespace Microsoft.AspNetCore.Blazor.Razor context.CodeWriter.Write("new "); context.CodeWriter.Write(node.BoundAttribute.TypeName); context.CodeWriter.Write("("); - context.CodeWriter.Write("e => "); + context.CodeWriter.Write(node.BoundAttribute.GetDelegateSignature()); + context.CodeWriter.Write(" => "); context.CodeWriter.Write(((IntermediateToken)node.Children[0]).Content); context.CodeWriter.Write(")"); } diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentTagHelperDescriptorProvider.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentTagHelperDescriptorProvider.cs index e07439a89e..f04736247c 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentTagHelperDescriptorProvider.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentTagHelperDescriptorProvider.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Razor; @@ -11,7 +12,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor { internal class ComponentTagHelperDescriptorProvider : RazorEngineFeatureBase, ITagHelperDescriptorProvider { - public static readonly string UIEventHandlerPropertyMetadata = "Blazor.IsUIEventHandler"; + public static readonly string DelegateSignatureMetadata = "Blazor.DelegateSignature"; public readonly static string ComponentTagHelperKind = ComponentDocumentClassifierPass.ComponentDocumentKind; @@ -113,7 +114,11 @@ namespace Microsoft.AspNetCore.Blazor.Razor if (property.kind == PropertyKind.Delegate) { - pb.Metadata.Add(UIEventHandlerPropertyMetadata, bool.TrueString); + var propertyType = (INamedTypeSymbol)property.property.Type; + var parameters = propertyType.DelegateInvokeMethod.Parameters; + + var signature = "(" + string.Join(", ", parameters.Select(p => p.Name)) + ")"; + pb.Metadata.Add(DelegateSignatureMetadata, signature); } xml = property.property.GetDocumentationCommentXml(); @@ -182,12 +187,8 @@ namespace Microsoft.AspNetCore.Blazor.Razor kind = PropertyKind.Enum; } - if (kind == PropertyKind.Default && - property.Type.TypeKind == TypeKind.Delegate && - property.Type.ToDisplayString(FullNameTypeDisplayFormat) == BlazorApi.UIEventHandler.FullTypeName) + if (kind == PropertyKind.Default && property.Type.TypeKind == TypeKind.Delegate) { - // For delegate types we do some special code generation when the type - // UIEventHandler. kind = PropertyKind.Delegate; } diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/TagHelperBoundAttributeDescriptorExtensions.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/TagHelperBoundAttributeDescriptorExtensions.cs index 64e7088932..e09cfb0f04 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/TagHelperBoundAttributeDescriptorExtensions.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/TagHelperBoundAttributeDescriptorExtensions.cs @@ -8,17 +8,27 @@ namespace Microsoft.AspNetCore.Blazor.Razor { internal static class TagHelperBoundAttributeDescriptorExtensions { - public static bool IsUIEventHandlerProperty(this BoundAttributeDescriptor attribute) + public static bool IsDelegateProperty(this BoundAttributeDescriptor attribute) { if (attribute == null) { throw new ArgumentNullException(nameof(attribute)); } - var key = ComponentTagHelperDescriptorProvider.UIEventHandlerPropertyMetadata; - return - attribute.Metadata.TryGetValue(key, out var value) && - string.Equals(value, bool.TrueString); + var key = ComponentTagHelperDescriptorProvider.DelegateSignatureMetadata; + return attribute.Metadata.TryGetValue(key, out var value); + } + + public static string GetDelegateSignature(this BoundAttributeDescriptor attribute) + { + if (attribute == null) + { + throw new ArgumentNullException(nameof(attribute)); + } + + var key = ComponentTagHelperDescriptorProvider.DelegateSignatureMetadata; + attribute.Metadata.TryGetValue(key, out var value); + return value; } } } diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/DesignTimeCodeGenerationRazorIntegrationTest.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/DesignTimeCodeGenerationRazorIntegrationTest.cs index 5175a51167..2cc8ad2075 100644 --- a/test/Microsoft.AspNetCore.Blazor.Build.Test/DesignTimeCodeGenerationRazorIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/DesignTimeCodeGenerationRazorIntegrationTest.cs @@ -239,7 +239,7 @@ global::System.Object __typeHelper = ""*, TestAssembly""; { base.BuildRenderTree(builder); - __o = new Microsoft.AspNetCore.Blazor.UIEventHandler(e => + __o = new Microsoft.AspNetCore.Blazor.UIEventHandler(eventArgs => #line 2 ""x:\dir\subdir\Test\TestComponent.cshtml"" Increment() diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/RuntimeCodeGenerationRazorIntegrationTest.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/RuntimeCodeGenerationRazorIntegrationTest.cs index f7751602b5..722f644b3f 100644 --- a/test/Microsoft.AspNetCore.Blazor.Build.Test/RuntimeCodeGenerationRazorIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/RuntimeCodeGenerationRazorIntegrationTest.cs @@ -225,7 +225,7 @@ namespace Test { base.BuildRenderTree(builder); builder.OpenComponent(0); - builder.AddAttribute(1, ""OnClick"", new Microsoft.AspNetCore.Blazor.UIEventHandler(e => Increment())); + builder.AddAttribute(1, ""OnClick"", new Microsoft.AspNetCore.Blazor.UIEventHandler(eventArgs => Increment())); builder.CloseComponent(); builder.AddContent(2, ""\n\n""); } diff --git a/test/Microsoft.AspNetCore.Blazor.Razor.Extensions.Test/ComponentTagHelperDescriptorProviderTest.cs b/test/Microsoft.AspNetCore.Blazor.Razor.Extensions.Test/ComponentTagHelperDescriptorProviderTest.cs index cee3ed6125..a2f4f27872 100644 --- a/test/Microsoft.AspNetCore.Blazor.Razor.Extensions.Test/ComponentTagHelperDescriptorProviderTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.Razor.Extensions.Test/ComponentTagHelperDescriptorProviderTest.cs @@ -282,8 +282,8 @@ namespace Test Assert.False(attribute.IsStringProperty); } - [Fact] // UIEventHandler properties have some special intellisense behavior - public void Excecute_UIEventHandlerProperty_CreatesDescriptor() + [Fact] + public void Execute_DelegateProperty_CreatesDescriptor() { // Arrange @@ -326,7 +326,7 @@ namespace Test Assert.False(attribute.IsBooleanProperty); Assert.False(attribute.IsEnum); Assert.False(attribute.IsStringProperty); - Assert.True(attribute.IsUIEventHandlerProperty()); + Assert.True(attribute.IsDelegateProperty()); } // For simplicity in testing, exlude the built-in components. We'll add more and we