From 8485e2ea10065edafb7b2fef0854f57f8746b257 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Tue, 10 Apr 2018 22:34:06 -0700 Subject: [PATCH] Add support for Action event handlers This change adds `Action` to the set of types that have an overload on RenderTreeBuilder. Additionally, we special case `Action` in the runtime because passing the event args via DynamicInvoke() would throw. Finally, reverted some of the clutter introduced by the first pass of the event handler feature. --- samples/StandaloneApp/Pages/Counter.cshtml | 3 +- .../Components/BindMethods.cs | 9 ++ .../RenderTree/RenderTreeBuilder.cs | 18 ++++ .../Rendering/Renderer.cs | 19 +++-- .../RuntimeCodeGenerationTest.cs | 61 ++++++++++++- .../TestComponent.codegen.cs | 32 +++++++ .../TestComponent.ir.txt | 24 ++++++ .../TestComponent.mappings.txt | 11 +++ .../TestComponent.codegen.cs | 24 ++++++ .../TestComponent.ir.txt | 20 +++++ .../TestComponent.codegen.cs | 32 +++++++ .../TestComponent.ir.txt | 24 ++++++ .../TestComponent.mappings.txt | 11 +++ .../TestComponent.codegen.cs | 32 +++++++ .../TestComponent.ir.txt | 24 ++++++ .../TestComponent.mappings.txt | 11 +++ .../TestComponent.codegen.cs | 24 ++++++ .../TestComponent.ir.txt | 20 +++++ .../RenderTreeBuilderTest.cs | 85 ++++++++++++++++++- .../RendererTest.cs | 35 +++++++- .../AddRemoveChildComponents.cshtml | 5 +- .../BasicTestApp/BindCasesComponent.cshtml | 2 +- .../BasicTestApp/CounterComponent.cshtml | 7 +- .../CounterComponentUsingChild.cshtml | 5 +- .../ExternalContentPackage.cshtml | 3 +- .../CookieCounterComponent.cshtml | 7 +- .../HttpRequestsComponent.cshtml | 5 +- .../ComponentFromPackage.cshtml | 5 +- 28 files changed, 518 insertions(+), 40 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_ArbitraryEventName_WithEventArgsMethodGroup/TestComponent.codegen.cs create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_ArbitraryEventName_WithEventArgsMethodGroup/TestComponent.ir.txt create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_ArbitraryEventName_WithEventArgsMethodGroup/TestComponent.mappings.txt create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsLambdaDelegate/TestComponent.codegen.cs create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsLambdaDelegate/TestComponent.ir.txt create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsMethodGroup/TestComponent.codegen.cs create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsMethodGroup/TestComponent.ir.txt create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsMethodGroup/TestComponent.mappings.txt create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgMethodGroup/TestComponent.codegen.cs create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgMethodGroup/TestComponent.ir.txt create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgMethodGroup/TestComponent.mappings.txt create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgsLambdaDelegate/TestComponent.codegen.cs create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgsLambdaDelegate/TestComponent.ir.txt diff --git a/samples/StandaloneApp/Pages/Counter.cshtml b/samples/StandaloneApp/Pages/Counter.cshtml index bf8e96e921..3843beb768 100644 --- a/samples/StandaloneApp/Pages/Counter.cshtml +++ b/samples/StandaloneApp/Pages/Counter.cshtml @@ -1,5 +1,4 @@ @page "/counter" -@using Microsoft.AspNetCore.Blazor

Counter

@@ -10,7 +9,7 @@ @functions { int currentCount = 0; - void IncrementCount(UIMouseEventArgs e) + void IncrementCount() { currentCount++; } diff --git a/src/Microsoft.AspNetCore.Blazor/Components/BindMethods.cs b/src/Microsoft.AspNetCore.Blazor/Components/BindMethods.cs index 39d8d0d90e..c154dc3aac 100644 --- a/src/Microsoft.AspNetCore.Blazor/Components/BindMethods.cs +++ b/src/Microsoft.AspNetCore.Blazor/Components/BindMethods.cs @@ -32,6 +32,15 @@ namespace Microsoft.AspNetCore.Blazor.Components return value; } + /// + /// Not intended to be used directly. + /// + public static MulticastDelegate GetEventHandlerValue(Action value) + where T : UIEventArgs + { + return value; + } + /// /// Not intended to be used directly. /// diff --git a/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeBuilder.cs b/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeBuilder.cs index 17fb4a7e6d..b0fbe8945d 100644 --- a/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeBuilder.cs +++ b/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeBuilder.cs @@ -148,6 +148,24 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree } } + /// + /// + /// Appends a frame representing an -valued attribute. + /// + /// + /// The attribute is associated with the most recently added element. If the value is null and the + /// current element is not a component, the frame will be omitted. + /// + /// + /// The . + /// An integer that represents the position of the instruction in the source code. + /// The name of the attribute. + /// The value of the attribute. + public void AddAttribute(int sequence, string name, Action value) + { + AddAttribute(sequence, name, (MulticastDelegate)value); + } + /// /// /// Appends a frame representing an -valued attribute. diff --git a/src/Microsoft.AspNetCore.Blazor/Rendering/Renderer.cs b/src/Microsoft.AspNetCore.Blazor/Rendering/Renderer.cs index 78c221f643..c4fd01ecbf 100644 --- a/src/Microsoft.AspNetCore.Blazor/Rendering/Renderer.cs +++ b/src/Microsoft.AspNetCore.Blazor/Rendering/Renderer.cs @@ -125,15 +125,20 @@ namespace Microsoft.AspNetCore.Blazor.Rendering { _eventHandlersById.Add(id, wrapper); } + // IMPORTANT: we're creating an additional delegate when necessary. This is + // going to get cached in _eventHandlersById, but the render tree diff + // will operate on 'AttributeValue' which means that we'll only create a new + // wrapper delegate when the underlying delegate changes. + // + // TLDR: If the component uses a method group or a non-capturing lambda + // we don't allocate much. + else if (frame.AttributeValue is Action action) + { + _eventHandlersById.Add(id, (UIEventArgs e) => action()); + } else if (frame.AttributeValue is MulticastDelegate @delegate) { - // IMPORTANT: we're creating an additional delegate when necessary. This is - // going to get cached in _eventHandlersById, but the render tree diff - // will operate on 'AttributeValue' which means that we'll only create a new - // wrapper delegate when the underlying delegate changes. - // - // TLDR: If the component uses a method group or a non-capturing lambda - // we don't allocate much. + _eventHandlersById.Add(id, (UIEventArgs e) => @delegate.DynamicInvoke(e)); } diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/RuntimeCodeGenerationTest.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/RuntimeCodeGenerationTest.cs index 6b59fdc99d..803e135260 100644 --- a/test/Microsoft.AspNetCore.Blazor.Build.Test/RuntimeCodeGenerationTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/RuntimeCodeGenerationTest.cs @@ -3,7 +3,6 @@ using Microsoft.CodeAnalysis.CSharp; using Xunit; -using Xunit.Abstractions; namespace Microsoft.AspNetCore.Blazor.Build.Test { @@ -276,7 +275,23 @@ namespace Test } [Fact] - public void EventHandler_OnElement_WithLambdaDelegate() + public void EventHandler_OnElement_WithNoArgsLambdaDelegate() + { + // Arrange + + // Act + var generated = CompileToCSharp(@" +@using Microsoft.AspNetCore.Blazor + { })"" />"); + + // Assert + AssertDocumentNodeMatchesBaseline(generated.CodeDocument); + AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); + CompileToAssembly(generated); + } + + [Fact] + public void EventHandler_OnElement_WithEventArgsLambdaDelegate() { // Arrange @@ -292,7 +307,27 @@ namespace Test } [Fact] - public void EventHandler_OnElement_WithDelegate() + public void EventHandler_OnElement_WithNoArgMethodGroup() + { + // Arrange + + // Act + var generated = CompileToCSharp(@" +@using Microsoft.AspNetCore.Blazor + +@functions { + void OnClick() { + } +}"); + + // Assert + AssertDocumentNodeMatchesBaseline(generated.CodeDocument); + AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); + CompileToAssembly(generated); + } + + [Fact] + public void EventHandler_OnElement_WithEventArgsMethodGroup() { // Arrange @@ -310,5 +345,25 @@ namespace Test AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); CompileToAssembly(generated); } + + [Fact] + public void EventHandler_OnElement_ArbitraryEventName_WithEventArgsMethodGroup() + { + // Arrange + + // Act + var generated = CompileToCSharp(@" +@using Microsoft.AspNetCore.Blazor + +@functions { + void OnClick(UIEventArgs e) { + } +}"); + + // Assert + AssertDocumentNodeMatchesBaseline(generated.CodeDocument); + AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); + CompileToAssembly(generated); + } } } diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_ArbitraryEventName_WithEventArgsMethodGroup/TestComponent.codegen.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_ArbitraryEventName_WithEventArgsMethodGroup/TestComponent.codegen.cs new file mode 100644 index 0000000000..8fc20f2e4b --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_ArbitraryEventName_WithEventArgsMethodGroup/TestComponent.codegen.cs @@ -0,0 +1,32 @@ +// +#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; + 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.OpenElement(0, "input"); + builder.AddAttribute(1, "onclick", Microsoft.AspNetCore.Blazor.Components.BindMethods.GetEventHandlerValue(OnClick)); + builder.CloseElement(); + builder.AddContent(2, "\n"); + } + #pragma warning restore 1998 +#line 3 "x:\dir\subdir\Test\TestComponent.cshtml" + + void OnClick(UIEventArgs e) { + } + +#line default +#line hidden + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_ArbitraryEventName_WithEventArgsMethodGroup/TestComponent.ir.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_ArbitraryEventName_WithEventArgsMethodGroup/TestComponent.ir.txt new file mode 100644 index 0000000000..b13779114e --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_ArbitraryEventName_WithEventArgsMethodGroup/TestComponent.ir.txt @@ -0,0 +1,24 @@ +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 - (1:0,1 [35] x:\dir\subdir\Test\TestComponent.cshtml) - Microsoft.AspNetCore.Blazor + ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent - + MethodDeclaration - - protected override - void - BuildRenderTree + CSharpCode - + IntermediateToken - - CSharp - base.BuildRenderTree(builder); + HtmlContent - + IntermediateToken - - Html - ( + IntermediateToken - (53:1,17 [7] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - OnClick + IntermediateToken - - CSharp - ) + HtmlContent - + IntermediateToken - - Html - /> + HtmlContent - (64:1,28 [2] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (64:1,28 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + CSharpCode - (78:2,12 [44] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (78:2,12 [44] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n void OnClick(UIEventArgs e) {\n }\n diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_ArbitraryEventName_WithEventArgsMethodGroup/TestComponent.mappings.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_ArbitraryEventName_WithEventArgsMethodGroup/TestComponent.mappings.txt new file mode 100644 index 0000000000..c9c49e4d75 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_ArbitraryEventName_WithEventArgsMethodGroup/TestComponent.mappings.txt @@ -0,0 +1,11 @@ +Source Location: (78:2,12 [44] x:\dir\subdir\Test\TestComponent.cshtml) +| + void OnClick(UIEventArgs e) { + } +| +Generated Location: (964:23,12 [44] ) +| + void OnClick(UIEventArgs e) { + } +| + diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsLambdaDelegate/TestComponent.codegen.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsLambdaDelegate/TestComponent.codegen.cs new file mode 100644 index 0000000000..7e58c05413 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsLambdaDelegate/TestComponent.codegen.cs @@ -0,0 +1,24 @@ +// +#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; + 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.OpenElement(0, "input"); + builder.AddAttribute(1, "onclick", Microsoft.AspNetCore.Blazor.Components.BindMethods.GetEventHandlerValue(x => { })); + builder.CloseElement(); + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsLambdaDelegate/TestComponent.ir.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsLambdaDelegate/TestComponent.ir.txt new file mode 100644 index 0000000000..58a90a079b --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsLambdaDelegate/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 - (1:0,1 [35] x:\dir\subdir\Test\TestComponent.cshtml) - Microsoft.AspNetCore.Blazor + ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent - + MethodDeclaration - - protected override - void - BuildRenderTree + CSharpCode - + IntermediateToken - - CSharp - base.BuildRenderTree(builder); + HtmlContent - + IntermediateToken - - Html - ( + IntermediateToken - (54:1,18 [8] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - x => { } + IntermediateToken - - CSharp - ) + HtmlContent - + IntermediateToken - - Html - /> diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsMethodGroup/TestComponent.codegen.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsMethodGroup/TestComponent.codegen.cs new file mode 100644 index 0000000000..b3f3686b47 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsMethodGroup/TestComponent.codegen.cs @@ -0,0 +1,32 @@ +// +#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; + 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.OpenElement(0, "input"); + builder.AddAttribute(1, "onclick", Microsoft.AspNetCore.Blazor.Components.BindMethods.GetEventHandlerValue(OnClick)); + builder.CloseElement(); + builder.AddContent(2, "\n"); + } + #pragma warning restore 1998 +#line 3 "x:\dir\subdir\Test\TestComponent.cshtml" + + void OnClick(UIMouseEventArgs e) { + } + +#line default +#line hidden + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsMethodGroup/TestComponent.ir.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsMethodGroup/TestComponent.ir.txt new file mode 100644 index 0000000000..ecf6331fca --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsMethodGroup/TestComponent.ir.txt @@ -0,0 +1,24 @@ +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 - (1:0,1 [35] x:\dir\subdir\Test\TestComponent.cshtml) - Microsoft.AspNetCore.Blazor + ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent - + MethodDeclaration - - protected override - void - BuildRenderTree + CSharpCode - + IntermediateToken - - CSharp - base.BuildRenderTree(builder); + HtmlContent - + IntermediateToken - - Html - ( + IntermediateToken - (53:1,17 [7] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - OnClick + IntermediateToken - - CSharp - ) + HtmlContent - + IntermediateToken - - Html - /> + HtmlContent - (64:1,28 [2] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (64:1,28 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + CSharpCode - (78:2,12 [49] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (78:2,12 [49] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n void OnClick(UIMouseEventArgs e) {\n }\n diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsMethodGroup/TestComponent.mappings.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsMethodGroup/TestComponent.mappings.txt new file mode 100644 index 0000000000..2b525eee21 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithEventArgsMethodGroup/TestComponent.mappings.txt @@ -0,0 +1,11 @@ +Source Location: (78:2,12 [49] x:\dir\subdir\Test\TestComponent.cshtml) +| + void OnClick(UIMouseEventArgs e) { + } +| +Generated Location: (964:23,12 [49] ) +| + void OnClick(UIMouseEventArgs e) { + } +| + diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgMethodGroup/TestComponent.codegen.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgMethodGroup/TestComponent.codegen.cs new file mode 100644 index 0000000000..0806dc3c97 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgMethodGroup/TestComponent.codegen.cs @@ -0,0 +1,32 @@ +// +#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; + 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.OpenElement(0, "input"); + builder.AddAttribute(1, "onclick", Microsoft.AspNetCore.Blazor.Components.BindMethods.GetEventHandlerValue(OnClick)); + builder.CloseElement(); + builder.AddContent(2, "\n"); + } + #pragma warning restore 1998 +#line 3 "x:\dir\subdir\Test\TestComponent.cshtml" + + void OnClick() { + } + +#line default +#line hidden + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgMethodGroup/TestComponent.ir.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgMethodGroup/TestComponent.ir.txt new file mode 100644 index 0000000000..834c9e596d --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgMethodGroup/TestComponent.ir.txt @@ -0,0 +1,24 @@ +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 - (1:0,1 [35] x:\dir\subdir\Test\TestComponent.cshtml) - Microsoft.AspNetCore.Blazor + ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent - + MethodDeclaration - - protected override - void - BuildRenderTree + CSharpCode - + IntermediateToken - - CSharp - base.BuildRenderTree(builder); + HtmlContent - + IntermediateToken - - Html - ( + IntermediateToken - (53:1,17 [7] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - OnClick + IntermediateToken - - CSharp - ) + HtmlContent - + IntermediateToken - - Html - /> + HtmlContent - (64:1,28 [2] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (64:1,28 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + CSharpCode - (78:2,12 [31] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (78:2,12 [31] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n void OnClick() {\n }\n diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgMethodGroup/TestComponent.mappings.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgMethodGroup/TestComponent.mappings.txt new file mode 100644 index 0000000000..2c1801830d --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgMethodGroup/TestComponent.mappings.txt @@ -0,0 +1,11 @@ +Source Location: (78:2,12 [31] x:\dir\subdir\Test\TestComponent.cshtml) +| + void OnClick() { + } +| +Generated Location: (964:23,12 [31] ) +| + void OnClick() { + } +| + diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgsLambdaDelegate/TestComponent.codegen.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgsLambdaDelegate/TestComponent.codegen.cs new file mode 100644 index 0000000000..4b3633feb2 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgsLambdaDelegate/TestComponent.codegen.cs @@ -0,0 +1,24 @@ +// +#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; + 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.OpenElement(0, "input"); + builder.AddAttribute(1, "onclick", Microsoft.AspNetCore.Blazor.Components.BindMethods.GetEventHandlerValue(() => { })); + builder.CloseElement(); + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgsLambdaDelegate/TestComponent.ir.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgsLambdaDelegate/TestComponent.ir.txt new file mode 100644 index 0000000000..0210b39f1c --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/EventHandler_OnElement_WithNoArgsLambdaDelegate/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 - (1:0,1 [35] x:\dir\subdir\Test\TestComponent.cshtml) - Microsoft.AspNetCore.Blazor + ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent - + MethodDeclaration - - protected override - void - BuildRenderTree + CSharpCode - + IntermediateToken - - CSharp - base.BuildRenderTree(builder); + HtmlContent - + IntermediateToken - - Html - ( + IntermediateToken - (54:1,18 [9] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - () => { } + IntermediateToken - - CSharp - ) + HtmlContent - + IntermediateToken - - Html - /> diff --git a/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeBuilderTest.cs b/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeBuilderTest.cs index 7da569fdc1..56c642583c 100644 --- a/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeBuilderTest.cs @@ -498,7 +498,7 @@ namespace Microsoft.AspNetCore.Blazor.Test } [Fact] - public void AddAttribute_Element_EventHandler_AddsFrame() + public void AddAttribute_Element_UIEventHandler_AddsFrame() { // Arrange var builder = new RenderTreeBuilder(new TestRenderer()); @@ -518,7 +518,7 @@ namespace Microsoft.AspNetCore.Blazor.Test } [Fact] - public void AddAttribute_Element_NullEventHandler_IgnoresFrame() + public void AddAttribute_Element_NullUIEventHandler_IgnoresFrame() { // Arrange var builder = new RenderTreeBuilder(new TestRenderer()); @@ -534,6 +534,43 @@ namespace Microsoft.AspNetCore.Blazor.Test frame => AssertFrame.Element(frame, "elem", 1, 0)); } + [Fact] + public void AddAttribute_Element_Action_AddsFrame() + { + // Arrange + var builder = new RenderTreeBuilder(new TestRenderer()); + + var value = new Action(() => { }); + + // Act + builder.OpenElement(0, "elem"); + builder.AddAttribute(1, "attr", value); + builder.CloseElement(); + + // Assert + Assert.Collection( + builder.GetFrames(), + frame => AssertFrame.Element(frame, "elem", 2, 0), + frame => AssertFrame.Attribute(frame, "attr", value, 1)); + } + + [Fact] + public void AddAttribute_Element_NullAction_IgnoresFrame() + { + // Arrange + var builder = new RenderTreeBuilder(new TestRenderer()); + + // Act + builder.OpenElement(0, "elem"); + builder.AddAttribute(1, "attr", (Action)null); + builder.CloseElement(); + + // Assert + Assert.Collection( + builder.GetFrames(), + frame => AssertFrame.Element(frame, "elem", 1, 0)); + } + public static TheoryData UIEventHandlerValues => new TheoryData { null, @@ -651,7 +688,7 @@ namespace Microsoft.AspNetCore.Blazor.Test } [Fact] - public void AddAttribute_Element_ObjectEventHandler_AddsFrame() + public void AddAttribute_Element_ObjectUIEventHandler_AddsFrame() { // Arrange var builder = new RenderTreeBuilder(new TestRenderer()); @@ -671,7 +708,7 @@ namespace Microsoft.AspNetCore.Blazor.Test } [Fact] - public void AddAttribute_Component_ObjectEventHandleValue_SetsAttributeValue() + public void AddAttribute_Component_ObjectUIEventHandleValue_SetsAttributeValue() { // Arrange var builder = new RenderTreeBuilder(new TestRenderer()); @@ -690,6 +727,46 @@ namespace Microsoft.AspNetCore.Blazor.Test frame => AssertFrame.Attribute(frame, "attr", value, 1)); } + [Fact] + public void AddAttribute_Element_ObjectAction_AddsFrame() + { + // Arrange + var builder = new RenderTreeBuilder(new TestRenderer()); + + var value = new Action(() => { }); + + // Act + builder.OpenElement(0, "elem"); + builder.AddAttribute(1, "attr", (object)value); + builder.CloseElement(); + + // Assert + Assert.Collection( + builder.GetFrames(), + frame => AssertFrame.Element(frame, "elem", 2, 0), + frame => AssertFrame.Attribute(frame, "attr", value, 1)); + } + + [Fact] + public void AddAttribute_Component_ObjectAction_SetsAttributeValue() + { + // Arrange + var builder = new RenderTreeBuilder(new TestRenderer()); + + var value = new Action(() => { }); + + // Act + builder.OpenComponent(0); + builder.AddAttribute(1, "attr", (object)value); + builder.CloseComponent(); + + // Assert + Assert.Collection( + builder.GetFrames(), + frame => AssertFrame.Component(frame, 2, 0), + frame => AssertFrame.Attribute(frame, "attr", value, 1)); + } + [Fact] public void AddAttribute_Element_ObjectNull_IgnoresFrame() { diff --git a/test/Microsoft.AspNetCore.Blazor.Test/RendererTest.cs b/test/Microsoft.AspNetCore.Blazor.Test/RendererTest.cs index 019f6f988b..d505bded92 100644 --- a/test/Microsoft.AspNetCore.Blazor.Test/RendererTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.Test/RendererTest.cs @@ -217,6 +217,34 @@ namespace Microsoft.AspNetCore.Blazor.Test Assert.Same(eventArgs, receivedArgs); } + [Fact] + public void CanDispatchActionEventsToTopLevelComponents() + { + // Arrange: Render a component with an event handler + var renderer = new TestRenderer(); + object receivedArgs = null; + + var component = new EventComponent + { + OnClickAction = () => { receivedArgs = new object(); } + }; + var componentId = renderer.AssignComponentId(component); + component.TriggerRender(); + + var eventHandlerId = renderer.Batches.Single() + .ReferenceFrames + .First(frame => frame.AttributeValue != null) + .AttributeEventHandlerId; + + // Assert: Event not yet fired + Assert.Null(receivedArgs); + + // Act/Assert: Event can be fired + var eventArgs = new UIMouseEventArgs(); + renderer.DispatchEvent(componentId, eventHandlerId, eventArgs); + Assert.NotNull(receivedArgs); + } + [Fact] public void CanDispatchEventsToNestedComponents() { @@ -955,6 +983,7 @@ namespace Microsoft.AspNetCore.Blazor.Test { public UIEventHandler OnTest { get; set; } public UIMouseEventHandler OnClick { get; set; } + public Action OnClickAction { get; set; } public bool SkipElement { get; set; } private int renderCount = 0; @@ -974,11 +1003,15 @@ namespace Microsoft.AspNetCore.Blazor.Test { builder.AddAttribute(4, "onclick", OnClick); } + if (OnClickAction != null) + { + builder.AddAttribute(5, "onclickaction", OnClickAction); + } builder.CloseElement(); builder.CloseElement(); } builder.CloseElement(); - builder.AddContent(5, $"Render count: {++renderCount}"); + builder.AddContent(6, $"Render count: {++renderCount}"); } public void HandleEvent(UIEventHandler handler, UIEventArgs args) diff --git a/test/testapps/BasicTestApp/AddRemoveChildComponents.cshtml b/test/testapps/BasicTestApp/AddRemoveChildComponents.cshtml index 92cf8f84bf..3603cbb45f 100644 --- a/test/testapps/BasicTestApp/AddRemoveChildComponents.cshtml +++ b/test/testapps/BasicTestApp/AddRemoveChildComponents.cshtml @@ -1,5 +1,4 @@ @using System.Collections.Generic -@using Microsoft.AspNetCore.Blazor Child components follow. @@ -13,13 +12,13 @@ Child components follow. int numAdded = 0; List currentChildrenMessages = new List(); - void AddChild(UIMouseEventArgs e) + void AddChild() { numAdded++; currentChildrenMessages.Add($"Child {numAdded}"); } - void RemoveChild(UIMouseEventArgs e) + void RemoveChild() { if (currentChildrenMessages.Count > 0) { diff --git a/test/testapps/BasicTestApp/BindCasesComponent.cshtml b/test/testapps/BasicTestApp/BindCasesComponent.cshtml index b0b1d5058f..4172ef4b55 100644 --- a/test/testapps/BasicTestApp/BindCasesComponent.cshtml +++ b/test/testapps/BasicTestApp/BindCasesComponent.cshtml @@ -64,7 +64,7 @@ enum SelectableValue { First, Second, Third, Fourth } SelectableValue selectValue = SelectableValue.Second; - void AddAndSelectNewSelectOption(UIMouseEventArgs e) + void AddAndSelectNewSelectOption() { includeFourthOption = true; selectValue = SelectableValue.Fourth; diff --git a/test/testapps/BasicTestApp/CounterComponent.cshtml b/test/testapps/BasicTestApp/CounterComponent.cshtml index 6edcd76211..5fc87328a9 100644 --- a/test/testapps/BasicTestApp/CounterComponent.cshtml +++ b/test/testapps/BasicTestApp/CounterComponent.cshtml @@ -1,7 +1,6 @@ -@using Microsoft.AspNetCore.Blazor -

Counter

+

Counter

Current count: @currentCount

-

+