diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDesignTimeNodeWriter.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDesignTimeNodeWriter.cs index d067eebc63..3ed8d0959c 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDesignTimeNodeWriter.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDesignTimeNodeWriter.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -422,7 +422,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor context.CodeWriter.Write(" = "); // If we have a parameter type, then add a type check. - if (node.BoundAttribute != null) + if (node.BoundAttribute != null && !node.BoundAttribute.IsWeaklyTyped()) { context.CodeWriter.Write(BlazorApi.RuntimeHelpers.TypeCheck); context.CodeWriter.Write("<"); @@ -436,7 +436,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor WriteCSharpToken(context, tokens[i]); } - if (node.BoundAttribute != null) + if (node.BoundAttribute != null && !node.BoundAttribute.IsWeaklyTyped()) { context.CodeWriter.Write(")"); } diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorMetadata.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorMetadata.cs index 95f2cb52b4..673ae9f504 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorMetadata.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorMetadata.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. namespace Microsoft.AspNetCore.Blazor.Razor @@ -30,6 +30,8 @@ namespace Microsoft.AspNetCore.Blazor.Razor { public static readonly string DelegateSignatureKey = "Blazor.DelegateSignature"; + public static readonly string WeaklyTypedKey = "Blazor.IsWeaklyTyped"; + public static readonly string RuntimeName = "Blazor.IComponent"; public readonly static string TagHelperKind = "Blazor.Component"; diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRuntimeNodeWriter.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRuntimeNodeWriter.cs index b91431ad1b..547d763cc0 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRuntimeNodeWriter.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRuntimeNodeWriter.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -379,7 +379,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor } else { - if (node.BoundAttribute != null) + if (node.BoundAttribute != null && !node.BoundAttribute.IsWeaklyTyped()) { context.CodeWriter.Write(BlazorApi.RuntimeHelpers.TypeCheck); context.CodeWriter.Write("<"); @@ -393,7 +393,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor context.CodeWriter.Write(tokens[i].Content); } - if (node.BoundAttribute != null) + if (node.BoundAttribute != null && !node.BoundAttribute.IsWeaklyTyped()) { context.CodeWriter.Write(")"); } diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/EventHandlerTagHelperDescriptorProvider.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/EventHandlerTagHelperDescriptorProvider.cs index 457d31465e..ccb307d677 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/EventHandlerTagHelperDescriptorProvider.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/EventHandlerTagHelperDescriptorProvider.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -143,6 +143,10 @@ namespace Microsoft.AspNetCore.Blazor.Razor // Use a string here so that we get HTML context by default. a.TypeName = typeof(string).FullName; + // But make this weakly typed (don't type check) - delegates have their own type-checking + // logic that we don't want to interfere with. + a.Metadata.Add(BlazorMetadata.Component.WeaklyTypedKey, bool.TrueString); + // WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like // a C# property will crash trying to create the toolips. a.SetPropertyName(entry.Attribute); diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/TagHelperBoundAttributeDescriptorExtensions.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/TagHelperBoundAttributeDescriptorExtensions.cs index 504eb0d04e..59cf7e1880 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/TagHelperBoundAttributeDescriptorExtensions.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/TagHelperBoundAttributeDescriptorExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -20,5 +20,18 @@ namespace Microsoft.AspNetCore.Blazor.Razor attribute.Metadata.TryGetValue(key, out var value) && string.Equals(value, bool.TrueString); } + + public static bool IsWeaklyTyped(this BoundAttributeDescriptor attribute) + { + if (attribute == null) + { + throw new ArgumentNullException(nameof(attribute)); + } + + var key = BlazorMetadata.Component.WeaklyTypedKey; + return + attribute.Metadata.TryGetValue(key, out var value) && + string.Equals(value, bool.TrueString); + } } } diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/DesignTimeCodeGenerationTest.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/DesignTimeCodeGenerationTest.cs index 2832cb83e5..facfb1abdf 100644 --- a/test/Microsoft.AspNetCore.Blazor.Build.Test/DesignTimeCodeGenerationTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/DesignTimeCodeGenerationTest.cs @@ -174,6 +174,40 @@ namespace Test CompileToAssembly(generated); } + // Regression test for #954 - we need to allow arbitrary event handler + // attributes with weak typing. + [Fact] + public void ChildComponent_WithWeaklyTypeEventHandler() + { + // Arrange + AdditionalSyntaxTrees.Add(Parse(@" +using System; +using Microsoft.AspNetCore.Blazor; +using Microsoft.AspNetCore.Blazor.Components; + +namespace Test +{ + public class DynamicElement : BlazorComponent + { + } +} +")); + + // Act + var generated = CompileToCSharp(@" +@addTagHelper *, TestAssembly + + +@functions { + private Action OnClick { get; set; } +}"); + + // Assert + AssertDocumentNodeMatchesBaseline(generated.CodeDocument); + AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); + CompileToAssembly(generated); + } + [Fact] public void ChildComponent_WithExplicitEventHandler() { diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/RuntimeCodeGenerationTest.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/RuntimeCodeGenerationTest.cs index d57e1bcb91..b381d87d7c 100644 --- a/test/Microsoft.AspNetCore.Blazor.Build.Test/RuntimeCodeGenerationTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/RuntimeCodeGenerationTest.cs @@ -166,6 +166,40 @@ namespace Test CompileToAssembly(generated); } + // Regression test for #954 - we need to allow arbitrary event handler + // attributes with weak typing. + [Fact] + public void ChildComponent_WithWeaklyTypeEventHandler() + { + // Arrange + AdditionalSyntaxTrees.Add(Parse(@" +using System; +using Microsoft.AspNetCore.Blazor; +using Microsoft.AspNetCore.Blazor.Components; + +namespace Test +{ + public class DynamicElement : BlazorComponent + { + } +} +")); + + // Act + var generated = CompileToCSharp(@" +@addTagHelper *, TestAssembly + + +@functions { + private Action OnClick { get; set; } +}"); + + // Assert + AssertDocumentNodeMatchesBaseline(generated.CodeDocument); + AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); + CompileToAssembly(generated); + } + [Fact] public void ChildComponent_WithExplicitEventHandler() { @@ -759,8 +793,6 @@ Welcome to your new app. [Fact] // https://github.com/aspnet/Blazor/issues/773 public void Regression_773() { - GenerateBaselines = true; - // Arrange AdditionalSyntaxTrees.Add(Parse(@" using Microsoft.AspNetCore.Blazor.Components; diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_WithWeaklyTypeEventHandler/TestComponent.codegen.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_WithWeaklyTypeEventHandler/TestComponent.codegen.cs new file mode 100644 index 0000000000..64eaf8d5aa --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_WithWeaklyTypeEventHandler/TestComponent.codegen.cs @@ -0,0 +1,49 @@ +// +#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 = Microsoft.AspNetCore.Blazor.Components.BindMethods.GetEventHandlerValue( +#line 2 "x:\dir\subdir\Test\TestComponent.cshtml" + OnClick + +#line default +#line hidden + ); + builder.AddAttribute(-1, "ChildContent", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => { + } + )); + } + #pragma warning restore 1998 +#line 4 "x:\dir\subdir\Test\TestComponent.cshtml" + + private Action OnClick { get; set; } + +#line default +#line hidden + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_WithWeaklyTypeEventHandler/TestComponent.ir.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_WithWeaklyTypeEventHandler/TestComponent.ir.txt new file mode 100644 index 0000000000..604f3fa88e --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_WithWeaklyTypeEventHandler/TestComponent.ir.txt @@ -0,0 +1,34 @@ +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 [37] x:\dir\subdir\Test\TestComponent.cshtml) - DynamicElement - Test.DynamicElement + ComponentAttributeExtensionNode - (56:1,25 [8] x:\dir\subdir\Test\TestComponent.cshtml) - onclick - onclick + CSharpExpression - + IntermediateToken - - CSharp - Microsoft.AspNetCore.Blazor.Components.BindMethods.GetEventHandlerValue( + IntermediateToken - (57:1,26 [7] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - OnClick + IntermediateToken - - CSharp - ) + HtmlContent - (68:1,37 [4] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (68:1,37 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n\n + CSharpCode - (84:3,12 [62] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (84:3,12 [62] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n private Action OnClick { get; set; }\n diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_WithWeaklyTypeEventHandler/TestComponent.mappings.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_WithWeaklyTypeEventHandler/TestComponent.mappings.txt new file mode 100644 index 0000000000..ecad042f3e --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_WithWeaklyTypeEventHandler/TestComponent.mappings.txt @@ -0,0 +1,19 @@ +Source Location: (14:0,14 [15] x:\dir\subdir\Test\TestComponent.cshtml) +|*, TestAssembly| +Generated Location: (559:16,38 [15] ) +|*, TestAssembly| + +Source Location: (57:1,26 [7] x:\dir\subdir\Test\TestComponent.cshtml) +|OnClick| +Generated Location: (1201:30,26 [7] ) +|OnClick| + +Source Location: (84:3,12 [62] x:\dir\subdir\Test\TestComponent.cshtml) +| + private Action OnClick { get; set; } +| +Generated Location: (1516:41,12 [62] ) +| + private Action OnClick { get; set; } +| + diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_WithWeaklyTypeEventHandler/TestComponent.codegen.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_WithWeaklyTypeEventHandler/TestComponent.codegen.cs new file mode 100644 index 0000000000..5ec08178e3 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_WithWeaklyTypeEventHandler/TestComponent.codegen.cs @@ -0,0 +1,31 @@ +// +#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(0); + builder.AddAttribute(1, "onclick", Microsoft.AspNetCore.Blazor.Components.BindMethods.GetEventHandlerValue(OnClick)); + builder.CloseComponent(); + } + #pragma warning restore 1998 +#line 4 "x:\dir\subdir\Test\TestComponent.cshtml" + + private Action OnClick { get; set; } + +#line default +#line hidden + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_WithWeaklyTypeEventHandler/TestComponent.ir.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_WithWeaklyTypeEventHandler/TestComponent.ir.txt new file mode 100644 index 0000000000..b432de290d --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_WithWeaklyTypeEventHandler/TestComponent.ir.txt @@ -0,0 +1,20 @@ +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 [37] x:\dir\subdir\Test\TestComponent.cshtml) - DynamicElement - Test.DynamicElement + ComponentAttributeExtensionNode - (56:1,25 [8] x:\dir\subdir\Test\TestComponent.cshtml) - onclick - onclick + CSharpExpression - + IntermediateToken - - CSharp - Microsoft.AspNetCore.Blazor.Components.BindMethods.GetEventHandlerValue( + IntermediateToken - (57:1,26 [7] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - OnClick + IntermediateToken - - CSharp - ) + CSharpCode - (84:3,12 [62] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (84:3,12 [62] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n private Action OnClick { get; set; }\n diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_WithWeaklyTypeEventHandler/TestComponent.mappings.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_WithWeaklyTypeEventHandler/TestComponent.mappings.txt new file mode 100644 index 0000000000..4d99283766 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_WithWeaklyTypeEventHandler/TestComponent.mappings.txt @@ -0,0 +1,9 @@ +Source Location: (84:3,12 [62] x:\dir\subdir\Test\TestComponent.cshtml) +| + private Action OnClick { get; set; } +| +Generated Location: (989:23,12 [62] ) +| + private Action OnClick { get; set; } +| + diff --git a/test/Microsoft.AspNetCore.Blazor.Razor.Extensions.Test/EventHandlerTagHelperDescriptorProviderTest.cs b/test/Microsoft.AspNetCore.Blazor.Razor.Extensions.Test/EventHandlerTagHelperDescriptorProviderTest.cs index 8cf1910ae9..5f6d7959ad 100644 --- a/test/Microsoft.AspNetCore.Blazor.Razor.Extensions.Test/EventHandlerTagHelperDescriptorProviderTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.Razor.Extensions.Test/EventHandlerTagHelperDescriptorProviderTest.cs @@ -1,6 +1,7 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.CSharp; @@ -97,6 +98,11 @@ namespace Test Assert.False(attribute.IsIndexerBooleanProperty); Assert.False(attribute.IsIndexerStringProperty); + Assert.Collection( + attribute.Metadata.OrderBy(kvp => kvp.Key), + kvp => Assert.Equal(kvp, new KeyValuePair(BlazorMetadata.Component.WeaklyTypedKey, bool.TrueString)), + kvp => Assert.Equal(kvp, new KeyValuePair("Common.PropertyName", "onclick"))); + Assert.Equal( "Sets the 'onclick' attribute to the provided string or delegate value. " + "A delegate value should be of type 'System.Action'.",