diff --git a/src/Components/Web/src/WebEventData/WebEventData.cs b/src/Components/Web/src/WebEventData/WebEventData.cs index 0e753a3b74c9..67fca018a372 100644 --- a/src/Components/Web/src/WebEventData/WebEventData.cs +++ b/src/Components/Web/src/WebEventData/WebEventData.cs @@ -58,8 +58,6 @@ private WebEventData(ulong eventHandlerId, EventFieldInfo? eventFieldInfo, Event public EventArgs EventArgs { get; } - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", - Justification = "We are already using the appropriate overload")] private static EventArgs ParseEventArgsJson( Renderer renderer, JsonSerializerOptions jsonSerializerOptions, @@ -74,9 +72,14 @@ private static EventArgs ParseEventArgsJson( return eventArgs; } - // For custom events, the args type is determined from the associated delegate + // For custom events, the args type is determined from the associated delegate. The type + // is registered via the [EventHandler] attribute, whose 'eventArgsType' argument is + // annotated with DynamicallyAccessedMembers so the members required for JSON + // deserialization are preserved when trimming. We deserialize using the JsonTypeInfo + // returned by the options, which is the trim-safe API. var eventArgsType = renderer.GetEventArgsType(eventHandlerId); - return (EventArgs)JsonSerializer.Deserialize(eventArgsJson.GetRawText(), eventArgsType, jsonSerializerOptions)!; + var eventArgsTypeInfo = jsonSerializerOptions.GetTypeInfo(eventArgsType); + return (EventArgs)JsonSerializer.Deserialize(eventArgsJson.GetRawText(), eventArgsTypeInfo)!; } catch (Exception e) { diff --git a/src/Components/Web/test/WebEventData/WebEventDataTest.cs b/src/Components/Web/test/WebEventData/WebEventDataTest.cs new file mode 100644 index 000000000000..94ab734b37ad --- /dev/null +++ b/src/Components/Web/test/WebEventData/WebEventDataTest.cs @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; +using Microsoft.AspNetCore.Components.RenderTree; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.Test.Helpers; + +namespace Microsoft.AspNetCore.Components.Web; + +public class WebEventDataTest +{ + private static readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web) + { + IncludeFields = true, + TypeInfoResolver = new DefaultJsonTypeInfoResolver(), + }; + + [Fact] + public void ParseEventArgsJson_DeserializesCustomEventArgs_UsingHandlerParameterType() + { + var renderer = new TestRenderer(); + var component = new CustomEventComponent + { + Handler = (Func)(_ => Task.CompletedTask), + }; + renderer.AssignRootComponentId(component); + component.TriggerRender(); + + var eventHandlerId = renderer.Batches.Single() + .ReferenceFrames + .First(frame => frame.AttributeValue != null) + .AttributeEventHandlerId; + + var descriptor = new WebEventDescriptor + { + EventHandlerId = eventHandlerId, + EventName = "oncustomevent", + }; + + using var eventArgsJson = JsonDocument.Parse("{\"value\":\"hello\"}"); + + var webEventData = WebEventData.Parse(renderer, _jsonOptions, descriptor, eventArgsJson.RootElement); + + var customArgs = Assert.IsType(webEventData.EventArgs); + Assert.Equal("hello", customArgs.Value); + Assert.Equal(eventHandlerId, webEventData.EventHandlerId); + } + + [Fact] + public void ParseEventArgsJson_ReturnsEmptyEventArgs_ForParameterlessHandler() + { + // Arrange: a handler that takes no EventArgs resolves to EventArgs. + var renderer = new TestRenderer(); + var component = new CustomEventComponent + { + Handler = (Func)(() => Task.CompletedTask), + }; + renderer.AssignRootComponentId(component); + component.TriggerRender(); + + var eventHandlerId = renderer.Batches.Single() + .ReferenceFrames + .First(frame => frame.AttributeValue != null) + .AttributeEventHandlerId; + + var descriptor = new WebEventDescriptor + { + EventHandlerId = eventHandlerId, + EventName = "oncustomevent", + }; + + using var eventArgsJson = JsonDocument.Parse("{}"); + + var webEventData = WebEventData.Parse(renderer, _jsonOptions, descriptor, eventArgsJson.RootElement); + + Assert.NotNull(webEventData.EventArgs); + Assert.IsType(webEventData.EventArgs); + } + + private sealed class CustomEventArgs : EventArgs + { + public string Value { get; set; } + } + + private sealed class CustomEventComponent : AutoRenderComponent, IHandleEvent + { + [Parameter] + public Delegate Handler { get; set; } + + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + builder.OpenElement(0, "element"); + builder.AddAttribute(1, "oncustomevent", Handler); + builder.CloseElement(); + } + + public Task HandleEventAsync(EventCallbackWorkItem callback, object arg) + => callback.InvokeAsync(arg); + } +}