Skip to content

Commit 8ddf53d

Browse files
authored
Add new methods to JS interop API (#61246)
* Add InvokeNewAsync, GetValueAsync & SetValueAsync to IJSRuntime * Add sync variants to IJSInProcessRuntime * Add relevant variants to IJSObjectReference and IJSInProcessReference * Add BeginInvokeJS overload using JSInvocationInfo configuration DTO to JSRuntime * Add InvokeJS overload using JSInvocationInfo configuration DTO to JSInProcessRuntime * Add support for the new operations to Microsoft.JSInterop.JS
1 parent 335de67 commit 8ddf53d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2015
-162
lines changed

package-lock.json

Lines changed: 24 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,37 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics.CodeAnalysis;
45
using Microsoft.JSInterop;
6+
using static Microsoft.AspNetCore.Internal.LinkerFlags;
57

68
namespace Microsoft.AspNetCore.Components.Endpoints;
79

810
internal sealed class UnsupportedJavaScriptRuntime : IJSRuntime
911
{
1012
private const string Message = "JavaScript interop calls cannot be issued during server-side static rendering, because the page has not yet loaded in the browser. Statically-rendered components must wrap any JavaScript interop calls in conditional logic to ensure those interop calls are not attempted during static rendering.";
1113

12-
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken, object?[]? args)
14+
public ValueTask<TValue> InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, CancellationToken cancellationToken, object?[]? args)
1315
=> throw new InvalidOperationException(Message);
1416

15-
ValueTask<TValue> IJSRuntime.InvokeAsync<TValue>(string identifier, object?[]? args)
17+
ValueTask<TValue> IJSRuntime.InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, object?[]? args)
18+
=> throw new InvalidOperationException(Message);
19+
20+
public ValueTask<IJSObjectReference> InvokeNewAsync(string identifier, object?[]? args)
21+
=> throw new InvalidOperationException(Message);
22+
23+
public ValueTask<IJSObjectReference> InvokeNewAsync(string identifier, CancellationToken cancellationToken, object?[]? args)
24+
=> throw new InvalidOperationException(Message);
25+
26+
public ValueTask<TValue> GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier)
27+
=> throw new InvalidOperationException(Message);
28+
29+
public ValueTask<TValue> GetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, CancellationToken cancellationToken)
30+
=> throw new InvalidOperationException(Message);
31+
32+
public ValueTask SetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value)
33+
=> throw new InvalidOperationException(Message);
34+
35+
public ValueTask SetValueAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, TValue value, CancellationToken cancellationToken)
1636
=> throw new InvalidOperationException(Message);
1737
}

src/Components/Server/src/Circuits/RemoteJSRuntime.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,21 @@ protected override void SendByteArray(int id, byte[] data)
105105
}
106106

107107
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson, JSCallResultType resultType, long targetInstanceId)
108+
{
109+
var invocationInfo = new JSInvocationInfo
110+
{
111+
AsyncHandle = asyncHandle,
112+
TargetInstanceId = targetInstanceId,
113+
Identifier = identifier,
114+
CallType = JSCallType.FunctionCall,
115+
ResultType = resultType,
116+
ArgsJson = argsJson,
117+
};
118+
119+
BeginInvokeJS(invocationInfo);
120+
}
121+
122+
protected override void BeginInvokeJS(in JSInvocationInfo invocationInfo)
108123
{
109124
if (_clientProxy is null)
110125
{
@@ -123,9 +138,16 @@ protected override void BeginInvokeJS(long asyncHandle, string identifier, strin
123138
}
124139
}
125140

126-
Log.BeginInvokeJS(_logger, asyncHandle, identifier);
141+
Log.BeginInvokeJS(_logger, invocationInfo.AsyncHandle, invocationInfo.Identifier);
127142

128-
_clientProxy.SendAsync("JS.BeginInvokeJS", asyncHandle, identifier, argsJson, (int)resultType, targetInstanceId);
143+
_clientProxy.SendAsync(
144+
"JS.BeginInvokeJS",
145+
invocationInfo.AsyncHandle,
146+
invocationInfo.Identifier,
147+
invocationInfo.ArgsJson,
148+
(int)invocationInfo.ResultType,
149+
invocationInfo.TargetInstanceId,
150+
(int)invocationInfo.CallType);
129151
}
130152

131153
protected override void ReceiveByteArray(int id, byte[] data)

src/Components/Server/test/ProtectedBrowserStorageTest.cs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,19 +352,54 @@ private static string ProtectionPrefix(string purpose)
352352

353353
class TestJSRuntime : IJSRuntime
354354
{
355-
public List<(string Identifier, object[] Args)> Invocations { get; }
356-
= new List<(string Identifier, object[] Args)>();
355+
public List<(string Identifier, object[] Args, JSCallType CallType)> Invocations { get; } = [];
357356

358357
public object NextInvocationResult { get; set; }
359358

360359
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken, object[] args)
361360
{
362-
Invocations.Add((identifier, args));
361+
Invocations.Add((identifier, args, JSCallType.FunctionCall));
363362
return (ValueTask<TValue>)NextInvocationResult;
364363
}
365364

366365
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args)
367366
=> InvokeAsync<TValue>(identifier, cancellationToken: CancellationToken.None, args: args);
367+
368+
public ValueTask<IJSObjectReference> InvokeNewAsync(string identifier, object[] args)
369+
{
370+
Invocations.Add((identifier, args, JSCallType.NewCall));
371+
return (ValueTask<IJSObjectReference>)NextInvocationResult;
372+
}
373+
374+
public ValueTask<IJSObjectReference> InvokeNewAsync(string identifier, CancellationToken cancellationToken, object[] args)
375+
{
376+
Invocations.Add((identifier, args, JSCallType.NewCall));
377+
return (ValueTask<IJSObjectReference>)NextInvocationResult;
378+
}
379+
380+
public ValueTask<TValue> GetValueAsync<TValue>(string identifier)
381+
{
382+
Invocations.Add((identifier, [], JSCallType.GetValue));
383+
return (ValueTask<TValue>)NextInvocationResult;
384+
}
385+
386+
public ValueTask<TValue> GetValueAsync<TValue>(string identifier, CancellationToken cancellationToken)
387+
{
388+
Invocations.Add((identifier, [], JSCallType.GetValue));
389+
return (ValueTask<TValue>)NextInvocationResult;
390+
}
391+
392+
public ValueTask SetValueAsync<TValue>(string identifier, TValue value)
393+
{
394+
Invocations.Add((identifier, [value], JSCallType.SetValue));
395+
return ValueTask.CompletedTask;
396+
}
397+
398+
public ValueTask SetValueAsync<TValue>(string identifier, TValue value, CancellationToken cancellationToken)
399+
{
400+
Invocations.Add((identifier, [value], JSCallType.SetValue));
401+
return ValueTask.CompletedTask;
402+
}
368403
}
369404

370405
class TestProtectedBrowserStorage : ProtectedBrowserStorage

src/Components/Web.JS/src/Boot.WebAssembly.Common.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { addDispatchEventMiddleware } from './Rendering/WebRendererInteropMethod
1414
import { WebAssemblyComponentDescriptor, WebAssemblyServerOptions, discoverWebAssemblyPersistedState } from './Services/ComponentDescriptorDiscovery';
1515
import { receiveDotNetDataStream } from './StreamingInterop';
1616
import { WebAssemblyComponentAttacher } from './Platform/WebAssemblyComponentAttacher';
17+
import { DotNet } from '@microsoft/dotnet-js-interop';
1718
import { MonoConfig } from '@microsoft/dotnet-runtime';
1819
import { RootComponentManager } from './Services/RootComponentManager';
1920
import { WebRendererId } from './Rendering/WebRendererId';
@@ -263,12 +264,12 @@ async function scheduleAfterStarted(operations: string): Promise<void> {
263264
Blazor._internal.updateRootComponents(operations);
264265
}
265266

266-
function invokeJSJson(identifier: string, targetInstanceId: number, resultType: number, argsJson: string, asyncHandle: number): string | null {
267+
function invokeJSJson(identifier: string, targetInstanceId: number, resultType: number, argsJson: string, asyncHandle: number, callType: number): string | null {
267268
if (asyncHandle !== 0) {
268-
dispatcher.beginInvokeJSFromDotNet(asyncHandle, identifier, argsJson, resultType, targetInstanceId);
269+
dispatcher.beginInvokeJSFromDotNet(asyncHandle, identifier, argsJson, resultType, targetInstanceId, callType);
269270
return null;
270271
} else {
271-
return dispatcher.invokeJSFromDotNet(identifier, argsJson, resultType, targetInstanceId);
272+
return dispatcher.invokeJSFromDotNet(identifier, argsJson, resultType, targetInstanceId, callType);
272273
}
273274
}
274275

src/Components/Web.JS/src/GlobalExports.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export interface IBlazor {
5252
forceCloseConnection?: () => Promise<void>;
5353
InputFile?: typeof InputFile;
5454
NavigationLock: typeof NavigationLock;
55-
invokeJSJson?: (identifier: string, targetInstanceId: number, resultType: number, argsJson: string, asyncHandle: number) => string | null;
55+
invokeJSJson?: (identifier: string, targetInstanceId: number, resultType: number, argsJson: string, asyncHandle: number, callType: number) => string | null;
5656
endInvokeDotNetFromJS?: (callId: string, success: boolean, resultJsonOrErrorMessage: string) => void;
5757
receiveByteArray?: (id: number, data: Uint8Array) => void;
5858
getPersistedState?: () => string;

src/Components/Web/src/Internal/IInternalWebJSInProcessRuntime.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.ComponentModel;
55
using System.Diagnostics.CodeAnalysis;
66
using Microsoft.JSInterop;
7+
using Microsoft.JSInterop.Infrastructure;
78

89
namespace Microsoft.AspNetCore.Components.Web.Internal;
910

@@ -17,4 +18,9 @@ public interface IInternalWebJSInProcessRuntime
1718
/// For internal framework use only.
1819
/// </summary>
1920
string InvokeJS(string identifier, [StringSyntax(StringSyntaxAttribute.Json)] string? argsJson, JSCallResultType resultType, long targetInstanceId);
21+
22+
/// <summary>
23+
/// For internal framework use only.
24+
/// </summary>
25+
string InvokeJS(in JSInvocationInfo invocationInfo);
2026
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
#nullable enable
2+
Microsoft.AspNetCore.Components.Web.Internal.IInternalWebJSInProcessRuntime.InvokeJS(in Microsoft.JSInterop.Infrastructure.JSInvocationInfo invocationInfo) -> string!
23
virtual Microsoft.AspNetCore.Components.Routing.NavLink.ShouldMatch(string! uriAbsolute) -> bool

src/Components/WebAssembly/JSInterop/src/InternalCalls.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ public static partial string InvokeJSJson(
2323
[JSMarshalAs<JSType.Number>] long targetInstanceId,
2424
int resultType,
2525
string argsJson,
26-
[JSMarshalAs<JSType.Number>] long asyncHandle);
26+
[JSMarshalAs<JSType.Number>] long asyncHandle,
27+
int callType);
2728

2829
[JSImport("Blazor._internal.endInvokeDotNetFromJS", "blazor-internal")]
2930
public static partial void EndInvokeDotNetFromJS(
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
#nullable enable
2+
override Microsoft.JSInterop.WebAssembly.WebAssemblyJSRuntime.BeginInvokeJS(in Microsoft.JSInterop.Infrastructure.JSInvocationInfo invocationInfo) -> void
3+
override Microsoft.JSInterop.WebAssembly.WebAssemblyJSRuntime.InvokeJS(in Microsoft.JSInterop.Infrastructure.JSInvocationInfo invocationInfo) -> string!

src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,30 @@ protected WebAssemblyJSRuntime()
2424

2525
/// <inheritdoc />
2626
protected override string InvokeJS(string identifier, [StringSyntax(StringSyntaxAttribute.Json)] string? argsJson, JSCallResultType resultType, long targetInstanceId)
27+
{
28+
return InternalCalls.InvokeJSJson(
29+
identifier,
30+
targetInstanceId,
31+
(int)resultType,
32+
argsJson ?? "[]",
33+
0,
34+
(int)JSCallType.FunctionCall
35+
);
36+
}
37+
38+
/// <inheritdoc />
39+
protected override string InvokeJS(in JSInvocationInfo invocationInfo)
2740
{
2841
try
2942
{
30-
return InternalCalls.InvokeJSJson(identifier, targetInstanceId, (int)resultType, argsJson ?? "[]", 0);
43+
return InternalCalls.InvokeJSJson(
44+
invocationInfo.Identifier,
45+
invocationInfo.TargetInstanceId,
46+
(int)invocationInfo.ResultType,
47+
invocationInfo.ArgsJson,
48+
invocationInfo.AsyncHandle,
49+
(int)invocationInfo.CallType
50+
);
3151
}
3252
catch (Exception ex)
3353
{
@@ -38,7 +58,27 @@ protected override string InvokeJS(string identifier, [StringSyntax(StringSyntax
3858
/// <inheritdoc />
3959
protected override void BeginInvokeJS(long asyncHandle, string identifier, [StringSyntax(StringSyntaxAttribute.Json)] string? argsJson, JSCallResultType resultType, long targetInstanceId)
4060
{
41-
InternalCalls.InvokeJSJson(identifier, targetInstanceId, (int)resultType, argsJson ?? "[]", asyncHandle);
61+
InternalCalls.InvokeJSJson(
62+
identifier,
63+
targetInstanceId,
64+
(int)resultType,
65+
argsJson ?? "[]",
66+
asyncHandle,
67+
(int)JSCallType.FunctionCall
68+
);
69+
}
70+
71+
/// <inheritdoc />
72+
protected override void BeginInvokeJS(in JSInvocationInfo invocationInfo)
73+
{
74+
InternalCalls.InvokeJSJson(
75+
invocationInfo.Identifier,
76+
invocationInfo.TargetInstanceId,
77+
(int)invocationInfo.ResultType,
78+
invocationInfo.ArgsJson,
79+
invocationInfo.AsyncHandle,
80+
(int)invocationInfo.CallType
81+
);
4282
}
4383

4484
/// <inheritdoc />

src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticationServiceTests.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,24 @@ public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToke
543543
return new ValueTask<TValue>((TValue)GetInvocationResult(identifier));
544544
}
545545

546+
public ValueTask<TValue> GetValueAsync<TValue>(string identifier)
547+
=> throw new NotImplementedException();
548+
549+
public ValueTask<TValue> GetValueAsync<TValue>(string identifier, CancellationToken cancellationToken)
550+
=> throw new NotImplementedException();
551+
552+
public ValueTask<IJSObjectReference> InvokeNewAsync(string identifier, object[] args)
553+
=> throw new NotImplementedException();
554+
555+
public ValueTask<IJSObjectReference> InvokeNewAsync(string identifier, CancellationToken cancellationToken, object[] args)
556+
=> throw new NotImplementedException();
557+
558+
public ValueTask SetValueAsync<TValue>(string identifier, TValue value)
559+
=> throw new NotImplementedException();
560+
561+
public ValueTask SetValueAsync<TValue>(string identifier, TValue value, CancellationToken cancellationToken)
562+
=> throw new NotImplementedException();
563+
546564
private object GetInvocationResult(string identifier)
547565
{
548566
switch (identifier)

src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,7 @@ public override ValueTask SetSignOutState()
777777
private class TestJsRuntime : IJSRuntime
778778
{
779779
public (string identifier, object[] args) LastInvocation { get; set; }
780+
780781
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args)
781782
{
782783
LastInvocation = (identifier, args);
@@ -788,6 +789,24 @@ public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToke
788789
LastInvocation = (identifier, args);
789790
return default;
790791
}
792+
793+
public ValueTask<IJSObjectReference> InvokeNewAsync(string identifier, object[] args)
794+
=> throw new NotImplementedException();
795+
796+
public ValueTask<IJSObjectReference> InvokeNewAsync(string identifier, CancellationToken cancellationToken, object[] args)
797+
=> throw new NotImplementedException();
798+
799+
public ValueTask<TValue> GetValueAsync<TValue>(string identifier)
800+
=> throw new NotImplementedException();
801+
802+
public ValueTask<TValue> GetValueAsync<TValue>(string identifier, CancellationToken cancellationToken)
803+
=> throw new NotImplementedException();
804+
805+
public ValueTask SetValueAsync<TValue>(string identifier, TValue value)
806+
=> throw new NotImplementedException();
807+
808+
public ValueTask SetValueAsync<TValue>(string identifier, TValue value, CancellationToken cancellationToken)
809+
=> throw new NotImplementedException();
791810
}
792811

793812
public class TestRemoteAuthenticatorView : RemoteAuthenticatorViewCore<RemoteAuthenticationState>

src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,7 @@ protected override Task TransmitStreamAsync(long streamId, DotNetStreamReference
168168

169169
string IInternalWebJSInProcessRuntime.InvokeJS(string identifier, string? argsJson, JSCallResultType resultType, long targetInstanceId)
170170
=> InvokeJS(identifier, argsJson, resultType, targetInstanceId);
171+
172+
string IInternalWebJSInProcessRuntime.InvokeJS(in JSInvocationInfo invocationInfo)
173+
=> InvokeJS(invocationInfo);
171174
}

0 commit comments

Comments
 (0)