diff --git a/src/Components/Components/src/CompilerServices/RuntimeHelpers.cs b/src/Components/Components/src/CompilerServices/RuntimeHelpers.cs index 8404807e4c3d..8abdc5faf0b7 100644 --- a/src/Components/Components/src/CompilerServices/RuntimeHelpers.cs +++ b/src/Components/Components/src/CompilerServices/RuntimeHelpers.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Runtime.CompilerServices; + namespace Microsoft.AspNetCore.Components.CompilerServices; /// @@ -50,4 +52,76 @@ public static EventCallback CreateInferredEventCallback(object receiver, F { return EventCallback.Factory.Create(receiver, callback); } + + /// + /// Not intended for use by application code. + /// + /// + /// + /// + /// + // + // This method is used with `@bind-Value` for components. When a component has a generic type, it's + // really messy to write the parameter type for ValueChanged - because it can contain generic + // type parameters. We're using a trick of type inference to generate the proper typing for the delegate + // so that method-group-to-delegate conversion works. + public static EventCallback CreateInferredEventCallback(object receiver, EventCallback callback, T value) + { + return EventCallback.Factory.Create(receiver, callback); + } + + /// + /// Not intended for use by application code. + /// + /// + /// + // + // This method is used with `@bind-Value:after` for components. When :after is provided we don't know the + // type of the expression provided by the developer or if we can invoke it directly, as it can be a lambda + // and unlike in JavaScript, C# doesn't support Immediately Invoked Function Expressions so we need to pass + // the expression to this helper method and invoke it inside. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvokeSynchronousDelegate(Action callback) + { + callback(); + } + + /// + /// Not intended for use by application code. + /// + /// + /// + // + // This method is used with `@bind-Value:after` for components. When :after is provided we don't know the + // type of the expression provided by the developer or if we can invoke it directly, as it can be a lambda + // and unlike in JavaScript, C# doesn't support Immediately Invoked Function Expressions so we need to pass + // the expression to this helper method and invoke it inside. + // In addition to that, when the receiving target delegate property result is awaitable, we can receive either + // an Action or a Func and we don't have that information at compile time, so we use this helper to + // normalize both operations into a Task in the same way we do for EventCallback + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task InvokeAsynchronousDelegate(Action callback) + { + callback(); + return Task.CompletedTask; + } + + /// + /// Not intended for use by application code. + /// + /// + /// + // + // This method is used with `@bind-Value:after` for components. When :after is provided we don't know the + // type of the expression provided by the developer or if we can invoke it directly, as it can be a lambda + // and unlike in JavaScript, C# doesn't support Immediately Invoked Function Expressions so we need to pass + // the expression to this helper method and invoke it inside. + // In addition to that, when the receiving target delegate property result is awaitable, we can receive either + // an Action or a Func and we don't have that information at compile time, so we use this helper to + // normalize both operations into a Task in the same way we do for EventCallback + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task InvokeAsynchronousDelegate(Func callback) + { + return callback(); + } } diff --git a/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs b/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs index 733c80e6e49e..42328e8f8f78 100644 --- a/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs +++ b/src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs @@ -42,6 +42,26 @@ public static EventCallback CreateBinder( return CreateBinderCore(factory, receiver, setter, culture, ConvertToString); } + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + string existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToString); + } + /// /// For internal use only. /// @@ -62,6 +82,26 @@ public static EventCallback CreateBinder( return CreateBinderCore(factory, receiver, setter, culture, ConvertToBool); } + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + bool existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToBool); + } + /// /// For internal use only. /// @@ -82,6 +122,26 @@ public static EventCallback CreateBinder( return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableBool); } + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + bool? existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableBool); + } + /// /// For internal use only. /// @@ -102,6 +162,26 @@ public static EventCallback CreateBinder( return CreateBinderCore(factory, receiver, setter, culture, ConvertToInt); } + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + int existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToInt); + } + /// /// For internal use only. /// @@ -119,7 +199,573 @@ public static EventCallback CreateBinder( int? existingValue, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableInt); + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableInt); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + int? existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableInt); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + long existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToLong); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + long existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToLong); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + short existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToShort); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + short existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToShort); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + long? existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableLong); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + long? existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableLong); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + short? existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableShort); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + short? existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableShort); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + float existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToFloat); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + float existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToFloat); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + float? existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableFloat); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + float? existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableFloat); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + double existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToDoubleDelegate); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + double existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToDoubleDelegate); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + double? existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableDoubleDelegate); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + double? existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableDoubleDelegate); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + decimal existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToDecimal); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + decimal existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToDecimal); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + decimal? existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableDecimal); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + decimal? existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableDecimal); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + DateTime existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToDateTime); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + DateTime existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToDateTime); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + DateTime existingValue, + string format, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToDateTimeWithFormat); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + DateTime existingValue, + string format, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToDateTimeWithFormat); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + DateTime? existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableDateTime); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + DateTime? existingValue, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableDateTime); + } + + /// + /// For internal use only. + /// + /// + /// + /// + /// + /// + /// + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static EventCallback CreateBinder( + this EventCallbackFactory factory, + object receiver, + Action setter, + DateTime? existingValue, + string format, + CultureInfo? culture = null) + { + return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToNullableDateTimeWithFormat); } /// @@ -129,17 +775,19 @@ public static EventCallback CreateBinder( /// /// /// + /// /// /// [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - long existingValue, + EventCallback setter, + DateTime? existingValue, + string format, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, ConvertToLong); + return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToNullableDateTimeWithFormat); } /// @@ -155,11 +803,11 @@ public static EventCallback CreateBinder( public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - short existingValue, + Action setter, + DateTimeOffset existingValue, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, ConvertToShort); + return CreateBinderCore(factory, receiver, setter, culture, ConvertToDateTimeOffset); } /// @@ -175,11 +823,11 @@ public static EventCallback CreateBinder( public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - long? existingValue, + EventCallback setter, + DateTimeOffset existingValue, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableLong); + return CreateBinderCore(factory, receiver, setter, culture, ConvertToDateTimeOffset); } /// @@ -189,17 +837,19 @@ public static EventCallback CreateBinder( /// /// /// + /// /// /// [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - short? existingValue, + Action setter, + DateTimeOffset existingValue, + string format, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableShort); + return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToDateTimeOffsetWithFormat); } /// @@ -209,17 +859,19 @@ public static EventCallback CreateBinder( /// /// /// + /// /// /// [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - float existingValue, + EventCallback setter, + DateTimeOffset existingValue, + string format, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, ConvertToFloat); + return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToDateTimeOffsetWithFormat); } /// @@ -235,11 +887,11 @@ public static EventCallback CreateBinder( public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - float? existingValue, + Action setter, + DateTimeOffset? existingValue, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableFloat); + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableDateTimeOffset); } /// @@ -255,11 +907,11 @@ public static EventCallback CreateBinder( public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - double existingValue, + EventCallback setter, + DateTimeOffset? existingValue, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, ConvertToDoubleDelegate); + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableDateTimeOffset); } /// @@ -269,17 +921,19 @@ public static EventCallback CreateBinder( /// /// /// + /// /// /// [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - double? existingValue, + Action setter, + DateTimeOffset? existingValue, + string format, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableDoubleDelegate); + return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToNullableDateTimeOffsetWithFormat); } /// @@ -289,17 +943,19 @@ public static EventCallback CreateBinder( /// /// /// + /// /// /// [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - decimal existingValue, + EventCallback setter, + DateTimeOffset? existingValue, + string format, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, ConvertToDecimal); + return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToNullableDateTimeOffsetWithFormat); } /// @@ -315,11 +971,11 @@ public static EventCallback CreateBinder( public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - decimal? existingValue, + Action setter, + DateOnly existingValue, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableDecimal); + return CreateBinderCore(factory, receiver, setter, culture, ConvertToDateOnly); } /// @@ -335,11 +991,11 @@ public static EventCallback CreateBinder( public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - DateTime existingValue, + EventCallback setter, + DateOnly existingValue, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, ConvertToDateTime); + return CreateBinderCore(factory, receiver, setter, culture, ConvertToDateOnly); } /// @@ -356,12 +1012,12 @@ public static EventCallback CreateBinder( public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - DateTime existingValue, + Action setter, + DateOnly existingValue, string format, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToDateTimeWithFormat); + return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToDateOnlyWithFormat); } /// @@ -371,17 +1027,19 @@ public static EventCallback CreateBinder( /// /// /// + /// /// /// [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - DateTime? existingValue, + EventCallback setter, + DateOnly existingValue, + string format, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableDateTime); + return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToDateOnlyWithFormat); } /// @@ -391,19 +1049,17 @@ public static EventCallback CreateBinder( /// /// /// - /// /// /// [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - DateTime? existingValue, - string format, + Action setter, + DateOnly? existingValue, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToNullableDateTimeWithFormat); + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableDateOnly); } /// @@ -419,11 +1075,11 @@ public static EventCallback CreateBinder( public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - DateTimeOffset existingValue, + EventCallback setter, + DateOnly? existingValue, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, ConvertToDateTimeOffset); + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableDateOnly); } /// @@ -440,12 +1096,12 @@ public static EventCallback CreateBinder( public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - DateTimeOffset existingValue, + Action setter, + DateOnly? existingValue, string format, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToDateTimeOffsetWithFormat); + return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToNullableDateOnlyWithFormat); } /// @@ -455,17 +1111,19 @@ public static EventCallback CreateBinder( /// /// /// + /// /// /// [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - DateTimeOffset? existingValue, + EventCallback setter, + DateOnly? existingValue, + string format, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableDateTimeOffset); + return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToNullableDateOnlyWithFormat); } /// @@ -475,19 +1133,17 @@ public static EventCallback CreateBinder( /// /// /// - /// /// /// [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - DateTimeOffset? existingValue, - string format, + Action setter, + TimeOnly existingValue, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToNullableDateTimeOffsetWithFormat); + return CreateBinderCore(factory, receiver, setter, culture, ConvertToTimeOnly); } /// @@ -503,11 +1159,11 @@ public static EventCallback CreateBinder( public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - DateOnly existingValue, + EventCallback setter, + TimeOnly existingValue, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, ConvertToDateOnly); + return CreateBinderCore(factory, receiver, setter, culture, ConvertToTimeOnly); } /// @@ -524,12 +1180,12 @@ public static EventCallback CreateBinder( public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - DateOnly existingValue, + Action setter, + TimeOnly existingValue, string format, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToDateOnlyWithFormat); + return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToTimeOnlyWithFormat); } /// @@ -539,17 +1195,19 @@ public static EventCallback CreateBinder( /// /// /// + /// /// /// [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - DateOnly? existingValue, + EventCallback setter, + TimeOnly existingValue, + string format, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableDateOnly); + return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToTimeOnlyWithFormat); } /// @@ -559,19 +1217,17 @@ public static EventCallback CreateBinder( /// /// /// - /// /// /// [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - DateOnly? existingValue, - string format, + Action setter, + TimeOnly? existingValue, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToNullableDateOnlyWithFormat); + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableTimeOnly); } /// @@ -587,11 +1243,11 @@ public static EventCallback CreateBinder( public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - TimeOnly existingValue, + EventCallback setter, + TimeOnly? existingValue, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, ConvertToTimeOnly); + return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableTimeOnly); } /// @@ -608,12 +1264,12 @@ public static EventCallback CreateBinder( public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, - TimeOnly existingValue, + Action setter, + TimeOnly? existingValue, string format, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToTimeOnlyWithFormat); + return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToNullableTimeOnlyWithFormat); } /// @@ -623,39 +1279,40 @@ public static EventCallback CreateBinder( /// /// /// + /// /// /// [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static EventCallback CreateBinder( this EventCallbackFactory factory, object receiver, - Action setter, + EventCallback setter, TimeOnly? existingValue, + string format, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, ConvertToNullableTimeOnly); + return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToNullableTimeOnlyWithFormat); } /// /// For internal use only. /// + /// /// /// /// /// - /// /// /// [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static EventCallback CreateBinder( + public static EventCallback CreateBinder<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>( this EventCallbackFactory factory, object receiver, - Action setter, - TimeOnly? existingValue, - string format, + Action setter, + T existingValue, CultureInfo? culture = null) { - return CreateBinderCore(factory, receiver, setter, culture, format, ConvertToNullableTimeOnlyWithFormat); + return CreateBinderCore(factory, receiver, setter, culture, ParserDelegateCache.Get()); } /// @@ -672,7 +1329,7 @@ public static EventCallback CreateBinder( public static EventCallback CreateBinder<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>( this EventCallbackFactory factory, object receiver, - Action setter, + EventCallback setter, T existingValue, CultureInfo? culture = null) { @@ -722,6 +1379,49 @@ private static EventCallback CreateBinderCore( return factory.Create(receiver, callback); } + private static EventCallback CreateBinderCore( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + CultureInfo? culture, + BindConverter.BindParser converter) + { + Func callback = async e => + { + T? value = default; + var converted = false; + try + { + converted = converter(e.Value, culture, out value); + } + catch + { + } + + // We only invoke the setter if the conversion didn't throw, or if the newly-entered value is empty. + // If the user entered some non-empty value we couldn't parse, we leave the state of the .NET field + // unchanged, which for a two-way binding results in the UI reverting to its previous valid state + // because the diff will see the current .NET output no longer matches the render tree since we + // patched it to reflect the state of the UI. + // + // This reversion behavior is valuable because alternatives are problematic: + // - If we assigned default(T) on failure, the user would lose whatever data they were editing, + // for example if they accidentally pressed an alphabetical key while editing a number with + // @bind:event="oninput" + // - If the diff mechanism didn't revert to the previous good value, the user wouldn't necessarily + // know that the data they are submitting is different from what they think they've typed + if (converted) + { + await setter.InvokeAsync(value!); + } + else if (string.Empty.Equals(e.Value)) + { + await setter.InvokeAsync(default!); + } + }; + return factory.Create(receiver, callback); + } + private static EventCallback CreateBinderCore( this EventCallbackFactory factory, object receiver, @@ -765,4 +1465,48 @@ private static EventCallback CreateBinderCore( }; return factory.Create(receiver, callback); } + + private static EventCallback CreateBinderCore( + this EventCallbackFactory factory, + object receiver, + EventCallback setter, + CultureInfo? culture, + string format, + BindConverter.BindParserWithFormat converter) + { + Func callback = async e => + { + T? value = default; + var converted = false; + try + { + converted = converter(e.Value, culture, format, out value); + } + catch + { + } + + // We only invoke the setter if the conversion didn't throw, or if the newly-entered value is empty. + // If the user entered some non-empty value we couldn't parse, we leave the state of the .NET field + // unchanged, which for a two-way binding results in the UI reverting to its previous valid state + // because the diff will see the current .NET output no longer matches the render tree since we + // patched it to reflect the state of the UI. + // + // This reversion behavior is valuable because alternatives are problematic: + // - If we assigned default(T) on failure, the user would lose whatever data they were editing, + // for example if they accidentally pressed an alphabetical key while editing a number with + // @bind:event="oninput" + // - If the diff mechanism didn't revert to the previous good value, the user wouldn't necessarily + // know that the data they are submitting is different from what they think they've typed + if (converted) + { + await setter.InvokeAsync(value!); + } + else if (string.Empty.Equals(e.Value)) + { + await setter.InvokeAsync(default!); + } + }; + return factory.Create(receiver, callback); + } } diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt index 53b72159add0..3b526beb24f9 100644 --- a/src/Components/Components/src/PublicAPI.Unshipped.txt +++ b/src/Components/Components/src/PublicAPI.Unshipped.txt @@ -1,2 +1,38 @@ #nullable enable Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddContent(int sequence, Microsoft.AspNetCore.Components.MarkupString? markupContent) -> void +static Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.CreateInferredEventCallback(object! receiver, Microsoft.AspNetCore.Components.EventCallback callback, T value) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.InvokeAsynchronousDelegate(System.Action! callback) -> System.Threading.Tasks.Task! +static Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.InvokeAsynchronousDelegate(System.Func! callback) -> System.Threading.Tasks.Task! +static Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.InvokeSynchronousDelegate(System.Action! callback) -> void +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, System.DateOnly existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, System.DateOnly existingValue, string! format, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, System.DateOnly? existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, System.DateOnly? existingValue, string! format, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, System.DateTime existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, System.DateTime existingValue, string! format, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, System.DateTime? existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, System.DateTime? existingValue, string! format, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, System.DateTimeOffset existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, System.DateTimeOffset existingValue, string! format, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, System.DateTimeOffset? existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, System.DateTimeOffset? existingValue, string! format, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, System.TimeOnly existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, System.TimeOnly existingValue, string! format, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, System.TimeOnly? existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, System.TimeOnly? existingValue, string! format, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, bool existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, bool? existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, decimal existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, decimal? existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, double existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, double? existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, float existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, float? existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, int existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, int? existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, long existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, long? existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, short existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, short? existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, string! existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback +static Microsoft.AspNetCore.Components.EventCallbackFactoryBinderExtensions.CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory! factory, object! receiver, Microsoft.AspNetCore.Components.EventCallback setter, T existingValue, System.Globalization.CultureInfo? culture = null) -> Microsoft.AspNetCore.Components.EventCallback diff --git a/src/Components/Components/test/RendererTest.cs b/src/Components/Components/test/RendererTest.cs index 6959ccc0d34f..164dc17e3978 100644 --- a/src/Components/Components/test/RendererTest.cs +++ b/src/Components/Components/test/RendererTest.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Globalization; using System.Runtime.ExceptionServices; +using Microsoft.AspNetCore.Components.CompilerServices; using Microsoft.AspNetCore.Components.HotReload; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.RenderTree; @@ -3042,6 +3043,199 @@ public async Task CanCombineBindAndConditionalAttribute() await renderTask; } + [Fact] + public async Task BindWithSynchronousSetter_Lambda() + { + // Arrange + var renderer = new TestRenderer(); + var component = new OuterEventComponent(); + var value = "value"; + component.RenderFragment = (builder) => + { + builder.OpenElement(0, "input"); + builder.AddAttribute(1, "onchange", EventCallback.Factory.CreateBinder( + component, + RuntimeHelpers.CreateInferredEventCallback(component, __value => value = __value, value), + value)); + builder.CloseElement(); + }; + var componentId = renderer.AssignRootComponentId(component); + await component.TriggerRenderAsync(); + var checkboxChangeEventHandlerId = renderer.Batches.Single() + .ReferenceFrames + .First(frame => frame.FrameType == RenderTreeFrameType.Attribute && frame.AttributeEventHandlerId != 0) + .AttributeEventHandlerId; + + // Act: Trigger change event + var eventArgs = new ChangeEventArgs { Value = "hello" }; + var renderTask = renderer.DispatchEventAsync(checkboxChangeEventHandlerId, eventArgs); + await renderTask; + + // Assert + Assert.Equal("hello", value); + } + + [Fact] + public async Task BindWithAsynchronousSetter_MethodGroupToDelegate() + { + // This test represents https://github.com/dotnet/blazor/issues/624 + + // Arrange: Rendered with textbox enabled + var renderer = new TestRenderer(); + var component = new OuterEventComponent(); + var value = "value"; + async Task SetValue(string __value) + { + value = __value; + await Task.CompletedTask; + } + component.RenderFragment = (builder) => + { + builder.OpenElement(0, "input"); + builder.AddAttribute(1, "onchange", EventCallback.Factory.CreateBinder( + component, + RuntimeHelpers.CreateInferredEventCallback(component, SetValue, value), + value)); + builder.CloseElement(); + }; + var componentId = renderer.AssignRootComponentId(component); + await component.TriggerRenderAsync(); + var checkboxChangeEventHandlerId = renderer.Batches.Single() + .ReferenceFrames + .First(frame => frame.FrameType == RenderTreeFrameType.Attribute && frame.AttributeEventHandlerId != 0) + .AttributeEventHandlerId; + + // Act: Trigger change event + var eventArgs = new ChangeEventArgs { Value = "hello" }; + var renderTask = renderer.DispatchEventAsync(checkboxChangeEventHandlerId, eventArgs); + await renderTask; + + // Assert + Assert.Equal("hello", value); + } + + [Fact] + public async Task BindWithAsynchronousSetter_ExistingEventCallback() + { + // This test represents https://github.com/dotnet/blazor/issues/624 + + // Arrange: Rendered with textbox enabled + var renderer = new TestRenderer(); + var component = new OuterEventComponent(); +#nullable enable + var value = "value"; + var eventCallback = new EventCallback(component, (string? __value) => + { + value = __value; + }); +#nullable disable + component.RenderFragment = (builder) => + { + builder.OpenElement(0, "input"); + builder.AddAttribute(1, "onchange", EventCallback.Factory.CreateBinder( + component, + RuntimeHelpers.CreateInferredEventCallback(component, eventCallback, value), + value)); + builder.CloseElement(); + }; + var componentId = renderer.AssignRootComponentId(component); + await component.TriggerRenderAsync(); + var checkboxChangeEventHandlerId = renderer.Batches.Single() + .ReferenceFrames + .First(frame => frame.FrameType == RenderTreeFrameType.Attribute && frame.AttributeEventHandlerId != 0) + .AttributeEventHandlerId; + + // Act: Trigger change event + var eventArgs = new ChangeEventArgs { Value = "hello" }; + var renderTask = renderer.DispatchEventAsync(checkboxChangeEventHandlerId, eventArgs); + await renderTask; + + // Assert + Assert.Equal("hello", value); + } + + [Fact] + public async Task BindWithAfter() + { + // This test represents https://github.com/dotnet/blazor/issues/624 + + // Arrange: Rendered with textbox enabled + var renderer = new TestRenderer(); + var component = new OuterEventComponent(); + string value = "value"; + component.RenderFragment = (builder) => + { + builder.OpenElement(0, "input"); + builder.AddAttribute(1, "onchange", EventCallback.Factory.CreateBinder( + component, + RuntimeHelpers.CreateInferredEventCallback( + component, + async __value => + { + value = __value; + await EventCallback.Factory.Create(component, () => Task.CompletedTask).InvokeAsync(); + }, + value), + value)); + builder.CloseElement(); + }; + var componentId = renderer.AssignRootComponentId(component); + await component.TriggerRenderAsync(); + var checkboxChangeEventHandlerId = renderer.Batches.Single() + .ReferenceFrames + .First(frame => frame.FrameType == RenderTreeFrameType.Attribute && frame.AttributeEventHandlerId != 0) + .AttributeEventHandlerId; + + // Act: Trigger change event + var eventArgs = new ChangeEventArgs { Value = "hello" }; + var renderTask = renderer.DispatchEventAsync(checkboxChangeEventHandlerId, eventArgs); + await renderTask; + + // Assert + Assert.Equal("hello", value); + } + + [Fact] + public async Task BindWithAfter_Action() + { + // This test represents https://github.com/dotnet/blazor/issues/624 + + // Arrange: Rendered with textbox enabled + var renderer = new TestRenderer(); + var component = new OuterEventComponent(); + string value = "value"; + component.RenderFragment = (builder) => + { + builder.OpenElement(0, "input"); + builder.AddAttribute(1, "onchange", EventCallback.Factory.CreateBinder( + component, + RuntimeHelpers.CreateInferredEventCallback( + component, + async __value => + { + value = __value; + await EventCallback.Factory.Create(component, () => { }).InvokeAsync(); + }, + value), + value)); + builder.CloseElement(); + }; + var componentId = renderer.AssignRootComponentId(component); + await component.TriggerRenderAsync(); + var checkboxChangeEventHandlerId = renderer.Batches.Single() + .ReferenceFrames + .First(frame => frame.FrameType == RenderTreeFrameType.Attribute && frame.AttributeEventHandlerId != 0) + .AttributeEventHandlerId; + + // Act: Trigger change event + var eventArgs = new ChangeEventArgs { Value = "hello" }; + var renderTask = renderer.DispatchEventAsync(checkboxChangeEventHandlerId, eventArgs); + await renderTask; + + // Assert + Assert.Equal("hello", value); + } + [Fact] public void HandlesNestedElementCapturesDuringRefresh() {