Skip to content

Commit 50fb6ee

Browse files
committed
Fix handling function references on JS side
1 parent bd2728c commit 50fb6ee

File tree

5 files changed

+27
-14
lines changed

5 files changed

+27
-14
lines changed

src/Components/test/E2ETest/Tests/InteropTest.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ public void CanInvokeInteropMethods()
108108
["invokeNewWithClassConstructorAsync.function"] = "6",
109109
["invokeNewWithNonConstructorAsync"] = "Success",
110110
// Function reference tests
111-
["changeFunctionViaObjectReferenceAsync"] = "42"
111+
["changeFunctionViaObjectReferenceAsync"] = "42",
112+
["invokeDelegateFromAsAsyncFunction"] = "42"
112113
};
113114

114115
var expectedSyncValues = new Dictionary<string, string>

src/Components/test/testassets/BasicTestApp/InteropComponent.razor

+3
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,9 @@
614614
var testClassRef = await JSRuntime.InvokeNewAsync("jsInteropTests.TestClass", "abraka");
615615
await testClassRef.SetValueAsync("getTextLength", funcRef);
616616
ReturnValues["changeFunctionViaObjectReferenceAsync"] = (await testClassRef.InvokeAsync<int>("getTextLength")).ToString();
617+
618+
var funcDelegate = funcRef.AsAsyncFunction<Func<Task<int>>>();
619+
ReturnValues["invokeDelegateFromAsAsyncFunction"] = (await funcDelegate()).ToString();
617620
}
618621

619622
private void FunctionReferenceTests()

src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,17 @@ export module DotNet {
562562
const targetInstance = cachedJSObjectsById[targetInstanceId];
563563

564564
if (targetInstance) {
565-
return targetInstance.resolveInvocationHandler(identifier, callType ?? JSCallType.FunctionCall);
565+
if (identifier) {
566+
return targetInstance.resolveInvocationHandler(identifier, callType ?? JSCallType.FunctionCall);
567+
} else {
568+
const wrappedObject = targetInstance.getWrappedObject();
569+
570+
if (wrappedObject instanceof Function) {
571+
return wrappedObject;
572+
} else {
573+
throw new Error(`JS object instance with ID ${targetInstanceId} is not a function.`);
574+
}
575+
}
566576
}
567577

568578
throw new Error(`JS object instance with ID ${targetInstanceId} does not exist (has it been disposed?).`);
@@ -626,7 +636,10 @@ export module DotNet {
626636
if (!isReadableProperty(parent, memberName)) {
627637
throw new Error(`The property '${identifier}' is not defined or is not readable.`);
628638
}
629-
return () => parent[memberName];
639+
640+
return parent[memberName] instanceof Function
641+
? () => parent[memberName].bind(parent)
642+
: () => parent[memberName];
630643
case JSCallType.SetValue:
631644
if (!isWritableProperty(parent, memberName)) {
632645
throw new Error(`The property '${identifier}' is not writable.`);

src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSFunctionReference.cs

+1-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
namespace Microsoft.JSInterop.Infrastructure;
1010

1111
/// <summary>
12-
/// TODO(OR): Document this.
12+
/// Helper for constructing a Func delegate that wraps interop call to a JavaScript function referenced via <see cref="IJSObjectReference"/>.
1313
/// </summary>
1414
internal readonly struct JSFunctionReference
1515
{
@@ -25,9 +25,6 @@ public JSFunctionReference(IJSObjectReference jsObjectReference)
2525
_jsObjectReference = jsObjectReference;
2626
}
2727

28-
/// <summary>
29-
/// TODO(OR): Document this.
30-
/// </summary>
3128
public static T CreateInvocationDelegate<T>(IJSObjectReference jsObjectReference) where T : Delegate
3229
{
3330
Type delegateType = typeof(T);

src/JSInterop/Microsoft.JSInterop/src/JSObjectReferenceExtensions.cs

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
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.Collections.Concurrent;
54
using System.Diagnostics.CodeAnalysis;
6-
using System.Reflection;
75
using Microsoft.JSInterop.Infrastructure;
86
using static Microsoft.AspNetCore.Internal.LinkerFlags;
97

@@ -171,12 +169,13 @@ public static ValueTask<IJSObjectReference> InvokeNewAsync(this IJSObjectReferen
171169
}
172170

173171
/// <summary>
174-
/// TODO(OR): Document this.
172+
/// Converts a JavaScript function reference into a .NET delegate of the specified type.
175173
/// </summary>
176-
/// <typeparam name="T"></typeparam>
177-
/// <param name="jsObjectReference"></param>
178-
/// <returns></returns>
179-
/// <exception cref="ArgumentException"></exception>
174+
/// <typeparam name="T">The type of the delegate to create. Must be a Func with the result type <see cref="Task"/>, <see cref="Task{R}"/>, <see cref="ValueTask"/>, or <see cref="ValueTask{R}"/>.</typeparam>
175+
/// <param name="jsObjectReference">The JavaScript object reference that represents the function to be invoked.</param>
176+
/// <returns>A Func delegate of type <typeparamref name="T"/> that can be used to invoke the JavaScript function.</returns>
177+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="jsObjectReference"/> is null.</exception>
178+
/// <exception cref="InvalidOperationException">Thrown when <typeparamref name="T"/> is not a valid Func type.</exception>
180179
public static T AsAsyncFunction<T>(this IJSObjectReference jsObjectReference) where T : Delegate
181180
{
182181
ArgumentNullException.ThrowIfNull(jsObjectReference);

0 commit comments

Comments
 (0)