Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions src/Components/Web/src/WebEventData/WebEventData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
{
Expand Down
102 changes: 102 additions & 0 deletions src/Components/Web/test/WebEventData/WebEventDataTest.cs
Original file line number Diff line number Diff line change
@@ -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<CustomEventArgs, Task>)(_ => 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<CustomEventArgs>(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>)(() => 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<EventArgs>(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);
}
}
Loading