Fix #954 - use weak typing for 'event handlers'

This change introduces a mechanism for bypassing type checking and then
uses for the 'event handlers'. The event handler tag helpers have some
ideosyncratic behaviors and rely on overloading at the render tree
builder level.
This commit is contained in:
Ryan Nowak 2018-07-05 08:26:46 -07:00
parent 97d044e479
commit 45544858a3
14 changed files with 265 additions and 12 deletions

View File

@ -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(")");
}

View File

@ -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";

View File

@ -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(")");
}

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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
<DynamicElement onclick=""@OnClick"" />
@functions {
private Action<UIMouseEventArgs> OnClick { get; set; }
}");
// Assert
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
CompileToAssembly(generated);
}
[Fact]
public void ChildComponent_WithExplicitEventHandler()
{

View File

@ -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
<DynamicElement onclick=""@OnClick"" />
@functions {
private Action<UIMouseEventArgs> 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;

View File

@ -0,0 +1,49 @@
// <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 = Microsoft.AspNetCore.Blazor.Components.BindMethods.GetEventHandlerValue<Microsoft.AspNetCore.Blazor.UIMouseEventArgs>(
#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<UIMouseEventArgs> OnClick { get; set; }
#line default
#line hidden
}
}
#pragma warning restore 1591

View File

@ -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<Microsoft.AspNetCore.Blazor.UIMouseEventArgs>(
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<UIMouseEventArgs> OnClick { get; set; }\n

View File

@ -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<UIMouseEventArgs> OnClick { get; set; }
|
Generated Location: (1516:41,12 [62] )
|
private Action<UIMouseEventArgs> OnClick { get; set; }
|

View File

@ -0,0 +1,31 @@
// <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.DynamicElement>(0);
builder.AddAttribute(1, "onclick", Microsoft.AspNetCore.Blazor.Components.BindMethods.GetEventHandlerValue<Microsoft.AspNetCore.Blazor.UIMouseEventArgs>(OnClick));
builder.CloseComponent();
}
#pragma warning restore 1998
#line 4 "x:\dir\subdir\Test\TestComponent.cshtml"
private Action<UIMouseEventArgs> OnClick { get; set; }
#line default
#line hidden
}
}
#pragma warning restore 1591

View File

@ -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<Microsoft.AspNetCore.Blazor.UIMouseEventArgs>(
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<UIMouseEventArgs> OnClick { get; set; }\n

View File

@ -0,0 +1,9 @@
Source Location: (84:3,12 [62] x:\dir\subdir\Test\TestComponent.cshtml)
|
private Action<UIMouseEventArgs> OnClick { get; set; }
|
Generated Location: (989:23,12 [62] )
|
private Action<UIMouseEventArgs> OnClick { get; set; }
|

View File

@ -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<string, string>(BlazorMetadata.Component.WeaklyTypedKey, bool.TrueString)),
kvp => Assert.Equal(kvp, new KeyValuePair<string, string>("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<Microsoft.AspNetCore.Blazor.UIMouseEventArgs>'.",