-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Use System.Text.Json's source generated code to deserialize WebEvents #32374
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3a6588f
0ce96b4
7f42121
3c5dbad
ef456b7
84cff2a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,21 +6,26 @@ | |
| using System; | ||
| using System.Diagnostics.CodeAnalysis; | ||
| using System.Text.Json; | ||
| using System.Text.Json.Serialization; | ||
| using System.Text.Json.Serialization.Metadata; | ||
| using Microsoft.AspNetCore.Components.RenderTree; | ||
| using static Microsoft.AspNetCore.Internal.LinkerFlags; | ||
|
|
||
| namespace Microsoft.AspNetCore.Components.Web | ||
| { | ||
| internal class WebEventData | ||
| { | ||
| // This class represents the second half of parsing incoming event data, | ||
| // once the event ID (and possibly the type of the eventArgs) becomes known. | ||
| public static WebEventData Parse(Renderer renderer, JsonSerializerOptions jsonSerializerOptions, string eventDescriptorJson, string eventArgsJson) | ||
| public static WebEventData Parse( | ||
| Renderer renderer, | ||
| WebEventJsonContext jsonSerializerContext, | ||
| string eventDescriptorJson, | ||
| string eventArgsJson) | ||
| { | ||
| WebEventDescriptor eventDescriptor; | ||
| try | ||
| { | ||
| eventDescriptor = Deserialize<WebEventDescriptor>(eventDescriptorJson); | ||
| eventDescriptor = Deserialize(eventDescriptorJson, jsonSerializerContext.WebEventDescriptor); | ||
| } | ||
| catch (Exception e) | ||
| { | ||
|
|
@@ -29,14 +34,18 @@ public static WebEventData Parse(Renderer renderer, JsonSerializerOptions jsonSe | |
|
|
||
| return Parse( | ||
| renderer, | ||
| jsonSerializerOptions, | ||
| jsonSerializerContext, | ||
| eventDescriptor, | ||
| eventArgsJson); | ||
| } | ||
|
|
||
| public static WebEventData Parse(Renderer renderer, JsonSerializerOptions jsonSerializerOptions, WebEventDescriptor eventDescriptor, string eventArgsJson) | ||
| public static WebEventData Parse( | ||
| Renderer renderer, | ||
| WebEventJsonContext jsonSerializerContext, | ||
| WebEventDescriptor eventDescriptor, | ||
| string eventArgsJson) | ||
| { | ||
| var parsedEventArgs = ParseEventArgsJson(renderer, jsonSerializerOptions, eventDescriptor.EventHandlerId, eventDescriptor.EventName, eventArgsJson); | ||
| var parsedEventArgs = ParseEventArgsJson(renderer, jsonSerializerContext, eventDescriptor.EventHandlerId, eventDescriptor.EventName, eventArgsJson); | ||
| return new WebEventData( | ||
| eventDescriptor.BrowserRendererId, | ||
| eventDescriptor.EventHandlerId, | ||
|
|
@@ -60,29 +69,35 @@ private WebEventData(int browserRendererId, ulong eventHandlerId, EventFieldInfo | |
|
|
||
| public EventArgs EventArgs { get; } | ||
|
|
||
| private static EventArgs ParseEventArgsJson(Renderer renderer, JsonSerializerOptions jsonSerializerOptions, ulong eventHandlerId, string eventName, string eventArgsJson) | ||
| private static EventArgs ParseEventArgsJson( | ||
| Renderer renderer, | ||
| WebEventJsonContext jsonSerializerContext, | ||
| ulong eventHandlerId, | ||
| string eventName, | ||
| string eventArgsJson) | ||
| { | ||
| try | ||
| { | ||
| if (TryDeserializeStandardWebEventArgs(eventName, eventArgsJson, out var eventArgs)) | ||
| if (TryDeserializeStandardWebEventArgs(eventName, eventArgsJson, jsonSerializerContext, out var eventArgs)) | ||
| { | ||
| return eventArgs; | ||
| } | ||
|
|
||
| // For custom events, the args type is determined from the associated delegate | ||
| var eventArgsType = renderer.GetEventArgsType(eventHandlerId); | ||
| return (EventArgs)JsonSerializer.Deserialize(eventArgsJson, eventArgsType, jsonSerializerOptions)!; | ||
| return (EventArgs)JsonSerializer.Deserialize(eventArgsJson, eventArgsType, jsonSerializerContext.Options)!; | ||
| } | ||
| catch (Exception e) | ||
| { | ||
| throw new InvalidOperationException($"There was an error parsing the event arguments. EventId: '{eventHandlerId}'.", e); | ||
| } | ||
| } | ||
|
|
||
| [DynamicDependency(JsonSerialized, typeof(DataTransfer))] | ||
| [DynamicDependency(JsonSerialized, typeof(DataTransferItem))] | ||
| [DynamicDependency(JsonSerialized, typeof(TouchPoint))] | ||
| private static bool TryDeserializeStandardWebEventArgs(string eventName, string eventArgsJson, [NotNullWhen(true)] out EventArgs? eventArgs) | ||
| private static bool TryDeserializeStandardWebEventArgs( | ||
| string eventName, | ||
| string eventArgsJson, | ||
| WebEventJsonContext jsonSerializerContext, | ||
| [NotNullWhen(true)] out EventArgs? eventArgs) | ||
| { | ||
| // For back-compatibility, we recognize the built-in list of web event names and hard-code | ||
| // rules about the deserialization type for their eventargs. This makes it possible to declare | ||
|
|
@@ -97,13 +112,13 @@ private static bool TryDeserializeStandardWebEventArgs(string eventName, string | |
| case "change": | ||
| // Special case for ChangeEventArgs because its value type can be one of | ||
| // several types, and System.Text.Json doesn't pick types dynamically | ||
| eventArgs = DeserializeChangeEventArgs(eventArgsJson); | ||
| eventArgs = DeserializeChangeEventArgs(eventArgsJson, jsonSerializerContext); | ||
| return true; | ||
|
|
||
| case "copy": | ||
| case "cut": | ||
| case "paste": | ||
| eventArgs = Deserialize<ClipboardEventArgs>(eventArgsJson); | ||
| eventArgs = Deserialize<ClipboardEventArgs>(eventArgsJson, jsonSerializerContext.ClipboardEventArgs); | ||
| return true; | ||
|
|
||
| case "drag": | ||
|
|
@@ -113,20 +128,20 @@ private static bool TryDeserializeStandardWebEventArgs(string eventName, string | |
| case "dragover": | ||
| case "dragstart": | ||
| case "drop": | ||
| eventArgs = Deserialize<DragEventArgs>(eventArgsJson); | ||
| eventArgs = Deserialize<DragEventArgs>(eventArgsJson, jsonSerializerContext.DragEventArgs); | ||
| return true; | ||
|
|
||
| case "focus": | ||
| case "blur": | ||
| case "focusin": | ||
| case "focusout": | ||
| eventArgs = Deserialize<FocusEventArgs>(eventArgsJson); | ||
| eventArgs = Deserialize<FocusEventArgs>(eventArgsJson, jsonSerializerContext.FocusEventArgs); | ||
| return true; | ||
|
|
||
| case "keydown": | ||
| case "keyup": | ||
| case "keypress": | ||
| eventArgs = Deserialize<KeyboardEventArgs>(eventArgsJson); | ||
| eventArgs = Deserialize<KeyboardEventArgs>(eventArgsJson, jsonSerializerContext.KeyboardEventArgs); | ||
| return true; | ||
|
|
||
| case "contextmenu": | ||
|
|
@@ -137,11 +152,11 @@ private static bool TryDeserializeStandardWebEventArgs(string eventName, string | |
| case "mousedown": | ||
| case "mouseup": | ||
| case "dblclick": | ||
| eventArgs = Deserialize<MouseEventArgs>(eventArgsJson); | ||
| eventArgs = Deserialize<MouseEventArgs>(eventArgsJson, jsonSerializerContext.MouseEventArgs); | ||
| return true; | ||
|
|
||
| case "error": | ||
| eventArgs = Deserialize<ErrorEventArgs>(eventArgsJson); | ||
| eventArgs = Deserialize<ErrorEventArgs>(eventArgsJson, jsonSerializerContext.ErrorEventArgs); | ||
| return true; | ||
|
|
||
| case "loadstart": | ||
|
|
@@ -150,7 +165,7 @@ private static bool TryDeserializeStandardWebEventArgs(string eventName, string | |
| case "load": | ||
| case "loadend": | ||
| case "progress": | ||
| eventArgs = Deserialize<ProgressEventArgs>(eventArgsJson); | ||
| eventArgs = Deserialize<ProgressEventArgs>(eventArgsJson, jsonSerializerContext.ProgressEventArgs); | ||
| return true; | ||
|
|
||
| case "touchcancel": | ||
|
|
@@ -159,7 +174,7 @@ private static bool TryDeserializeStandardWebEventArgs(string eventName, string | |
| case "touchenter": | ||
| case "touchleave": | ||
| case "touchstart": | ||
| eventArgs = Deserialize<TouchEventArgs>(eventArgsJson); | ||
| eventArgs = Deserialize<TouchEventArgs>(eventArgsJson, jsonSerializerContext.TouchEventArgs); | ||
| return true; | ||
|
|
||
| case "gotpointercapture": | ||
|
|
@@ -172,16 +187,16 @@ private static bool TryDeserializeStandardWebEventArgs(string eventName, string | |
| case "pointerout": | ||
| case "pointerover": | ||
| case "pointerup": | ||
| eventArgs = Deserialize<PointerEventArgs>(eventArgsJson); | ||
| eventArgs = Deserialize<PointerEventArgs>(eventArgsJson, jsonSerializerContext.PointerEventArgs); | ||
| return true; | ||
|
|
||
| case "wheel": | ||
| case "mousewheel": | ||
| eventArgs = Deserialize<WheelEventArgs>(eventArgsJson); | ||
| eventArgs = Deserialize<WheelEventArgs>(eventArgsJson, jsonSerializerContext.WheelEventArgs); | ||
| return true; | ||
|
|
||
| case "toggle": | ||
| eventArgs = Deserialize<EventArgs>(eventArgsJson); | ||
| eventArgs = Deserialize<EventArgs>(eventArgsJson, jsonSerializerContext.EventArgs); | ||
| return true; | ||
|
|
||
| default: | ||
|
|
@@ -219,13 +234,11 @@ private static bool TryDeserializeStandardWebEventArgs(string eventName, string | |
| return null; | ||
| } | ||
|
|
||
| [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "The correct members are preserved by DynamicDependencies.")] | ||
| // This should use JSON source generation | ||
| static T Deserialize<[DynamicallyAccessedMembers(JsonSerialized)] T>(string json) => JsonSerializer.Deserialize<T>(json, JsonSerializerOptionsProvider.Options)!; | ||
| static T Deserialize<T>(string json, JsonTypeInfo<T?> jsonTypeInfo) => JsonSerializer.Deserialize(json, jsonTypeInfo)!; | ||
|
|
||
| private static ChangeEventArgs DeserializeChangeEventArgs(string eventArgsJson) | ||
| private static ChangeEventArgs DeserializeChangeEventArgs(string eventArgsJson, WebEventJsonContext jsonSerializerContext) | ||
| { | ||
| var changeArgs = Deserialize<ChangeEventArgs>(eventArgsJson); | ||
| var changeArgs = Deserialize(eventArgsJson, jsonSerializerContext.ChangeEventArgs); | ||
| var jsonElement = (JsonElement)changeArgs.Value!; | ||
| switch (jsonElement.ValueKind) | ||
| { | ||
|
|
@@ -245,4 +258,22 @@ private static ChangeEventArgs DeserializeChangeEventArgs(string eventArgsJson) | |
| return changeArgs; | ||
| } | ||
| } | ||
|
|
||
| [JsonSerializable(typeof(WebEventDescriptor))] | ||
| [JsonSerializable(typeof(WebEventDescriptor))] | ||
| [JsonSerializable(typeof(EventArgs))] | ||
| [JsonSerializable(typeof(ChangeEventArgs))] | ||
| [JsonSerializable(typeof(ClipboardEventArgs))] | ||
| [JsonSerializable(typeof(DragEventArgs))] | ||
| [JsonSerializable(typeof(ErrorEventArgs))] | ||
| [JsonSerializable(typeof(FocusEventArgs))] | ||
| [JsonSerializable(typeof(KeyboardEventArgs))] | ||
| [JsonSerializable(typeof(MouseEventArgs))] | ||
| [JsonSerializable(typeof(PointerEventArgs))] | ||
| [JsonSerializable(typeof(ProgressEventArgs))] | ||
| [JsonSerializable(typeof(TouchEventArgs))] | ||
| [JsonSerializable(typeof(WheelEventArgs))] | ||
| internal sealed partial class WebEventJsonContext : JsonSerializerContext | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like it would be cleaner to have a global public class JsonSourceGenerationModeAttribute : JsonAttribute
{
public JsonSourceGenerationModeAttribute(JsonSourceGenerationMode mode) { }
}Building on #32374 (comment), we'd have [JsonSourceGenerationMode(JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(WebEventDescriptor)]
[JsonSerializable(typeof(EventArgs)]
internal sealed partial class WebEventJsonContext : JsonSerializerContext
{
}
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That does seem like a better alternative. It feels unusual to want fine-grained control over the serialization behavior on a per-type basis. That said, I imagine most users wouldn't really configure this since the extra IL for serialization isn't really noticeable. |
||
| { | ||
| } | ||
| } | ||
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using System.ComponentModel; | ||
| using System.Text.Json; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.AspNetCore.Components.RenderTree; | ||
| using Microsoft.AspNetCore.Components.Web; | ||
|
|
@@ -18,6 +19,8 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Infrastructure | |
| [EditorBrowsable(EditorBrowsableState.Never)] | ||
| public static class JSInteropMethods | ||
| { | ||
| private static WebEventJsonContext? _jsonContext; | ||
|
|
||
| /// <summary> | ||
| /// For framework use only. | ||
| /// </summary> | ||
|
|
@@ -34,8 +37,16 @@ public static void NotifyLocationChanged(string uri, bool isInterceptedLink) | |
| public static Task DispatchEvent(WebEventDescriptor eventDescriptor, string eventArgsJson) | ||
| { | ||
| var renderer = RendererRegistry.Find(eventDescriptor.BrowserRendererId); | ||
| var jsonSerializerOptions = DefaultWebAssemblyJSRuntime.Instance.ReadJsonSerializerOptions(); | ||
| var webEvent = WebEventData.Parse(renderer, jsonSerializerOptions, eventDescriptor, eventArgsJson); | ||
|
|
||
| // JsonSerializerOptions are tightly bound to the JsonContext. Cache it on first use using a copy | ||
| // of the serializer settings. | ||
| if (_jsonContext is null) | ||
| { | ||
| var jsonSerializerOptions = DefaultWebAssemblyJSRuntime.Instance.ReadJsonSerializerOptions(); | ||
| _jsonContext = new(new JsonSerializerOptions(jsonSerializerOptions)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we move the
pranavkm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| var webEvent = WebEventData.Parse(renderer, _jsonContext, eventDescriptor, eventArgsJson); | ||
| return renderer.DispatchEventAsync( | ||
| webEvent.EventHandlerId, | ||
| webEvent.EventFieldInfo, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.