diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/BlazorMetadata.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/BlazorMetadata.cs index 1c15fd28fa..32a52899e8 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/BlazorMetadata.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/BlazorMetadata.cs @@ -55,6 +55,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Components public static readonly string DelegateSignatureKey = "Blazor.DelegateSignature"; + public static readonly string EventCallbackKey = "Blazor.EventCallback"; + public static readonly string WeaklyTypedKey = "Blazor.IsWeaklyTyped"; public static readonly string RuntimeName = "Blazor.IComponent"; diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentBindLoweringPass.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentBindLoweringPass.cs index cd3c831ae6..a02fd59235 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentBindLoweringPass.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentBindLoweringPass.cs @@ -158,9 +158,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Components // there will be errors. In general the errors that come from C# in this case are good enough // to understand the problem. // - // The BindMethods calls are required in this case because to give us a good experience. They + // We also support and encourage the use of EventCallback<> with bind. So in the above example + // the ValueChanged property could be an Action<> or an EventCallback<>. + // + // The BindMethods calls are required with Action<> because to give us a good experience. They // use overloading to ensure that can get an Action that will convert and set an arbitrary - // value. + // value. We have a similar set of APIs to use with EventCallback<>. // // We also assume that the element will be treated as a component for now because // multiple passes handle 'special' tag helpers. We have another pass that translates @@ -207,67 +210,28 @@ namespace Microsoft.AspNetCore.Razor.Language.Components format = GetAttributeContent(formatNode); } - // Now rewrite the content of the value node to look like: - // - // BindMethods.GetValue() OR - // BindMethods.GetValue(, ) var valueExpressionTokens = new List(); - valueExpressionTokens.Add(new IntermediateToken() + var changeExpressionTokens = new List(); + if (changeAttribute != null && changeAttribute.IsDelegateProperty()) { - Content = $"{ComponentsApi.BindMethods.GetValue}(", - Kind = TokenKind.CSharp - }); - valueExpressionTokens.Add(original); - if (!string.IsNullOrEmpty(format?.Content)) - { - valueExpressionTokens.Add(new IntermediateToken() - { - Content = ", ", - Kind = TokenKind.CSharp, - }); - valueExpressionTokens.Add(format); - } - valueExpressionTokens.Add(new IntermediateToken() - { - Content = ")", - Kind = TokenKind.CSharp, - }); - - // Now rewrite the content of the change-handler node. There are two cases we care about - // here. If it's a component attribute, then don't use the 'BindMethods wrapper. We expect - // component attributes to always 'match' on type. - // - // __value => = __value - // - // For general DOM attributes, we need to be able to create a delegate that accepts UIEventArgs - // so we use BindMethods.SetValueHandler - // - // BindMethods.SetValueHandler(__value => = __value, ) OR - // BindMethods.SetValueHandler(__value => = __value, , ) - // - // Note that the linemappings here are applied to the value attribute, not the change attribute. - - string changeExpressionContent = null; - if (changeAttribute == null && format == null) - { - changeExpressionContent = $"{ComponentsApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content})"; - } - else if (changeAttribute == null && format != null) - { - changeExpressionContent = $"{ComponentsApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content}, {format.Content})"; + RewriteNodesForDelegateBind( + original, + format, + valueAttribute, + changeAttribute, + valueExpressionTokens, + changeExpressionTokens); } else { - changeExpressionContent = $"__value => {original.Content} = __value"; + RewriteNodesForEventCallbackBind( + original, + format, + valueAttribute, + changeAttribute, + valueExpressionTokens, + changeExpressionTokens); } - var changeExpressionTokens = new List() - { - new IntermediateToken() - { - Content = changeExpressionContent, - Kind = TokenKind.CSharp - } - }; if (parent is MarkupElementIntermediateNode) { @@ -521,6 +485,151 @@ namespace Microsoft.AspNetCore.Razor.Language.Components return false; } + private void RewriteNodesForDelegateBind( + IntermediateToken original, + IntermediateToken format, + BoundAttributeDescriptor valueAttribute, + BoundAttributeDescriptor changeAttribute, + List valueExpressionTokens, + List changeExpressionTokens) + { + // Now rewrite the content of the value node to look like: + // + // BindMethods.GetValue() OR + // BindMethods.GetValue(, ) + valueExpressionTokens.Add(new IntermediateToken() + { + Content = $"{ComponentsApi.BindMethods.GetValue}(", + Kind = TokenKind.CSharp + }); + valueExpressionTokens.Add(original); + if (!string.IsNullOrEmpty(format?.Content)) + { + valueExpressionTokens.Add(new IntermediateToken() + { + Content = ", ", + Kind = TokenKind.CSharp, + }); + valueExpressionTokens.Add(format); + } + valueExpressionTokens.Add(new IntermediateToken() + { + Content = ")", + Kind = TokenKind.CSharp, + }); + + // Now rewrite the content of the change-handler node. There are two cases we care about + // here. If it's a component attribute, then don't use the 'BindMethods' wrapper. We expect + // component attributes to always 'match' on type. + // + // __value => = __value + // + // For general DOM attributes, we need to be able to create a delegate that accepts UIEventArgs + // so we use BindMethods.SetValueHandler + // + // BindMethods.SetValueHandler(__value => = __value, ) OR + // BindMethods.SetValueHandler(__value => = __value, , ) + // + // Note that the linemappings here are applied to the value attribute, not the change attribute. + + string changeExpressionContent; + if (changeAttribute == null && format == null) + { + // DOM + changeExpressionContent = $"{ComponentsApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content})"; + } + else if (changeAttribute == null && format != null) + { + // DOM + format + changeExpressionContent = $"{ComponentsApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content}, {format.Content})"; + } + else + { + // Component + changeExpressionContent = $"__value => {original.Content} = __value"; + } + changeExpressionTokens.Add(new IntermediateToken() + { + Content = changeExpressionContent, + Kind = TokenKind.CSharp + }); + } + + private void RewriteNodesForEventCallbackBind( + IntermediateToken original, + IntermediateToken format, + BoundAttributeDescriptor valueAttribute, + BoundAttributeDescriptor changeAttribute, + List valueExpressionTokens, + List changeExpressionTokens) + { + // Now rewrite the content of the value node to look like: + // + // BindMethods.GetValue() OR + // BindMethods.GetValue(, ) + valueExpressionTokens.Add(new IntermediateToken() + { + Content = $"{ComponentsApi.BindMethods.GetValue}(", + Kind = TokenKind.CSharp + }); + valueExpressionTokens.Add(original); + if (!string.IsNullOrEmpty(format?.Content)) + { + valueExpressionTokens.Add(new IntermediateToken() + { + Content = ", ", + Kind = TokenKind.CSharp, + }); + valueExpressionTokens.Add(format); + } + valueExpressionTokens.Add(new IntermediateToken() + { + Content = ")", + Kind = TokenKind.CSharp, + }); + + // Now rewrite the content of the change-handler node. There are two cases we care about + // here. If it's a component attribute, then don't use the 'CreateBinder' wrapper. We expect + // component attributes to always 'match' on type. + // + // The really tricky part of this is that we CANNOT write the type name of of the EventCallback we + // intend to create. Doing so would really complicate the story for how we deal with generic types, + // since the generic type lowering pass runs after this. To keep this simple we're relying on + // the compiler to resolve overloads for us. + // + // EventCallbackFactory.CreateInferred(this, __value => = __value, ) + // + // For general DOM attributes, we need to be able to create a delegate that accepts UIEventArgs + // so we use 'CreateBinder' + // + // EventCallbackFactory.CreateBinder(this, __value => = __value, ) OR + // EventCallbackFactory.CreateBinder(this, __value => = __value, , ) + // + // Note that the linemappings here are applied to the value attribute, not the change attribute. + + string changeExpressionContent; + if (changeAttribute == null && format == null) + { + // DOM + changeExpressionContent = $"{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateBinderMethod}(this, __value => {original.Content} = __value, {original.Content})"; + } + else if (changeAttribute == null && format != null) + { + // DOM + format + changeExpressionContent = $"{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateBinderMethod}(this, __value => {original.Content} = __value, {original.Content}, {format.Content})"; + } + else + { + // Component + changeExpressionContent = $"{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateInferredMethod}(this, __value => {original.Content} = __value, {original.Content})"; + } + changeExpressionTokens.Add(new IntermediateToken() + { + Content = changeExpressionContent, + Kind = TokenKind.CSharp + }); + } + private static IntermediateToken GetAttributeContent(TagHelperPropertyIntermediateNode node) { var template = node.FindDescendantNodes().FirstOrDefault(); diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDesignTimeNodeWriter.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDesignTimeNodeWriter.cs index da3c03b2d8..62c1fab448 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDesignTimeNodeWriter.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDesignTimeNodeWriter.cs @@ -541,6 +541,55 @@ namespace Microsoft.AspNetCore.Razor.Language.Components context.CodeWriter.Write(")"); } } + else if (node.BoundAttribute?.IsEventCallbackProperty() ?? false) + { + // This is the case where we are writing an EventCallback (a delegate with super-powers). + // + // An event callback can either be passed verbatim, or it can be created by the EventCallbackFactory. + // Since we don't look at the code the user typed inside the attribute value, this is always + // resolved via overloading. + + if (canTypeCheck && NeedsTypeCheck(node)) + { + context.CodeWriter.Write(ComponentsApi.RuntimeHelpers.TypeCheck); + context.CodeWriter.Write("<"); + context.CodeWriter.Write(node.TypeName); + context.CodeWriter.Write(">"); + context.CodeWriter.Write("("); + } + + // Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, ...) OR + // Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, ...) + + context.CodeWriter.Write(ComponentsApi.EventCallback.FactoryAccessor); + context.CodeWriter.Write("."); + context.CodeWriter.Write(ComponentsApi.EventCallbackFactory.CreateMethod); + + if (node.TryParseEventCallbackTypeArgument(out var argument)) + { + context.CodeWriter.Write("<"); + context.CodeWriter.Write(argument); + context.CodeWriter.Write(">"); + } + + context.CodeWriter.Write("("); + context.CodeWriter.Write("this"); + context.CodeWriter.Write(", "); + + context.CodeWriter.WriteLine(); + + for (var i = 0; i < tokens.Count; i++) + { + WriteCSharpToken(context, tokens[i]); + } + + context.CodeWriter.Write(")"); + + if (canTypeCheck && NeedsTypeCheck(node)) + { + context.CodeWriter.Write(")"); + } + } else { // This is the case when an attribute contains C# code diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentEventHandlerLoweringPass.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentEventHandlerLoweringPass.cs index 7a731dabdd..aeefbab150 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentEventHandlerLoweringPass.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentEventHandlerLoweringPass.cs @@ -123,16 +123,16 @@ namespace Microsoft.AspNetCore.Razor.Language.Components // Now rewrite the content of the value node to look like: // - // BindMethods.GetEventHandlerValue() + // EventCallback.Factory.Create(this, ) // - // This method is overloaded on string and TDelegate, which means that it will put the code in the + // This method is overloaded on string and T, which means that it will put the code in the // correct context for intellisense when typing in the attribute. var eventArgsType = node.TagHelper.GetEventArgsType(); var tokens = new List() { new IntermediateToken() { - Content = $"{ComponentsApi.BindMethods.GetEventHandlerValue}<{eventArgsType}>(", + Content = $"{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateMethod}<{eventArgsType}>(this, ", Kind = TokenKind.CSharp }, new IntermediateToken() diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentGenericTypePass.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentGenericTypePass.cs index a3646ba767..a7709d3116 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentGenericTypePass.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentGenericTypePass.cs @@ -218,6 +218,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Components // This is a weakly typed delegate, treat it as Action attribute.TypeName = "System.Action"; } + else if (attribute.TypeName == null && (attribute.BoundAttribute?.IsEventCallbackProperty() ?? false)) + { + // This is a weakly typed event-callback, treat it as EventCallback (non-generic) + attribute.TypeName = ComponentsApi.EventCallback.FullTypeName; + } else if (attribute.TypeName == null) { // This is a weakly typed attribute, treat it as System.Object diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentRuntimeNodeWriter.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentRuntimeNodeWriter.cs index 8fcf48ec18..96aa4ceb79 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentRuntimeNodeWriter.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentRuntimeNodeWriter.cs @@ -460,7 +460,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components } else { - // See comments in BlazorDesignTimeNodeWriter for a description of the cases that are possible. + // See comments in ComponentDesignTimeNodeWriter for a description of the cases that are possible. var tokens = GetCSharpTokens(node); if ((node.BoundAttribute?.IsDelegateProperty() ?? false) || (node.BoundAttribute?.IsChildContentProperty() ?? false)) @@ -482,6 +482,47 @@ namespace Microsoft.AspNetCore.Razor.Language.Components context.CodeWriter.Write(")"); } } + else if (node.BoundAttribute?.IsEventCallbackProperty() ?? false) + { + if (canTypeCheck && NeedsTypeCheck(node)) + { + context.CodeWriter.Write(ComponentsApi.RuntimeHelpers.TypeCheck); + context.CodeWriter.Write("<"); + context.CodeWriter.Write(node.TypeName); + context.CodeWriter.Write(">"); + context.CodeWriter.Write("("); + } + + // Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, ...) OR + // Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, ...) + + context.CodeWriter.Write(ComponentsApi.EventCallback.FactoryAccessor); + context.CodeWriter.Write("."); + context.CodeWriter.Write(ComponentsApi.EventCallbackFactory.CreateMethod); + + if (node.TryParseEventCallbackTypeArgument(out var argument)) + { + context.CodeWriter.Write("<"); + context.CodeWriter.Write(argument); + context.CodeWriter.Write(">"); + } + + context.CodeWriter.Write("("); + context.CodeWriter.Write("this"); + context.CodeWriter.Write(", "); + + for (var i = 0; i < tokens.Count; i++) + { + context.CodeWriter.Write(tokens[i].Content); + } + + context.CodeWriter.Write(")"); + + if (canTypeCheck && NeedsTypeCheck(node)) + { + context.CodeWriter.Write(")"); + } + } else { if (canTypeCheck && NeedsTypeCheck(node)) diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentsApi.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentsApi.cs index 8d3133b03c..59db7873dd 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentsApi.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentsApi.cs @@ -129,5 +129,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Components { public static readonly string FullTypeName = "Microsoft.AspNetCore.Components.ElementRef"; } + + public static class EventCallback + { + public static readonly string FullTypeName = "Microsoft.AspNetCore.Components.EventCallback"; + public static readonly string MetadataName = FullTypeName; + + public static readonly string FactoryAccessor = FullTypeName + ".Factory"; + } + + public static class EventCallbackOfT + { + public static readonly string MetadataName = "Microsoft.AspNetCore.Components.EventCallback`1"; + } + + public static class EventCallbackFactory + { + public static readonly string CreateMethod = "Create"; + public static readonly string CreateInferredMethod = "CreateInferred"; + public static readonly string CreateBinderMethod = "CreateBinder"; + } } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/TagHelperBoundAttributeDescriptorExtensions.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/TagHelperBoundAttributeDescriptorExtensions.cs index 07d0d538af..0a99a07b63 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/TagHelperBoundAttributeDescriptorExtensions.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/TagHelperBoundAttributeDescriptorExtensions.cs @@ -20,6 +20,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Components string.Equals(value, bool.TrueString); } + /// + /// Gets a value indicating whether the attribute is of type EventCallback or + /// EventCallback{T} + /// + /// The . + /// true if the attribute is an event callback, otherwise false. + public static bool IsEventCallbackProperty(this BoundAttributeDescriptor attribute) + { + if (attribute == null) + { + throw new ArgumentNullException(nameof(attribute)); + } + + var key = BlazorMetadata.Component.EventCallbackKey; + return + attribute.Metadata.TryGetValue(key, out var value) && + string.Equals(value, bool.TrueString); + } + public static bool IsGenericTypedProperty(this BoundAttributeDescriptor attribute) { if (attribute == null) diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/ComponentAttributeIntermediateNode.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/ComponentAttributeIntermediateNode.cs index 1b63ad3984..d5b94bde14 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/ComponentAttributeIntermediateNode.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Intermediate/ComponentAttributeIntermediateNode.cs @@ -101,5 +101,42 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate formatter.WriteProperty(nameof(TagHelper), TagHelper?.DisplayName); formatter.WriteProperty(nameof(TypeName), TypeName); } + + public bool TryParseEventCallbackTypeArgument(out string argument) + { + // This is ugly and ad-hoc, but for various layering reasons we can't just use Roslyn APIs + // to parse this. We need to parse this just before we write it out to the code generator, + // so we can't compute it up front either. + + if (BoundAttribute == null || !BoundAttribute.IsEventCallbackProperty()) + { + throw new InvalidOperationException("This attribute is not an EventCallback attribute."); + } + + if (string.Equals(TypeName, ComponentsApi.EventCallback.FullTypeName, StringComparison.Ordinal)) + { + // Non-Generic + argument = null; + return false; + } + + if (TypeName != null && + TypeName.Length > ComponentsApi.EventCallback.FullTypeName.Length + "<>".Length && + TypeName.StartsWith(ComponentsApi.EventCallback.FullTypeName, StringComparison.Ordinal) && + TypeName[ComponentsApi.EventCallback.FullTypeName.Length] == '<' && + TypeName[TypeName.Length - 1] == '>') + { + // OK this is promising. + // + // Chop off leading `...EventCallback<` and let the length so the ending `>` is cut off as well. + argument = TypeName.Substring(ComponentsApi.EventCallback.FullTypeName.Length + 1, TypeName.Length - (ComponentsApi.EventCallback.FullTypeName.Length + "<>".Length)); + return true; + } + + // If we get here this is a failure. This should only happen if someone manages to mangle the name with extensibility. + // We don't really want to crash though. + argument = null; + return false; + } } } diff --git a/src/Razor/Microsoft.CodeAnalysis.Razor/src/BindTagHelperDescriptorProvider.cs b/src/Razor/Microsoft.CodeAnalysis.Razor/src/BindTagHelperDescriptorProvider.cs index 97fd3b0483..8f62491145 100644 --- a/src/Razor/Microsoft.CodeAnalysis.Razor/src/BindTagHelperDescriptorProvider.cs +++ b/src/Razor/Microsoft.CodeAnalysis.Razor/src/BindTagHelperDescriptorProvider.cs @@ -33,7 +33,7 @@ namespace Microsoft.CodeAnalysis.Razor // We generate: // + // onchange="@EventCallbackFactory.CreateBinder(this, __value => FirstName = __value, FirstName)"/> // // This isn't very different from code the user could write themselves - thus the pronouncement // that bind is very much like a macro. @@ -356,7 +356,13 @@ namespace Microsoft.CodeAnalysis.Razor for (var i = 0; i < tagHelper.BoundAttributes.Count; i++) { var changeAttribute = tagHelper.BoundAttributes[i]; - if (!changeAttribute.Name.EndsWith("Changed") || !changeAttribute.IsDelegateProperty()) + if (!changeAttribute.Name.EndsWith("Changed") || + + // Allow the ValueChanged attribute to be a delegate or EventCallback<>. + // + // We assume that the Delegate or EventCallback<> has a matching type, and the C# compiler will help + // you figure figure it out if you did it wrongly. + (!changeAttribute.IsDelegateProperty() && !changeAttribute.IsEventCallbackProperty())) { continue; } @@ -367,7 +373,7 @@ namespace Microsoft.CodeAnalysis.Razor var expressionAttributeName = valueAttributeName + "Expression"; for (var j = 0; j < tagHelper.BoundAttributes.Count; j++) { - if (tagHelper.BoundAttributes[j].Name == valueAttributeName && !tagHelper.BoundAttributes[j].IsDelegateProperty()) + if (tagHelper.BoundAttributes[j].Name == valueAttributeName) { valueAttribute = tagHelper.BoundAttributes[j]; diff --git a/src/Razor/Microsoft.CodeAnalysis.Razor/src/ComponentTagHelperDescriptorProvider.cs b/src/Razor/Microsoft.CodeAnalysis.Razor/src/ComponentTagHelperDescriptorProvider.cs index 1474e86055..322c69e4cf 100644 --- a/src/Razor/Microsoft.CodeAnalysis.Razor/src/ComponentTagHelperDescriptorProvider.cs +++ b/src/Razor/Microsoft.CodeAnalysis.Razor/src/ComponentTagHelperDescriptorProvider.cs @@ -43,7 +43,7 @@ namespace Microsoft.CodeAnalysis.Razor // We need to see private members too compilation = WithMetadataImportOptionsAll(compilation); - var symbols = BlazorSymbols.Create(compilation); + var symbols = ComponentSymbols.Create(compilation); var types = new List(); var visitor = new ComponentTypeVisitor(symbols, types); @@ -81,7 +81,7 @@ namespace Microsoft.CodeAnalysis.Razor return compilation.WithOptions(newCompilationOptions); } - private TagHelperDescriptor CreateDescriptor(BlazorSymbols symbols, INamedTypeSymbol type) + private TagHelperDescriptor CreateDescriptor(ComponentSymbols symbols, INamedTypeSymbol type) { var typeName = type.ToDisplayString(FullNameTypeDisplayFormat); var assemblyName = type.ContainingAssembly.Identity.Name; @@ -157,6 +157,11 @@ namespace Microsoft.CodeAnalysis.Razor pb.Metadata.Add(BlazorMetadata.Component.ChildContentKey, bool.TrueString); } + if (kind == PropertyKind.EventCallback) + { + pb.Metadata.Add(BlazorMetadata.Component.EventCallbackKey, bool.TrueString); + } + if (kind == PropertyKind.Delegate) { pb.Metadata.Add(BlazorMetadata.Component.DelegateSignatureKey, bool.TrueString); @@ -230,7 +235,7 @@ namespace Microsoft.CodeAnalysis.Razor }); } - private TagHelperDescriptor CreateChildContentDescriptor(BlazorSymbols symbols, TagHelperDescriptor component, BoundAttributeDescriptor attribute) + private TagHelperDescriptor CreateChildContentDescriptor(ComponentSymbols symbols, TagHelperDescriptor component, BoundAttributeDescriptor attribute) { var typeName = component.GetTypeName() + "." + attribute.Name; var assemblyName = component.AssemblyName; @@ -297,7 +302,7 @@ namespace Microsoft.CodeAnalysis.Razor // - have the [Parameter] attribute // - have a setter, even if private // - are not indexers - private IEnumerable<(IPropertySymbol property, PropertyKind kind)> GetProperties(BlazorSymbols symbols, INamedTypeSymbol type) + private IEnumerable<(IPropertySymbol property, PropertyKind kind)> GetProperties(ComponentSymbols symbols, INamedTypeSymbol type) { var properties = new Dictionary(StringComparer.Ordinal); do @@ -368,6 +373,19 @@ namespace Microsoft.CodeAnalysis.Razor kind = PropertyKind.ChildContent; } + if (kind == PropertyKind.Default && property.Type == symbols.EventCallback) + { + kind = PropertyKind.EventCallback; + } + + if (kind == PropertyKind.Default && + property.Type is INamedTypeSymbol namedType2 && + namedType2.IsGenericType && + namedType2.ConstructedFrom == symbols.EventCallbackOfT) + { + kind = PropertyKind.EventCallback; + } + if (kind == PropertyKind.Default && property.Type.TypeKind == TypeKind.Delegate) { kind = PropertyKind.Delegate; @@ -390,13 +408,18 @@ namespace Microsoft.CodeAnalysis.Razor Enum, ChildContent, Delegate, + EventCallback, } - private class BlazorSymbols + private class ComponentSymbols { - public static BlazorSymbols Create(Compilation compilation) + public static ComponentSymbols Create(Compilation compilation) { - var symbols = new BlazorSymbols(); + // We find a bunch of important and fundamental types here that are needed to discover + // components. If one of these isn't defined then we just bail, because the results will + // be unpredictable. + var symbols = new ComponentSymbols(); + symbols.ComponentBase = compilation.GetTypeByMetadataName(ComponentsApi.ComponentBase.MetadataName); if (symbols.ComponentBase == null) { @@ -422,18 +445,34 @@ namespace Microsoft.CodeAnalysis.Razor if (symbols.RenderFragment == null) { // No definition for RenderFragment, nothing to do. + return null; } symbols.RenderFragmentOfT = compilation.GetTypeByMetadataName(ComponentsApi.RenderFragmentOfT.MetadataName); if (symbols.RenderFragmentOfT == null) { - // No definition for RenderFragment, nothing to do. + // No definition for RenderFragment, nothing to do. + return null; + } + + symbols.EventCallback = compilation.GetTypeByMetadataName(ComponentsApi.EventCallback.MetadataName); + if (symbols.EventCallback == null) + { + // No definition for EventCallback, nothing to do. + return null; + } + + symbols.EventCallbackOfT = compilation.GetTypeByMetadataName(ComponentsApi.EventCallbackOfT.MetadataName); + if (symbols.EventCallbackOfT == null) + { + // No definition for EventCallback, nothing to do. + return null; } return symbols; } - private BlazorSymbols() + private ComponentSymbols() { } @@ -446,14 +485,18 @@ namespace Microsoft.CodeAnalysis.Razor public INamedTypeSymbol RenderFragment { get; private set; } public INamedTypeSymbol RenderFragmentOfT { get; private set; } + + public INamedTypeSymbol EventCallback { get; private set; } + + public INamedTypeSymbol EventCallbackOfT { get; private set; } } private class ComponentTypeVisitor : SymbolVisitor { - private readonly BlazorSymbols _symbols; + private readonly ComponentSymbols _symbols; private readonly List _results; - public ComponentTypeVisitor(BlazorSymbols symbols, List results) + public ComponentTypeVisitor(ComponentSymbols symbols, List results) { _symbols = symbols; _results = results; diff --git a/src/Razor/Microsoft.CodeAnalysis.Razor/test/BindTagHelperDescriptorProviderTest.cs b/src/Razor/Microsoft.CodeAnalysis.Razor/test/BindTagHelperDescriptorProviderTest.cs index 6aa2b87c1d..be41787fd3 100644 --- a/src/Razor/Microsoft.CodeAnalysis.Razor/test/BindTagHelperDescriptorProviderTest.cs +++ b/src/Razor/Microsoft.CodeAnalysis.Razor/test/BindTagHelperDescriptorProviderTest.cs @@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.Razor public class BindTagHelperDescriptorProviderTest : BaseTagHelperDescriptorProviderTest { [Fact] - public void Execute_FindsBindTagHelperOnComponentType_CreatesDescriptor() + public void Execute_FindsBindTagHelperOnComponentType_Delegate_CreatesDescriptor() { // Arrange var compilation = BaseCompilation.AddSyntaxTrees(Parse(@" @@ -130,6 +130,120 @@ namespace Test Assert.False(attribute.IsEnum); } + [Fact] + public void Execute_FindsBindTagHelperOnComponentType_EventCallback_CreatesDescriptor() + { + // Arrange + var compilation = BaseCompilation.AddSyntaxTrees(Parse(@" +using Microsoft.AspNetCore.Components; + +namespace Test +{ + public class MyComponent : IComponent + { + public void Init(RenderHandle renderHandle) { } + + public void SetParameters(ParameterCollection parameters) { } + + [Parameter] + string MyProperty { get; set; } + + [Parameter] + EventCallback MyPropertyChanged { get; set; } + } +} +")); + + Assert.Empty(compilation.GetDiagnostics()); + + var context = TagHelperDescriptorProviderContext.Create(); + context.SetCompilation(compilation); + + // We run after component discovery and depend on the results. + var componentProvider = new ComponentTagHelperDescriptorProvider(); + componentProvider.Execute(context); + + var provider = new BindTagHelperDescriptorProvider(); + + // Act + provider.Execute(context); + + // Assert + var matches = GetBindTagHelpers(context); + var bind = Assert.Single(matches); + + // These are features Bind Tags Helpers don't use. Verifying them once here and + // then ignoring them. + Assert.Empty(bind.AllowedChildTags); + Assert.Null(bind.TagOutputHint); + + // These are features that are invariants of all Bind Tag Helpers. Verifying them once + // here and then ignoring them. + Assert.Empty(bind.Diagnostics); + Assert.False(bind.HasErrors); + Assert.Equal(BlazorMetadata.Bind.TagHelperKind, bind.Kind); + Assert.Equal(BlazorMetadata.Bind.RuntimeName, bind.Metadata[TagHelperMetadata.Runtime.Name]); + Assert.False(bind.IsDefaultKind()); + Assert.False(bind.KindUsesDefaultTagHelperRuntime()); + + Assert.Equal("MyProperty", bind.Metadata[BlazorMetadata.Bind.ValueAttribute]); + Assert.Equal("MyPropertyChanged", bind.Metadata[BlazorMetadata.Bind.ChangeAttribute]); + + Assert.Equal( + "Binds the provided expression to the 'MyProperty' property and a change event " + + "delegate to the 'MyPropertyChanged' property of the component.", + bind.Documentation); + + // These are all trivially derived from the assembly/namespace/type name + Assert.Equal("TestAssembly", bind.AssemblyName); + Assert.Equal("Test.MyComponent", bind.Name); + Assert.Equal("Test.MyComponent", bind.DisplayName); + Assert.Equal("Test.MyComponent", bind.GetTypeName()); + + var rule = Assert.Single(bind.TagMatchingRules); + Assert.Empty(rule.Diagnostics); + Assert.False(rule.HasErrors); + Assert.Null(rule.ParentTag); + Assert.Equal("MyComponent", rule.TagName); + Assert.Equal(TagStructure.Unspecified, rule.TagStructure); + + var requiredAttribute = Assert.Single(rule.Attributes); + Assert.Empty(requiredAttribute.Diagnostics); + Assert.Equal("bind-MyProperty", requiredAttribute.DisplayName); + Assert.Equal("bind-MyProperty", requiredAttribute.Name); + Assert.Equal(RequiredAttributeDescriptor.NameComparisonMode.FullMatch, requiredAttribute.NameComparison); + Assert.Null(requiredAttribute.Value); + Assert.Equal(RequiredAttributeDescriptor.ValueComparisonMode.None, requiredAttribute.ValueComparison); + + var attribute = Assert.Single(bind.BoundAttributes); + + // Invariants + Assert.Empty(attribute.Diagnostics); + Assert.False(attribute.HasErrors); + Assert.Equal(BlazorMetadata.Bind.TagHelperKind, attribute.Kind); + Assert.False(attribute.IsDefaultKind()); + Assert.False(attribute.HasIndexer); + Assert.Null(attribute.IndexerNamePrefix); + Assert.Null(attribute.IndexerTypeName); + Assert.False(attribute.IsIndexerBooleanProperty); + Assert.False(attribute.IsIndexerStringProperty); + + Assert.Equal( + "Binds the provided expression to the 'MyProperty' property and a change event " + + "delegate to the 'MyPropertyChanged' property of the component.", + attribute.Documentation); + + Assert.Equal("bind-MyProperty", attribute.Name); + Assert.Equal("MyProperty", attribute.GetPropertyName()); + Assert.Equal("string Test.MyComponent.MyProperty", attribute.DisplayName); + + // Defined from the property type + Assert.Equal("System.String", attribute.TypeName); + Assert.True(attribute.IsStringProperty); + Assert.False(attribute.IsBooleanProperty); + Assert.False(attribute.IsEnum); + } + [Fact] public void Execute_NoMatchedPropertiesOnComponent_IgnoresComponent() { diff --git a/src/Razor/Microsoft.CodeAnalysis.Razor/test/ComponentTagHelperDescriptorProviderTest.cs b/src/Razor/Microsoft.CodeAnalysis.Razor/test/ComponentTagHelperDescriptorProviderTest.cs index 52752cf28b..42be5cb115 100644 --- a/src/Razor/Microsoft.CodeAnalysis.Razor/test/ComponentTagHelperDescriptorProviderTest.cs +++ b/src/Razor/Microsoft.CodeAnalysis.Razor/test/ComponentTagHelperDescriptorProviderTest.cs @@ -577,6 +577,171 @@ namespace Test }); } + [Fact] + public void Execute_EventCallbackProperty_CreatesDescriptor() + { + // Arrange + + var compilation = BaseCompilation.AddSyntaxTrees(Parse(@" +using Microsoft.AspNetCore.Components; + +namespace Test +{ + public class MyComponent : ComponentBase + { + [Parameter] + EventCallback OnClick { 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); + + Assert.Equal("TestAssembly", component.AssemblyName); + Assert.Equal("Test.MyComponent", component.Name); + + var attribute = Assert.Single(component.BoundAttributes); + Assert.Equal("OnClick", attribute.Name); + Assert.Equal("Microsoft.AspNetCore.Components.EventCallback", attribute.TypeName); + + Assert.False(attribute.HasIndexer); + Assert.False(attribute.IsBooleanProperty); + Assert.False(attribute.IsEnum); + Assert.False(attribute.IsStringProperty); + Assert.True(attribute.IsEventCallbackProperty()); + Assert.False(attribute.IsDelegateProperty()); + Assert.False(attribute.IsChildContentProperty()); + } + + [Fact] + public void Execute_EventCallbackProperty_CreatesDescriptor_ClosedGeneric() + { + // Arrange + + var compilation = BaseCompilation.AddSyntaxTrees(Parse(@" +using Microsoft.AspNetCore.Components; + +namespace Test +{ + public class MyComponent : ComponentBase + { + [Parameter] + EventCallback OnClick { 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); + + Assert.Equal("TestAssembly", component.AssemblyName); + Assert.Equal("Test.MyComponent", component.Name); + + Assert.Collection( + component.BoundAttributes.OrderBy(a => a.Name), + a => + { + Assert.Equal("OnClick", a.Name); + Assert.Equal("Microsoft.AspNetCore.Components.EventCallback", a.TypeName); + Assert.False(a.HasIndexer); + Assert.False(a.IsBooleanProperty); + Assert.False(a.IsEnum); + Assert.False(a.IsStringProperty); + Assert.True(a.IsEventCallbackProperty()); + Assert.False(a.IsDelegateProperty()); + Assert.False(a.IsChildContentProperty()); + Assert.False(a.IsGenericTypedProperty()); + + }); + } + + [Fact] + public void Execute_EventCallbackProperty_CreatesDescriptor_OpenGeneric() + { + // Arrange + + var compilation = BaseCompilation.AddSyntaxTrees(Parse(@" +using Microsoft.AspNetCore.Components; + +namespace Test +{ + public class MyComponent : ComponentBase + { + [Parameter] + EventCallback OnClick { 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); + + Assert.Equal("TestAssembly", component.AssemblyName); + Assert.Equal("Test.MyComponent", component.Name); + + Assert.Collection( + component.BoundAttributes.OrderBy(a => a.Name), + a => + { + Assert.Equal("OnClick", a.Name); + Assert.Equal("Microsoft.AspNetCore.Components.EventCallback", a.TypeName); + Assert.False(a.HasIndexer); + Assert.False(a.IsBooleanProperty); + Assert.False(a.IsEnum); + Assert.False(a.IsStringProperty); + Assert.True(a.IsEventCallbackProperty()); + Assert.False(a.IsDelegateProperty()); + Assert.False(a.IsChildContentProperty()); + Assert.True(a.IsGenericTypedProperty()); + + }, + a => + { + Assert.Equal("T", a.Name); + Assert.Equal("T", a.GetPropertyName()); + Assert.Equal("T", a.DisplayName); + Assert.Equal("System.Type", a.TypeName); + Assert.True(a.IsTypeParameterProperty()); + }); + } + [Fact] public void Execute_RenderFragmentProperty_CreatesDescriptors() { diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/BindMethods.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/BindMethods.cs index 1e23d5a70f..3e4b51b60e 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/BindMethods.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/BindMethods.cs @@ -69,6 +69,24 @@ namespace Microsoft.AspNetCore.Components return value; } + /// + /// Not intended to be used directly. + /// + public static EventCallback GetEventHandlerValue(EventCallback value) + where T : UIEventArgs + { + return value; + } + + /// + /// Not intended to be used directly. + /// + public static EventCallback GetEventHandlerValue(EventCallback value) + where T : UIEventArgs + { + return value; + } + /// /// Not intended to be used directly. /// diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/EventCallback.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/EventCallback.cs new file mode 100644 index 0000000000..52c4fe1aab --- /dev/null +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/EventCallback.cs @@ -0,0 +1,33 @@ +// 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; + +namespace Microsoft.AspNetCore.Components +{ + public readonly struct EventCallback + { + public static readonly EventCallbackFactory Factory = new EventCallbackFactory(); + + internal readonly MulticastDelegate Delegate; + internal readonly IHandleEvent Receiver; + + public EventCallback(IHandleEvent receiver, MulticastDelegate @delegate) + { + Receiver = receiver; + Delegate = @delegate; + } + } + + public readonly struct EventCallback + { + internal readonly MulticastDelegate Delegate; + internal readonly IHandleEvent Receiver; + + public EventCallback(IHandleEvent receiver, MulticastDelegate @delegate) + { + Receiver = receiver; + Delegate = @delegate; + } + } +} diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/EventCallbackFactory.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/EventCallbackFactory.cs new file mode 100644 index 0000000000..6f54596a1f --- /dev/null +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/EventCallbackFactory.cs @@ -0,0 +1,43 @@ +// 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; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Components +{ + public sealed class EventCallbackFactory + { + public EventCallback Create(object receiver, EventCallback callback) => default; + + public EventCallback Create(object receiver, Action callback) => default; + + public EventCallback Create(object receiver, Action callback) => default; + + public EventCallback Create(object receiver, Func callback) => default; + + public EventCallback Create(object receiver, Func callback) => default; + + public EventCallback Create(object receiver, string callback) => default; + + public EventCallback Create(object receiver, EventCallback callback) => default; + + public EventCallback Create(object receiver, Action callback) => default; + + public EventCallback Create(object receiver, Action callback) => default; + + public EventCallback Create(object receiver, Func callback) => default; + + public EventCallback Create(object receiver, Func callback) => default; + + public EventCallback CreateInferred(object receiver, Action callback, T value) + { + return Create(receiver, callback); + } + + public EventCallback CreateInferred(object receiver, Func callback, T value) + { + return Create(receiver, callback); + } + } +} diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/EventCallbackFactoryBinderExtensions.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/EventCallbackFactoryBinderExtensions.cs new file mode 100644 index 0000000000..8a920ff53c --- /dev/null +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/EventCallbackFactoryBinderExtensions.cs @@ -0,0 +1,107 @@ +// 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; + +namespace Microsoft.AspNetCore.Components +{ + public static class EventCallbackFactoryBinderExtensions + { + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + string existingValue) => default; + + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + bool existingValue) => default; + + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + bool? existingValue) => default; + + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + int existingValue) => default; + + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + int? existingValue) => default; + + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + long existingValue) => default; + + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + long? existingValue) => default; + + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + float existingValue) => default; + + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + float? existingValue) => default; + + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + double existingValue) => default; + + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + double? existingValue) => default; + + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + decimal existingValue) => default; + + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + decimal? existingValue) => default; + + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + DateTime existingValue) => default; + + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + DateTime existingValue, + string format) => default; + + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + T existingValue) where T : Enum => default; + } +} diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/EventCallbackFactoryUIEventArgsExtensions.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/EventCallbackFactoryUIEventArgsExtensions.cs new file mode 100644 index 0000000000..c81d09fd07 --- /dev/null +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/EventCallbackFactoryUIEventArgsExtensions.cs @@ -0,0 +1,59 @@ +// 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; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Components +{ + public static class EventCallbackFactoryUIEventArgsExtensions + { + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Action callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Func callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Action callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Func callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Action callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Func callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Action callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Func callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Action callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Func callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Action callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Func callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Action callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Func callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Action callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Func callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Action callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Func callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Action callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Func callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Action callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Func callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Action callback) => default; + + public static EventCallback Create(this EventCallbackFactory factory, object receiver, Func callback) => default; + } +} diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/IHandleEvent.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/IHandleEvent.cs new file mode 100644 index 0000000000..b5ba9a87c8 --- /dev/null +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/IHandleEvent.cs @@ -0,0 +1,9 @@ +// 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.Components +{ + public interface IHandleEvent + { + } +} diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/RenderTree/RenderTreeBuilder.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/RenderTree/RenderTreeBuilder.cs index 8cbcb3712d..e07c337a4d 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/RenderTree/RenderTreeBuilder.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.ComponentShim/Microsoft.AspNetCore.Components/RenderTree/RenderTreeBuilder.cs @@ -72,6 +72,14 @@ namespace Microsoft.AspNetCore.Components.RenderTree { } + public void AddAttribute(int sequence, string name, EventCallback value) + { + } + + public void AddAttribute(int sequence, string name, EventCallback value) + { + } + public void AddAttribute(int sequence, string name, object value) { }