Skip to content
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

Custom event args: support receiving IJSObjectReference #31591

Merged
merged 5 commits into from
Apr 8, 2021
Merged
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
3 changes: 2 additions & 1 deletion src/Components/Server/src/Circuits/CircuitHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,8 @@ public async Task DispatchEvent(string eventDescriptorJson, string eventArgsJson
WebEventData webEventData;
try
{
webEventData = WebEventData.Parse(Renderer, eventDescriptorJson, eventArgsJson);
var jsonSerializerOptions = JSRuntime.ReadJsonSerializerOptions();
webEventData = WebEventData.Parse(Renderer, jsonSerializerOptions, eventDescriptorJson, eventArgsJson);
}
catch (Exception ex)
{
Expand Down
2 changes: 2 additions & 0 deletions src/Components/Server/src/Circuits/RemoteJSRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public RemoteJSRuntime(IOptions<CircuitOptions> options, ILogger<RemoteJSRuntime
JsonSerializerOptions.Converters.Add(new ElementReferenceJsonConverter(ElementReferenceContext));
}

public JsonSerializerOptions ReadJsonSerializerOptions() => JsonSerializerOptions;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: the reason I didn't use a property-with-getter here is to avoid confusion with the same property on the base class. Other usages of JsonSerializerOptions should access the property from the base directly without going through some extra layer of getter.

Since this is entirely internal API I'd prefer optimizing for not accidentally making an extra method call over the elegance of its appearance.

Same in the two other equivalent cases below.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment to this effect to this API? Also new is your friend

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not using new is specifically what I was referring to with my comment above.


internal void Initialize(CircuitClientProxy clientProxy)
{
_clientProxy = clientProxy ?? throw new ArgumentNullException(nameof(clientProxy));
Expand Down
11 changes: 6 additions & 5 deletions src/Components/Shared/src/WebEventData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ 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, string eventDescriptorJson, string eventArgsJson)
public static WebEventData Parse(Renderer renderer, JsonSerializerOptions jsonSerializerOptions, string eventDescriptorJson, string eventArgsJson)
{
WebEventDescriptor eventDescriptor;
try
Expand All @@ -29,13 +29,14 @@ public static WebEventData Parse(Renderer renderer, string eventDescriptorJson,

return Parse(
renderer,
jsonSerializerOptions,
eventDescriptor,
eventArgsJson);
}

public static WebEventData Parse(Renderer renderer, WebEventDescriptor eventDescriptor, string eventArgsJson)
public static WebEventData Parse(Renderer renderer, JsonSerializerOptions jsonSerializerOptions, WebEventDescriptor eventDescriptor, string eventArgsJson)
{
var parsedEventArgs = ParseEventArgsJson(renderer, eventDescriptor.EventHandlerId, eventDescriptor.EventName, eventArgsJson);
var parsedEventArgs = ParseEventArgsJson(renderer, jsonSerializerOptions, eventDescriptor.EventHandlerId, eventDescriptor.EventName, eventArgsJson);
return new WebEventData(
eventDescriptor.BrowserRendererId,
eventDescriptor.EventHandlerId,
Expand All @@ -59,7 +60,7 @@ private WebEventData(int browserRendererId, ulong eventHandlerId, EventFieldInfo

public EventArgs EventArgs { get; }

private static EventArgs ParseEventArgsJson(Renderer renderer, ulong eventHandlerId, string eventName, string eventArgsJson)
private static EventArgs ParseEventArgsJson(Renderer renderer, JsonSerializerOptions jsonSerializerOptions, ulong eventHandlerId, string eventName, string eventArgsJson)
{
try
{
Expand All @@ -70,7 +71,7 @@ private static EventArgs ParseEventArgsJson(Renderer renderer, ulong eventHandle

// For custom events, the args type is determined from the associated delegate
var eventArgsType = renderer.GetEventArgsType(eventHandlerId);
return (EventArgs)JsonSerializer.Deserialize(eventArgsJson, eventArgsType, JsonSerializerOptionsProvider.Options)!;
return (EventArgs)JsonSerializer.Deserialize(eventArgsJson, eventArgsType, jsonSerializerOptions)!;
}
catch (Exception e)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public static void NotifyLocationChanged(string uri, bool isInterceptedLink)
public static Task DispatchEvent(WebEventDescriptor eventDescriptor, string eventArgsJson)
{
var renderer = RendererRegistry.Find(eventDescriptor.BrowserRendererId);
var webEvent = WebEventData.Parse(renderer, eventDescriptor, eventArgsJson);
var jsonSerializerOptions = DefaultWebAssemblyJSRuntime.Instance.ReadJsonSerializerOptions();
var webEvent = WebEventData.Parse(renderer, jsonSerializerOptions, eventDescriptor, eventArgsJson);
return renderer.DispatchEventAsync(
webEvent.EventHandlerId,
webEvent.EventFieldInfo,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
<argument>ILLink</argument>
<argument>IL2072</argument>
<property name="Scope">member</property>
<property name="Target">M:Microsoft.AspNetCore.Components.Web.WebEventData.ParseEventArgsJson(Microsoft.AspNetCore.Components.RenderTree.Renderer,System.UInt64,System.String,System.String)</property>
<property name="Target">M:Microsoft.AspNetCore.Components.Web.WebEventData.ParseEventArgsJson(Microsoft.AspNetCore.Components.RenderTree.Renderer,System.Text.Json.JsonSerializerOptions,System.UInt64,System.String,System.String)</property>
</attribute>
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
<argument>ILLink</argument>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text.Json;
using Microsoft.JSInterop.Infrastructure;
using Microsoft.JSInterop.WebAssembly;

Expand All @@ -23,6 +24,8 @@ private DefaultWebAssemblyJSRuntime()
JsonSerializerOptions.Converters.Add(new ElementReferenceJsonConverter(ElementReferenceContext));
}

public JsonSerializerOptions ReadJsonSerializerOptions() => JsonSerializerOptions;

// The following methods are invoke via Mono's JS interop mechanism (invoke_method)
public static string? InvokeDotNet(string assemblyName, string methodIdentifier, string dotNetObjectId, string argsJson)
{
Expand Down
3 changes: 2 additions & 1 deletion src/Components/WebView/WebView/src/IpcReceiver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ private void EndInvokeJS(PageContext pageContext, long asyncHandle, bool succeed
private Task DispatchBrowserEventAsync(PageContext pageContext, string eventDescriptor, string eventArgs)
{
var renderer = pageContext.Renderer;
var webEventData = WebEventData.Parse(renderer, eventDescriptor, eventArgs);
var jsonSerializerOptions = pageContext.JSRuntime.ReadJsonSerializerOptions();
var webEventData = WebEventData.Parse(renderer, jsonSerializerOptions, eventDescriptor, eventArgs);
return renderer.DispatchEventAsync(
webEventData.EventHandlerId,
webEventData.EventFieldInfo,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public void AttachToWebView(IpcSender ipcSender)
_ipcSender = ipcSender;
}

public JsonSerializerOptions ReadJsonSerializerOptions() => JsonSerializerOptions;

protected override void BeginInvokeJS(long taskId, string identifier, string argsJson, JSCallResultType resultType, long targetInstanceId)
{
_ipcSender.BeginInvokeJS(taskId, identifier, argsJson, resultType, targetInstanceId);
Expand Down
8 changes: 8 additions & 0 deletions src/Components/test/E2ETest/Tests/EventCustomArgsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,14 @@ public void CanAliasBrowserEvent_WithoutAnyNativeListenerForBrowserEvent()
Browser.True(() => GetLogLines().Contains("Received custom mouseover event"));
}

[Fact]
public void CanRegisterCustomEventAndSupplyIJSObjectReference()
{
Browser.Exists(By.Id("register-sendjsobject")).Click();
Browser.FindElement(By.Id("trigger-sendjsobject-event-directly")).Click();
Browser.Equal("Event with IJSObjectReference received: Hello!", () => GetLogLines().Single());
}

void SendKeysSequentially(IWebElement target, string text)
{
foreach (var c in text)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
@ontestevent="@HandleTestEvent"
@onkeydown.testvariant="@HandleCustomKeyDown"
@onkeydown.yetanother="@HandleYetAnotherKeyboardEvent"
@oncustommouseover="@(e => { LogMessage("Received custom mouseover event"); })">
@oncustommouseover="@(e => { LogMessage("Received custom mouseover event"); })"
@onsendjsobject="HandleEventWithIJSObjectReference">
Event target
<div id="test-event-target-child" style="background: #afa; padding: 1em;">
Child
Expand Down Expand Up @@ -55,6 +56,17 @@
Register custom mouseover event (which has no corresponding native listener)
</button>

<button id="register-sendjsobject"
onclick="Blazor.registerCustomEventType('sendjsobject', { createEventArgs: event => event.detail })">
Register custom event that sends IJSObjectReference
</button>

<button id="trigger-sendjsobject-event-directly"
onclick="document.getElementById('test-event-target-child').dispatchEvent(new CustomEvent('sendjsobject', { bubbles: true, detail: { jsObject: DotNet.createJSObjectReference({ getMyValue: () => 'Hello!' }) } }))">
Trigger sendjsobject event directly
</button>


<p>
<label>
<input type="checkbox" id="custom-keydown-prevent-default" @bind="customKeyDownPreventDefault" />
Expand Down Expand Up @@ -99,4 +111,10 @@
{
LogMessage($"Yet another aliased event received: {eventArgs.YouPressed}");
}

async Task HandleEventWithIJSObjectReference(EventWithIJSObjectReferenceEventArgs eventArgs)
{
var innerValue = await eventArgs.JsObject.InvokeAsync<string>("getMyValue", null);
LogMessage($"Event with IJSObjectReference received: {innerValue}");
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace BasicTestApp.CustomEventTypesNamespace
{
[EventHandler("ontestevent", typeof(TestEventArgs), true, true)]
[EventHandler("onkeydown.testvariant", typeof(TestKeyDownEventArgs), true, true)]
[EventHandler("onkeydown.yetanother", typeof(YetAnotherCustomKeyboardEventArgs), true, true)]
[EventHandler("oncustommouseover", typeof(EventArgs), true, true)]
[EventHandler("onsendjsobject", typeof(EventWithIJSObjectReferenceEventArgs), true, true)]
public static class EventHandlers
{
}
Expand All @@ -25,4 +27,9 @@ class YetAnotherCustomKeyboardEventArgs : EventArgs
{
public string YouPressed { get; set; }
}

class EventWithIJSObjectReferenceEventArgs : EventArgs
{
public IJSObjectReference JsObject { get; set; }
}
}