Allow passing DotNetObjectRef to JS in interop calls, and invoking
instance methods on it
This commit is contained in:
parent
38b3051d09
commit
154289ed3d
|
|
@ -32,8 +32,8 @@ function boot() {
|
||||||
connection.start()
|
connection.start()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
DotNet.attachDispatcher({
|
DotNet.attachDispatcher({
|
||||||
beginInvokeDotNetFromJS: (callId, assemblyName, methodIdentifier, argsJson) => {
|
beginInvokeDotNetFromJS: (callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson) => {
|
||||||
connection.send('BeginInvokeDotNetFromJS', callId ? callId.toString() : null, assemblyName, methodIdentifier, argsJson);
|
connection.send('BeginInvokeDotNetFromJS', callId ? callId.toString() : null, assemblyName, methodIdentifier, dotNetObjectId || 0, argsJson);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -292,27 +292,34 @@ function getArrayDataPointer<T>(array: System_Array<T>): number {
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachInteropInvoker() {
|
function attachInteropInvoker() {
|
||||||
const dotNetDispatcherInvokeMethodHandle = findMethod('Microsoft.JSInterop', 'Microsoft.JSInterop', 'DotNetDispatcher', 'Invoke');
|
const dotNetDispatcherInvokeMethodHandle = findMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop', 'MonoWebAssemblyJSRuntime', 'InvokeDotNet');
|
||||||
const dotNetDispatcherBeginInvokeMethodHandle = findMethod('Microsoft.JSInterop', 'Microsoft.JSInterop', 'DotNetDispatcher', 'BeginInvoke');
|
const dotNetDispatcherBeginInvokeMethodHandle = findMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop', 'MonoWebAssemblyJSRuntime', 'BeginInvokeDotNet');
|
||||||
|
|
||||||
DotNet.attachDispatcher({
|
DotNet.attachDispatcher({
|
||||||
beginInvokeDotNetFromJS: (callId, assemblyName, methodIdentifier, argsJson) => {
|
beginInvokeDotNetFromJS: (callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson) => {
|
||||||
|
// As a current limitation, we can only pass 4 args. Fortunately we only need one of
|
||||||
|
// 'assemblyName' or 'dotNetObjectId', so overload them in a single slot
|
||||||
|
const assemblyNameOrDotNetObjectId = dotNetObjectId
|
||||||
|
? dotNetObjectId.toString()
|
||||||
|
: assemblyName;
|
||||||
|
|
||||||
monoPlatform.callMethod(dotNetDispatcherBeginInvokeMethodHandle, null, [
|
monoPlatform.callMethod(dotNetDispatcherBeginInvokeMethodHandle, null, [
|
||||||
callId ? monoPlatform.toDotNetString(callId.toString()) : null,
|
callId ? monoPlatform.toDotNetString(callId.toString()) : null,
|
||||||
monoPlatform.toDotNetString(assemblyName),
|
monoPlatform.toDotNetString(assemblyNameOrDotNetObjectId!),
|
||||||
monoPlatform.toDotNetString(methodIdentifier),
|
monoPlatform.toDotNetString(methodIdentifier),
|
||||||
monoPlatform.toDotNetString(argsJson)
|
monoPlatform.toDotNetString(argsJson)
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
invokeDotNetFromJS: (assemblyName, methodIdentifier, argsJson) => {
|
invokeDotNetFromJS: (assemblyName, methodIdentifier, dotNetObjectId, argsJson) => {
|
||||||
const resultJsonStringPtr = monoPlatform.callMethod(dotNetDispatcherInvokeMethodHandle, null, [
|
const resultJsonStringPtr = monoPlatform.callMethod(dotNetDispatcherInvokeMethodHandle, null, [
|
||||||
monoPlatform.toDotNetString(assemblyName),
|
assemblyName ? monoPlatform.toDotNetString(assemblyName) : null,
|
||||||
monoPlatform.toDotNetString(methodIdentifier),
|
monoPlatform.toDotNetString(methodIdentifier),
|
||||||
|
dotNetObjectId ? monoPlatform.toDotNetString(dotNetObjectId.toString()) : null,
|
||||||
monoPlatform.toDotNetString(argsJson)
|
monoPlatform.toDotNetString(argsJson)
|
||||||
]) as System_String;
|
]) as System_String;
|
||||||
return resultJsonStringPtr
|
return resultJsonStringPtr
|
||||||
? JSON.parse(monoPlatform.toJavaScriptString(resultJsonStringPtr))
|
? monoPlatform.toJavaScriptString(resultJsonStringPtr)
|
||||||
: null;
|
: null;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -51,9 +51,9 @@ namespace Microsoft.AspNetCore.Blazor.Server.Circuits
|
||||||
CircuitHost = circuitHost;
|
CircuitHost = circuitHost;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BeginInvokeDotNetFromJS(string callId, string assemblyName, string methodIdentifier, string argsJson)
|
public void BeginInvokeDotNetFromJS(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson)
|
||||||
{
|
{
|
||||||
EnsureCircuitHost().BeginInvokeDotNetFromJS(callId, assemblyName, methodIdentifier, argsJson);
|
EnsureCircuitHost().BeginInvokeDotNetFromJS(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void CircuitHost_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
private async void CircuitHost_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ namespace Microsoft.AspNetCore.Blazor.Server.Circuits
|
||||||
_isInitialized = true;
|
_isInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void BeginInvokeDotNetFromJS(string callId, string assemblyName, string methodIdentifier, string argsJson)
|
public async void BeginInvokeDotNetFromJS(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson)
|
||||||
{
|
{
|
||||||
AssertInitialized();
|
AssertInitialized();
|
||||||
|
|
||||||
|
|
@ -122,7 +122,7 @@ namespace Microsoft.AspNetCore.Blazor.Server.Circuits
|
||||||
{
|
{
|
||||||
SetCurrentCircuitHost(this);
|
SetCurrentCircuitHost(this);
|
||||||
|
|
||||||
DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, argsJson);
|
DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Blazor
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a reference to a rendered element.
|
/// Represents a reference to a rendered element.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly struct ElementRef : ICustomJsonSerializer
|
public readonly struct ElementRef : ICustomArgSerializer
|
||||||
{
|
{
|
||||||
static long _nextIdForWebAssemblyOnly = 1;
|
static long _nextIdForWebAssemblyOnly = 1;
|
||||||
|
|
||||||
|
|
@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Blazor
|
||||||
internal static ElementRef CreateWithUniqueId()
|
internal static ElementRef CreateWithUniqueId()
|
||||||
=> new ElementRef(CreateUniqueId());
|
=> new ElementRef(CreateUniqueId());
|
||||||
|
|
||||||
object ICustomJsonSerializer.ToJsonPrimitive()
|
object ICustomArgSerializer.ToJsonPrimitive()
|
||||||
{
|
{
|
||||||
return new Dictionary<string, object>
|
return new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.JSInterop.Internal;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
@ -23,17 +24,27 @@ namespace Microsoft.JSInterop
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="assemblyName">The assembly containing the method to be invoked.</param>
|
/// <param name="assemblyName">The assembly containing the method to be invoked.</param>
|
||||||
/// <param name="methodIdentifier">The identifier of the method to be invoked. The method must be annotated with a <see cref="JSInvokableAttribute"/> matching this identifier string.</param>
|
/// <param name="methodIdentifier">The identifier of the method to be invoked. The method must be annotated with a <see cref="JSInvokableAttribute"/> matching this identifier string.</param>
|
||||||
|
/// <param name="dotNetObjectId">For instance method calls, identifies the target object.</param>
|
||||||
/// <param name="argsJson">A JSON representation of the parameters.</param>
|
/// <param name="argsJson">A JSON representation of the parameters.</param>
|
||||||
/// <returns>A JSON representation of the return value, or null.</returns>
|
/// <returns>A JSON representation of the return value, or null.</returns>
|
||||||
public static string Invoke(string assemblyName, string methodIdentifier, string argsJson)
|
public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson)
|
||||||
{
|
{
|
||||||
// This method doesn't need [JSInvokable] because the platform is responsible for having
|
// This method doesn't need [JSInvokable] because the platform is responsible for having
|
||||||
// some way to dispatch calls here. The logic inside here is the thing that checks whether
|
// some way to dispatch calls here. The logic inside here is the thing that checks whether
|
||||||
// the targeted method has [JSInvokable]. It is not itself subject to that restriction,
|
// the targeted method has [JSInvokable]. It is not itself subject to that restriction,
|
||||||
// because there would be nobody to police that. This method *is* the police.
|
// because there would be nobody to police that. This method *is* the police.
|
||||||
|
|
||||||
var syncResult = InvokeSynchronously(assemblyName, methodIdentifier, argsJson);
|
// DotNetDispatcher only works with JSRuntimeBase instances.
|
||||||
return syncResult == null ? null : Json.Serialize(syncResult);
|
var jsRuntime = (JSRuntimeBase)JSRuntime.Current;
|
||||||
|
|
||||||
|
var targetInstance = (object)null;
|
||||||
|
if (dotNetObjectId != default)
|
||||||
|
{
|
||||||
|
targetInstance = jsRuntime.ArgSerializerStrategy.FindDotNetObject(dotNetObjectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson);
|
||||||
|
return syncResult == null ? null : Json.Serialize(syncResult, jsRuntime.ArgSerializerStrategy);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -42,16 +53,26 @@ namespace Microsoft.JSInterop
|
||||||
/// <param name="callId">A value identifying the asynchronous call that should be passed back with the result, or null if no result notification is required.</param>
|
/// <param name="callId">A value identifying the asynchronous call that should be passed back with the result, or null if no result notification is required.</param>
|
||||||
/// <param name="assemblyName">The assembly containing the method to be invoked.</param>
|
/// <param name="assemblyName">The assembly containing the method to be invoked.</param>
|
||||||
/// <param name="methodIdentifier">The identifier of the method to be invoked. The method must be annotated with a <see cref="JSInvokableAttribute"/> matching this identifier string.</param>
|
/// <param name="methodIdentifier">The identifier of the method to be invoked. The method must be annotated with a <see cref="JSInvokableAttribute"/> matching this identifier string.</param>
|
||||||
|
/// <param name="dotNetObjectId">For instance method calls, identifies the target object.</param>
|
||||||
/// <param name="argsJson">A JSON representation of the parameters.</param>
|
/// <param name="argsJson">A JSON representation of the parameters.</param>
|
||||||
/// <returns>A JSON representation of the return value, or null.</returns>
|
/// <returns>A JSON representation of the return value, or null.</returns>
|
||||||
public static void BeginInvoke(string callId, string assemblyName, string methodIdentifier, string argsJson)
|
public static void BeginInvoke(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson)
|
||||||
{
|
{
|
||||||
// This method doesn't need [JSInvokable] because the platform is responsible for having
|
// This method doesn't need [JSInvokable] because the platform is responsible for having
|
||||||
// some way to dispatch calls here. The logic inside here is the thing that checks whether
|
// some way to dispatch calls here. The logic inside here is the thing that checks whether
|
||||||
// the targeted method has [JSInvokable]. It is not itself subject to that restriction,
|
// the targeted method has [JSInvokable]. It is not itself subject to that restriction,
|
||||||
// because there would be nobody to police that. This method *is* the police.
|
// because there would be nobody to police that. This method *is* the police.
|
||||||
|
|
||||||
var syncResult = InvokeSynchronously(assemblyName, methodIdentifier, argsJson);
|
// DotNetDispatcher only works with JSRuntimeBase instances.
|
||||||
|
// If the developer wants to use a totally custom IJSRuntime, then their JS-side
|
||||||
|
// code has to implement its own way of returning async results.
|
||||||
|
var jsRuntimeBaseInstance = (JSRuntimeBase)JSRuntime.Current;
|
||||||
|
|
||||||
|
var targetInstance = dotNetObjectId == default
|
||||||
|
? null
|
||||||
|
: jsRuntimeBaseInstance.ArgSerializerStrategy.FindDotNetObject(dotNetObjectId);
|
||||||
|
|
||||||
|
var syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson);
|
||||||
|
|
||||||
// If there was no callId, the caller does not want to be notified about the result
|
// If there was no callId, the caller does not want to be notified about the result
|
||||||
if (callId != null)
|
if (callId != null)
|
||||||
|
|
@ -61,11 +82,6 @@ namespace Microsoft.JSInterop
|
||||||
var task = syncResult is Task syncResultTask ? syncResultTask : Task.FromResult(syncResult);
|
var task = syncResult is Task syncResultTask ? syncResultTask : Task.FromResult(syncResult);
|
||||||
task.ContinueWith(completedTask =>
|
task.ContinueWith(completedTask =>
|
||||||
{
|
{
|
||||||
// DotNetDispatcher only works with JSRuntimeBase instances.
|
|
||||||
// If the developer wants to use a totally custom IJSRuntime, then their JS-side
|
|
||||||
// code has to implement its own way of returning async results.
|
|
||||||
var jsRuntimeBaseInstance = (JSRuntimeBase)JSRuntime.Current;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = TaskGenericsUtil.GetTaskResult(completedTask);
|
var result = TaskGenericsUtil.GetTaskResult(completedTask);
|
||||||
|
|
@ -80,8 +96,18 @@ namespace Microsoft.JSInterop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object InvokeSynchronously(string assemblyName, string methodIdentifier, string argsJson)
|
private static object InvokeSynchronously(string assemblyName, string methodIdentifier, object targetInstance, string argsJson)
|
||||||
{
|
{
|
||||||
|
if (targetInstance != null)
|
||||||
|
{
|
||||||
|
if (assemblyName != null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"For instance method calls, '{nameof(assemblyName)}' should be null. Value received: '{assemblyName}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
assemblyName = targetInstance.GetType().Assembly.GetName().Name;
|
||||||
|
}
|
||||||
|
|
||||||
var (methodInfo, parameterTypes) = GetCachedMethodInfo(assemblyName, methodIdentifier);
|
var (methodInfo, parameterTypes) = GetCachedMethodInfo(assemblyName, methodIdentifier);
|
||||||
|
|
||||||
// There's no direct way to say we want to deserialize as an array with heterogenous
|
// There's no direct way to say we want to deserialize as an array with heterogenous
|
||||||
|
|
@ -101,16 +127,26 @@ namespace Microsoft.JSInterop
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second, convert each supplied value to the type expected by the method
|
// Second, convert each supplied value to the type expected by the method
|
||||||
var serializerStrategy = SimpleJson.SimpleJson.CurrentJsonSerializerStrategy;
|
var runtime = (JSRuntimeBase)JSRuntime.Current;
|
||||||
|
var serializerStrategy = runtime.ArgSerializerStrategy;
|
||||||
for (var i = 0; i < suppliedArgsLength; i++)
|
for (var i = 0; i < suppliedArgsLength; i++)
|
||||||
{
|
{
|
||||||
suppliedArgs[i] = serializerStrategy.DeserializeObject(
|
if (parameterTypes[i] == typeof(JSAsyncCallResult))
|
||||||
suppliedArgs[i], parameterTypes[i]);
|
{
|
||||||
|
// For JS async call results, we have to defer the deserialization until
|
||||||
|
// later when we know what type it's meant to be deserialized as
|
||||||
|
suppliedArgs[i] = new JSAsyncCallResult(suppliedArgs[i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
suppliedArgs[i] = serializerStrategy.DeserializeObject(
|
||||||
|
suppliedArgs[i], parameterTypes[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return methodInfo.Invoke(null, suppliedArgs);
|
return methodInfo.Invoke(targetInstance, suppliedArgs);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -124,10 +160,28 @@ namespace Microsoft.JSInterop
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="asyncHandle">The identifier for the function invocation.</param>
|
/// <param name="asyncHandle">The identifier for the function invocation.</param>
|
||||||
/// <param name="succeeded">A flag to indicate whether the invocation succeeded.</param>
|
/// <param name="succeeded">A flag to indicate whether the invocation succeeded.</param>
|
||||||
/// <param name="resultOrException">If <paramref name="succeeded"/> is <c>true</c>, specifies the invocation result. If <paramref name="succeeded"/> is <c>false</c>, gives the <see cref="Exception"/> corresponding to the invocation failure.</param>
|
/// <param name="result">If <paramref name="succeeded"/> is <c>true</c>, specifies the invocation result. If <paramref name="succeeded"/> is <c>false</c>, gives the <see cref="Exception"/> corresponding to the invocation failure.</param>
|
||||||
[JSInvokable(nameof(DotNetDispatcher) + "." + nameof(EndInvoke))]
|
[JSInvokable(nameof(DotNetDispatcher) + "." + nameof(EndInvoke))]
|
||||||
public static void EndInvoke(long asyncHandle, bool succeeded, object resultOrException)
|
public static void EndInvoke(long asyncHandle, bool succeeded, JSAsyncCallResult result)
|
||||||
=> ((JSRuntimeBase)JSRuntime.Current).EndInvokeJS(asyncHandle, succeeded, resultOrException);
|
=> ((JSRuntimeBase)JSRuntime.Current).EndInvokeJS(asyncHandle, succeeded, result.ResultOrException);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases the reference to the specified .NET object. This allows the .NET runtime
|
||||||
|
/// to garbage collect that object if there are no other references to it.
|
||||||
|
///
|
||||||
|
/// To avoid leaking memory, the JavaScript side code must call this for every .NET
|
||||||
|
/// object it obtains a reference to. The exception is if that object is used for
|
||||||
|
/// the entire lifetime of a given user's session, in which case it is released
|
||||||
|
/// automatically when the JavaScript runtime is disposed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dotNetObjectId">The identifier previously passed to JavaScript code.</param>
|
||||||
|
[JSInvokable(nameof(DotNetDispatcher) + "." + nameof(ReleaseDotNetObject))]
|
||||||
|
public static void ReleaseDotNetObject(long dotNetObjectId)
|
||||||
|
{
|
||||||
|
// DotNetDispatcher only works with JSRuntimeBase instances.
|
||||||
|
var jsRuntime = (JSRuntimeBase)JSRuntime.Current;
|
||||||
|
jsRuntime.ArgSerializerStrategy.ReleaseDotNetObject(dotNetObjectId);
|
||||||
|
}
|
||||||
|
|
||||||
private static (MethodInfo, Type[]) GetCachedMethodInfo(string assemblyName, string methodIdentifier)
|
private static (MethodInfo, Type[]) GetCachedMethodInfo(string assemblyName, string methodIdentifier)
|
||||||
{
|
{
|
||||||
|
|
@ -156,14 +210,37 @@ namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
// TODO: Consider looking first for assembly-level attributes (i.e., if there are any,
|
// TODO: Consider looking first for assembly-level attributes (i.e., if there are any,
|
||||||
// only use those) to avoid scanning, especially for framework assemblies.
|
// only use those) to avoid scanning, especially for framework assemblies.
|
||||||
return GetRequiredLoadedAssembly(assemblyName)
|
var result = new Dictionary<string, (MethodInfo, Type[])>();
|
||||||
|
var invokableMethods = GetRequiredLoadedAssembly(assemblyName)
|
||||||
.GetExportedTypes()
|
.GetExportedTypes()
|
||||||
.SelectMany(type => type.GetMethods())
|
.SelectMany(type => type.GetMethods())
|
||||||
.Where(method => method.IsDefined(typeof(JSInvokableAttribute), inherit: false))
|
.Where(method => method.IsDefined(typeof(JSInvokableAttribute), inherit: false));
|
||||||
.ToDictionary(
|
foreach (var method in invokableMethods)
|
||||||
method => method.GetCustomAttribute<JSInvokableAttribute>(false).Identifier,
|
{
|
||||||
method => (method, method.GetParameters().Select(p => p.ParameterType).ToArray())
|
var identifier = method.GetCustomAttribute<JSInvokableAttribute>(false).Identifier ?? method.Name;
|
||||||
);
|
var parameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result.Add(identifier, (method, parameterTypes));
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
if (result.ContainsKey(identifier))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"The assembly '{assemblyName}' contains more than one " +
|
||||||
|
$"[JSInvokable] method with identifier '{identifier}'. All [JSInvokable] methods within the same " +
|
||||||
|
$"assembly must have different identifiers. You can pass a custom identifier as a parameter to " +
|
||||||
|
$"the [JSInvokable] attribute.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Assembly GetRequiredLoadedAssembly(string assemblyName)
|
private static Assembly GetRequiredLoadedAssembly(string assemblyName)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Microsoft.JSInterop
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Wraps a JS interop argument, indicating that the value should not be serialized as JSON
|
||||||
|
/// but instead should be passed as a reference.
|
||||||
|
///
|
||||||
|
/// To avoid leaking memory, the reference must later be disposed by JS code or by .NET code.
|
||||||
|
/// </summary>
|
||||||
|
public class DotNetObjectRef : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the object instance represented by this wrapper.
|
||||||
|
/// </summary>
|
||||||
|
public object Value { get; }
|
||||||
|
|
||||||
|
// We track an associated IJSRuntime purely so that this class can be IDisposable
|
||||||
|
// in the normal way. Developers are more likely to use objectRef.Dispose() than
|
||||||
|
// some less familiar API such as JSRuntime.Current.UntrackObjectRef(objectRef).
|
||||||
|
private IJSRuntime _attachedToRuntime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs an instance of <see cref="DotNetObjectRef"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value being wrapped.</param>
|
||||||
|
public DotNetObjectRef(object value)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures the <see cref="DotNetObjectRef"/> is associated with the specified <see cref="IJSRuntime"/>.
|
||||||
|
/// Developers do not normally need to invoke this manually, since it is called automatically by
|
||||||
|
/// framework code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="runtime">The <see cref="IJSRuntime"/>.</param>
|
||||||
|
public void EnsureAttachedToJsRuntime(IJSRuntime runtime)
|
||||||
|
{
|
||||||
|
// The reason we populate _attachedToRuntime here rather than in the constructor
|
||||||
|
// is to ensure developers can't accidentally try to reuse DotNetObjectRef across
|
||||||
|
// different IJSRuntime instances. This method gets called as part of serializing
|
||||||
|
// the DotNetObjectRef during an interop call.
|
||||||
|
|
||||||
|
var existingRuntime = Interlocked.CompareExchange(ref _attachedToRuntime, runtime, null);
|
||||||
|
if (existingRuntime != null && existingRuntime != runtime)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"The {nameof(DotNetObjectRef)} is already associated with a different {nameof(IJSRuntime)}. Do not attempt to re-use {nameof(DotNetObjectRef)} instances with multiple {nameof(IJSRuntime)} instances.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops tracking this object reference, allowing it to be garbage collected
|
||||||
|
/// (if there are no other references to it). Once the instance is disposed, it
|
||||||
|
/// can no longer be used in interop calls from JavaScript code.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_attachedToRuntime?.UntrackObjectRef(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,14 +5,14 @@ namespace Microsoft.JSInterop.Internal
|
||||||
{
|
{
|
||||||
// This is "soft" internal because we're trying to avoid expanding JsonUtil into a sophisticated
|
// This is "soft" internal because we're trying to avoid expanding JsonUtil into a sophisticated
|
||||||
// API. Developers who want that would be better served by using a different JSON package
|
// API. Developers who want that would be better served by using a different JSON package
|
||||||
// instead. Also the perf implications of the ICustomJsonSerializer approach aren't ideal
|
// instead. Also the perf implications of the ICustomArgSerializer approach aren't ideal
|
||||||
// (it forces structs to be boxed, and returning a dictionary means lots more allocations
|
// (it forces structs to be boxed, and returning a dictionary means lots more allocations
|
||||||
// and boxing of any value-typed properties).
|
// and boxing of any value-typed properties).
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal. Intended for framework use only.
|
/// Internal. Intended for framework use only.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ICustomJsonSerializer
|
public interface ICustomArgSerializer
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal. Intended for framework use only.
|
/// Internal. Intended for framework use only.
|
||||||
|
|
@ -18,5 +18,15 @@ namespace Microsoft.JSInterop
|
||||||
/// <param name="args">JSON-serializable arguments.</param>
|
/// <param name="args">JSON-serializable arguments.</param>
|
||||||
/// <returns>An instance of <typeparamref name="T"/> obtained by JSON-deserializing the return value.</returns>
|
/// <returns>An instance of <typeparamref name="T"/> obtained by JSON-deserializing the return value.</returns>
|
||||||
Task<T> InvokeAsync<T>(string identifier, params object[] args);
|
Task<T> InvokeAsync<T>(string identifier, params object[] args);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops tracking the .NET object represented by the <see cref="DotNetObjectRef"/>.
|
||||||
|
/// This allows it to be garbage collected (if nothing else holds a reference to it)
|
||||||
|
/// and means the JS-side code can no longer invoke methods on the instance or pass
|
||||||
|
/// it as an argument to subsequent calls.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dotNetObjectRef">The reference to stop tracking.</param>
|
||||||
|
/// <remarks>This method is called automaticallly by <see cref="DotNetObjectRef.Dispose"/>.</remarks>
|
||||||
|
void UntrackObjectRef(DotNetObjectRef dotNetObjectRef);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.JSInterop.Internal;
|
||||||
|
using SimpleJson;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.JSInterop
|
||||||
|
{
|
||||||
|
internal class InteropArgSerializerStrategy : PocoJsonSerializerStrategy
|
||||||
|
{
|
||||||
|
private readonly JSRuntimeBase _jsRuntime;
|
||||||
|
private const string _dotNetObjectPrefix = "__dotNetObject:";
|
||||||
|
private object _storageLock = new object();
|
||||||
|
private long _nextId = 1; // Start at 1, because 0 signals "no object"
|
||||||
|
private Dictionary<long, DotNetObjectRef> _trackedRefsById = new Dictionary<long, DotNetObjectRef>();
|
||||||
|
private Dictionary<DotNetObjectRef, long> _trackedIdsByRef = new Dictionary<DotNetObjectRef, long>();
|
||||||
|
|
||||||
|
public InteropArgSerializerStrategy(JSRuntimeBase jsRuntime)
|
||||||
|
{
|
||||||
|
_jsRuntime = jsRuntime ?? throw new ArgumentNullException(nameof(jsRuntime));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool TrySerializeKnownTypes(object input, out object output)
|
||||||
|
{
|
||||||
|
switch (input)
|
||||||
|
{
|
||||||
|
case DotNetObjectRef marshalByRefValue:
|
||||||
|
EnsureDotNetObjectTracked(marshalByRefValue, out var id);
|
||||||
|
|
||||||
|
// Special value format recognized by the code in Microsoft.JSInterop.js
|
||||||
|
// If we have to make it more clash-resistant, we can do
|
||||||
|
output = _dotNetObjectPrefix + id;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case ICustomArgSerializer customArgSerializer:
|
||||||
|
output = customArgSerializer.ToJsonPrimitive();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return base.TrySerializeKnownTypes(input, out output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object DeserializeObject(object value, Type type)
|
||||||
|
{
|
||||||
|
if (value is string valueString)
|
||||||
|
{
|
||||||
|
if (valueString.StartsWith(_dotNetObjectPrefix))
|
||||||
|
{
|
||||||
|
var dotNetObjectId = long.Parse(valueString.Substring(_dotNetObjectPrefix.Length));
|
||||||
|
return FindDotNetObject(dotNetObjectId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.DeserializeObject(value, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object FindDotNetObject(long dotNetObjectId)
|
||||||
|
{
|
||||||
|
lock (_storageLock)
|
||||||
|
{
|
||||||
|
return _trackedRefsById.TryGetValue(dotNetObjectId, out var dotNetObjectRef)
|
||||||
|
? dotNetObjectRef.Value
|
||||||
|
: throw new ArgumentException($"There is no tracked object with id '{dotNetObjectId}'. Perhaps the reference was already released.", nameof(dotNetObjectId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops tracking the specified .NET object reference.
|
||||||
|
/// This overload is typically invoked from JS code via JS interop.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dotNetObjectId">The ID of the <see cref="DotNetObjectRef"/>.</param>
|
||||||
|
public void ReleaseDotNetObject(long dotNetObjectId)
|
||||||
|
{
|
||||||
|
lock (_storageLock)
|
||||||
|
{
|
||||||
|
if (_trackedRefsById.TryGetValue(dotNetObjectId, out var dotNetObjectRef))
|
||||||
|
{
|
||||||
|
_trackedRefsById.Remove(dotNetObjectId);
|
||||||
|
_trackedIdsByRef.Remove(dotNetObjectRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops tracking the specified .NET object reference.
|
||||||
|
/// This overload is typically invoked from .NET code by <see cref="DotNetObjectRef.Dispose"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dotNetObjectRef">The <see cref="DotNetObjectRef"/>.</param>
|
||||||
|
public void ReleaseDotNetObject(DotNetObjectRef dotNetObjectRef)
|
||||||
|
{
|
||||||
|
lock (_storageLock)
|
||||||
|
{
|
||||||
|
if (_trackedIdsByRef.TryGetValue(dotNetObjectRef, out var dotNetObjectId))
|
||||||
|
{
|
||||||
|
_trackedRefsById.Remove(dotNetObjectId);
|
||||||
|
_trackedIdsByRef.Remove(dotNetObjectRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureDotNetObjectTracked(DotNetObjectRef dotNetObjectRef, out long dotNetObjectId)
|
||||||
|
{
|
||||||
|
dotNetObjectRef.EnsureAttachedToJsRuntime(_jsRuntime);
|
||||||
|
|
||||||
|
lock (_storageLock)
|
||||||
|
{
|
||||||
|
// Assign an ID only if it doesn't already have one
|
||||||
|
if (!_trackedIdsByRef.TryGetValue(dotNetObjectRef, out dotNetObjectId))
|
||||||
|
{
|
||||||
|
dotNetObjectId = _nextId++;
|
||||||
|
_trackedRefsById.Add(dotNetObjectId, dotNetObjectRef);
|
||||||
|
_trackedIdsByRef.Add(dotNetObjectRef, dotNetObjectId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.JSInterop.Internal
|
||||||
|
{
|
||||||
|
// This type takes care of a special case in handling the result of an async call from
|
||||||
|
// .NET to JS. The information about what type the result should be exists only on the
|
||||||
|
// corresponding TaskCompletionSource<T>. We don't have that information at the time
|
||||||
|
// that we deserialize the incoming argsJson before calling DotNetDispatcher.EndInvoke.
|
||||||
|
// Declaring the EndInvoke parameter type as JSAsyncCallResult defers the deserialization
|
||||||
|
// until later when we have access to the TaskCompletionSource<T>.
|
||||||
|
//
|
||||||
|
// There's no reason why developers would need anything similar to this in user code,
|
||||||
|
// because this is the mechanism by which we resolve the incoming argsJson to the correct
|
||||||
|
// user types before completing calls.
|
||||||
|
//
|
||||||
|
// It's marked as 'public' only because it has to be for use as an argument on a
|
||||||
|
// [JSInvokable] method.
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Intended for framework use only.
|
||||||
|
/// </summary>
|
||||||
|
public class JSAsyncCallResult
|
||||||
|
{
|
||||||
|
internal object ResultOrException { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs an instance of <see cref="JSAsyncCallResult"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="resultOrException">The result of the call.</param>
|
||||||
|
internal JSAsyncCallResult(object resultOrException)
|
||||||
|
{
|
||||||
|
ResultOrException = resultOrException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,8 +17,8 @@ namespace Microsoft.JSInterop
|
||||||
/// <returns>An instance of <typeparamref name="T"/> obtained by JSON-deserializing the return value.</returns>
|
/// <returns>An instance of <typeparamref name="T"/> obtained by JSON-deserializing the return value.</returns>
|
||||||
public T Invoke<T>(string identifier, params object[] args)
|
public T Invoke<T>(string identifier, params object[] args)
|
||||||
{
|
{
|
||||||
var resultJson = InvokeJS(identifier, Json.Serialize(args));
|
var resultJson = InvokeJS(identifier, Json.Serialize(args, ArgSerializerStrategy));
|
||||||
return Json.Deserialize<T>(resultJson);
|
return Json.Deserialize<T>(resultJson, ArgSerializerStrategy);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,23 @@ namespace Microsoft.JSInterop
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the identifier for the method. The identifier must be unique within the scope
|
/// Gets the identifier for the method. The identifier must be unique within the scope
|
||||||
/// of an assembly.
|
/// of an assembly.
|
||||||
|
///
|
||||||
|
/// If not set, the identifier is taken from the name of the method. In this case the
|
||||||
|
/// method name must be unique within the assembly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Identifier { get; }
|
public string Identifier { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs an instance of <see cref="JSInvokableAttribute"/>.
|
/// Constructs an instance of <see cref="JSInvokableAttribute"/> without setting
|
||||||
|
/// an identifier for the method.
|
||||||
|
/// </summary>
|
||||||
|
public JSInvokableAttribute()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs an instance of <see cref="JSInvokableAttribute"/> using the specified
|
||||||
|
/// identifier.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="identifier">An identifier for the method, which must be unique within the scope of the assembly.</param>
|
/// <param name="identifier">An identifier for the method, which must be unique within the scope of the assembly.</param>
|
||||||
public JSInvokableAttribute(string identifier)
|
public JSInvokableAttribute(string identifier)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,20 @@ namespace Microsoft.JSInterop
|
||||||
private readonly ConcurrentDictionary<long, object> _pendingTasks
|
private readonly ConcurrentDictionary<long, object> _pendingTasks
|
||||||
= new ConcurrentDictionary<long, object>();
|
= new ConcurrentDictionary<long, object>();
|
||||||
|
|
||||||
|
internal InteropArgSerializerStrategy ArgSerializerStrategy { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs an instance of <see cref="JSRuntimeBase"/>.
|
||||||
|
/// </summary>
|
||||||
|
public JSRuntimeBase()
|
||||||
|
{
|
||||||
|
ArgSerializerStrategy = new InteropArgSerializerStrategy(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void UntrackObjectRef(DotNetObjectRef dotNetObjectRef)
|
||||||
|
=> ArgSerializerStrategy.ReleaseDotNetObject(dotNetObjectRef);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invokes the specified JavaScript function asynchronously.
|
/// Invokes the specified JavaScript function asynchronously.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -36,7 +50,10 @@ namespace Microsoft.JSInterop
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
BeginInvokeJS(taskId, identifier, args?.Length > 0 ? Json.Serialize(args) : null);
|
var argsJson = args?.Length > 0
|
||||||
|
? Json.Serialize(args, ArgSerializerStrategy)
|
||||||
|
: null;
|
||||||
|
BeginInvokeJS(taskId, identifier, argsJson);
|
||||||
return tcs.Task;
|
return tcs.Task;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|
@ -70,7 +87,7 @@ namespace Microsoft.JSInterop
|
||||||
callId,
|
callId,
|
||||||
success,
|
success,
|
||||||
resultOrException
|
resultOrException
|
||||||
}));
|
}, ArgSerializerStrategy));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void EndInvokeJS(long asyncHandle, bool succeeded, object resultOrException)
|
internal void EndInvokeJS(long asyncHandle, bool succeeded, object resultOrException)
|
||||||
|
|
@ -82,7 +99,11 @@ namespace Microsoft.JSInterop
|
||||||
|
|
||||||
if (succeeded)
|
if (succeeded)
|
||||||
{
|
{
|
||||||
TaskGenericsUtil.SetTaskCompletionSourceResult(tcs, resultOrException);
|
var resultType = TaskGenericsUtil.GetTaskCompletionSourceResultType(tcs);
|
||||||
|
var resultValue = resultOrException is SimpleJson.JsonObject
|
||||||
|
? ArgSerializerStrategy.DeserializeObject(resultOrException, resultType)
|
||||||
|
: resultOrException;
|
||||||
|
TaskGenericsUtil.SetTaskCompletionSourceResult(tcs, resultValue);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -40,13 +40,7 @@ module DotNet {
|
||||||
* @returns The result of the operation.
|
* @returns The result of the operation.
|
||||||
*/
|
*/
|
||||||
export function invokeMethod<T>(assemblyName: string, methodIdentifier: string, ...args: any[]): T {
|
export function invokeMethod<T>(assemblyName: string, methodIdentifier: string, ...args: any[]): T {
|
||||||
const dispatcher = getRequiredDispatcher();
|
return invokePossibleInstanceMethod<T>(assemblyName, methodIdentifier, null, args);
|
||||||
if (dispatcher.invokeDotNetFromJS) {
|
|
||||||
const argsJson = JSON.stringify(args);
|
|
||||||
return dispatcher.invokeDotNetFromJS(assemblyName, methodIdentifier, argsJson);
|
|
||||||
} else {
|
|
||||||
throw new Error('The current dispatcher does not support synchronous calls from JS to .NET. Use invokeAsync instead.');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -58,14 +52,29 @@ module DotNet {
|
||||||
* @returns A promise representing the result of the operation.
|
* @returns A promise representing the result of the operation.
|
||||||
*/
|
*/
|
||||||
export function invokeMethodAsync<T>(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise<T> {
|
export function invokeMethodAsync<T>(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise<T> {
|
||||||
|
return invokePossibleInstanceMethodAsync(assemblyName, methodIdentifier, null, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function invokePossibleInstanceMethod<T>(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[]): T {
|
||||||
|
const dispatcher = getRequiredDispatcher();
|
||||||
|
if (dispatcher.invokeDotNetFromJS) {
|
||||||
|
const argsJson = JSON.stringify(args, argReplacer);
|
||||||
|
const resultJson = dispatcher.invokeDotNetFromJS(assemblyName, methodIdentifier, dotNetObjectId, argsJson);
|
||||||
|
return resultJson ? parseJsonWithRevivers(resultJson) : null;
|
||||||
|
} else {
|
||||||
|
throw new Error('The current dispatcher does not support synchronous calls from JS to .NET. Use invokeAsync instead.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function invokePossibleInstanceMethodAsync<T>(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[]): Promise<T> {
|
||||||
const asyncCallId = nextAsyncCallId++;
|
const asyncCallId = nextAsyncCallId++;
|
||||||
const resultPromise = new Promise<T>((resolve, reject) => {
|
const resultPromise = new Promise<T>((resolve, reject) => {
|
||||||
pendingAsyncCalls[asyncCallId] = { resolve, reject };
|
pendingAsyncCalls[asyncCallId] = { resolve, reject };
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const argsJson = JSON.stringify(args);
|
const argsJson = JSON.stringify(args, argReplacer);
|
||||||
getRequiredDispatcher().beginInvokeDotNetFromJS(asyncCallId, assemblyName, methodIdentifier, argsJson);
|
getRequiredDispatcher().beginInvokeDotNetFromJS(asyncCallId, assemblyName, methodIdentifier, dotNetObjectId, argsJson);
|
||||||
} catch(ex) {
|
} catch(ex) {
|
||||||
// Synchronous failure
|
// Synchronous failure
|
||||||
completePendingCall(asyncCallId, false, ex);
|
completePendingCall(asyncCallId, false, ex);
|
||||||
|
|
@ -108,22 +117,24 @@ module DotNet {
|
||||||
/**
|
/**
|
||||||
* Optional. If implemented, invoked by the runtime to perform a synchronous call to a .NET method.
|
* Optional. If implemented, invoked by the runtime to perform a synchronous call to a .NET method.
|
||||||
*
|
*
|
||||||
* @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke.
|
* @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke. The value may be null when invoking instance methods.
|
||||||
* @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier.
|
* @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier.
|
||||||
|
* @param dotNetObjectId If given, the call will be to an instance method on the specified DotNetObject. Pass null or undefined to call static methods.
|
||||||
* @param argsJson JSON representation of arguments to pass to the method.
|
* @param argsJson JSON representation of arguments to pass to the method.
|
||||||
* @returns The result of the invocation.
|
* @returns JSON representation of the result of the invocation.
|
||||||
*/
|
*/
|
||||||
invokeDotNetFromJS?(assemblyName: string, methodIdentifier: string, argsJson: string): any;
|
invokeDotNetFromJS?(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): string | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked by the runtime to begin an asynchronous call to a .NET method.
|
* Invoked by the runtime to begin an asynchronous call to a .NET method.
|
||||||
*
|
*
|
||||||
* @param callId A value identifying the asynchronous operation. This value should be passed back in a later call from .NET to JS.
|
* @param callId A value identifying the asynchronous operation. This value should be passed back in a later call from .NET to JS.
|
||||||
* @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke.
|
* @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke. The value may be null when invoking instance methods.
|
||||||
* @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier.
|
* @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier.
|
||||||
|
* @param dotNetObjectId If given, the call will be to an instance method on the specified DotNetObject. Pass null to call static methods.
|
||||||
* @param argsJson JSON representation of arguments to pass to the method.
|
* @param argsJson JSON representation of arguments to pass to the method.
|
||||||
*/
|
*/
|
||||||
beginInvokeDotNetFromJS(callId: number, assemblyName: string, methodIdentifier: string, argsJson: string): void;
|
beginInvokeDotNetFromJS(callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -149,7 +160,7 @@ module DotNet {
|
||||||
const result = findJSFunction(identifier).apply(null, parseJsonWithRevivers(argsJson));
|
const result = findJSFunction(identifier).apply(null, parseJsonWithRevivers(argsJson));
|
||||||
return result === null || result === undefined
|
return result === null || result === undefined
|
||||||
? null
|
? null
|
||||||
: JSON.stringify(result);
|
: JSON.stringify(result, argReplacer);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -172,8 +183,8 @@ module DotNet {
|
||||||
// On completion, dispatch result back to .NET
|
// On completion, dispatch result back to .NET
|
||||||
// Not using "await" because it codegens a lot of boilerplate
|
// Not using "await" because it codegens a lot of boilerplate
|
||||||
promise.then(
|
promise.then(
|
||||||
result => getRequiredDispatcher().beginInvokeDotNetFromJS(0, 'Microsoft.JSInterop', 'DotNetDispatcher.EndInvoke', JSON.stringify([asyncHandle, true, result])),
|
result => getRequiredDispatcher().beginInvokeDotNetFromJS(0, 'Microsoft.JSInterop', 'DotNetDispatcher.EndInvoke', null, JSON.stringify([asyncHandle, true, result], argReplacer)),
|
||||||
error => getRequiredDispatcher().beginInvokeDotNetFromJS(0, 'Microsoft.JSInterop', 'DotNetDispatcher.EndInvoke', JSON.stringify([asyncHandle, false, formatError(error)]))
|
error => getRequiredDispatcher().beginInvokeDotNetFromJS(0, 'Microsoft.JSInterop', 'DotNetDispatcher.EndInvoke', null, JSON.stringify([asyncHandle, false, formatError(error)]))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -231,4 +242,46 @@ module DotNet {
|
||||||
throw new Error(`The value '${resultIdentifier}' is not a function.`);
|
throw new Error(`The value '${resultIdentifier}' is not a function.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DotNetObject {
|
||||||
|
constructor(private _id: number) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public invokeMethod<T>(methodIdentifier: string, ...args: any[]): T {
|
||||||
|
return invokePossibleInstanceMethod<T>(null, methodIdentifier, this._id, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public invokeMethodAsync<T>(methodIdentifier: string, ...args: any[]): Promise<T> {
|
||||||
|
return invokePossibleInstanceMethodAsync<T>(null, methodIdentifier, this._id, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose() {
|
||||||
|
const promise = invokeMethodAsync<any>(
|
||||||
|
'Microsoft.JSInterop',
|
||||||
|
'DotNetDispatcher.ReleaseDotNetObject',
|
||||||
|
this._id);
|
||||||
|
promise.catch(error => console.error(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
public serializeAsArg() {
|
||||||
|
return `__dotNetObject:${this._id}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dotNetObjectValueFormat = /^__dotNetObject\:(\d+)$/;
|
||||||
|
attachReviver(function reviveDotNetObject(key: any, value: any) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const match = value.match(dotNetObjectValueFormat);
|
||||||
|
if (match) {
|
||||||
|
return new DotNetObject(parseInt(match[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unrecognized - let another reviver handle it
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
|
||||||
|
function argReplacer(key: string, value: any) {
|
||||||
|
return value instanceof DotNetObject ? value.serializeAsArg() : value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,9 @@ namespace Microsoft.JSInterop
|
||||||
public static string Serialize(object value)
|
public static string Serialize(object value)
|
||||||
=> SimpleJson.SimpleJson.SerializeObject(value);
|
=> SimpleJson.SimpleJson.SerializeObject(value);
|
||||||
|
|
||||||
|
internal static string Serialize(object value, SimpleJson.IJsonSerializerStrategy serializerStrategy)
|
||||||
|
=> SimpleJson.SimpleJson.SerializeObject(value, serializerStrategy);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deserializes the JSON string, creating an object of the specified generic type.
|
/// Deserializes the JSON string, creating an object of the specified generic type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -29,5 +32,8 @@ namespace Microsoft.JSInterop
|
||||||
/// <returns>An object of the specified type.</returns>
|
/// <returns>An object of the specified type.</returns>
|
||||||
public static T Deserialize<T>(string json)
|
public static T Deserialize<T>(string json)
|
||||||
=> SimpleJson.SimpleJson.DeserializeObject<T>(json);
|
=> SimpleJson.SimpleJson.DeserializeObject<T>(json);
|
||||||
|
|
||||||
|
internal static T Deserialize<T>(string json, SimpleJson.IJsonSerializerStrategy serializerStrategy)
|
||||||
|
=> SimpleJson.SimpleJson.DeserializeObject<T>(json, serializerStrategy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,6 @@ using System.Reflection;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
using Microsoft.JSInterop.Internal;
|
|
||||||
using SimpleJson.Reflection;
|
using SimpleJson.Reflection;
|
||||||
|
|
||||||
// ReSharper disable LoopCanBeConvertedToQuery
|
// ReSharper disable LoopCanBeConvertedToQuery
|
||||||
|
|
@ -1538,8 +1537,6 @@ namespace SimpleJson
|
||||||
output = input.ToString();
|
output = input.ToString();
|
||||||
else if (input is TimeSpan)
|
else if (input is TimeSpan)
|
||||||
output = ((TimeSpan)input).ToString("c");
|
output = ((TimeSpan)input).ToString("c");
|
||||||
else if (input is ICustomJsonSerializer customJsonSerializer)
|
|
||||||
output = customJsonSerializer.ToJsonPrimitive();
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Enum inputEnum = input as Enum;
|
Enum inputEnum = input as Enum;
|
||||||
|
|
|
||||||
|
|
@ -22,26 +22,41 @@ namespace Microsoft.JSInterop
|
||||||
public static void SetTaskCompletionSourceException(object taskCompletionSource, Exception exception)
|
public static void SetTaskCompletionSourceException(object taskCompletionSource, Exception exception)
|
||||||
=> CreateResultSetter(taskCompletionSource).SetException(taskCompletionSource, exception);
|
=> CreateResultSetter(taskCompletionSource).SetException(taskCompletionSource, exception);
|
||||||
|
|
||||||
|
public static Type GetTaskCompletionSourceResultType(object taskCompletionSource)
|
||||||
|
=> CreateResultSetter(taskCompletionSource).ResultType;
|
||||||
|
|
||||||
public static object GetTaskResult(Task task)
|
public static object GetTaskResult(Task task)
|
||||||
{
|
{
|
||||||
var getter = _cachedResultGetters.GetOrAdd(task.GetType(), taskType =>
|
var getter = _cachedResultGetters.GetOrAdd(task.GetType(), taskInstanceType =>
|
||||||
{
|
{
|
||||||
if (taskType.IsGenericType)
|
var resultType = GetTaskResultType(taskInstanceType);
|
||||||
{
|
return resultType == null
|
||||||
var resultType = taskType.GetGenericArguments().Single();
|
? new VoidTaskResultGetter()
|
||||||
return (ITaskResultGetter)Activator.CreateInstance(
|
: (ITaskResultGetter)Activator.CreateInstance(
|
||||||
typeof(TaskResultGetter<>).MakeGenericType(resultType));
|
typeof(TaskResultGetter<>).MakeGenericType(resultType));
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new VoidTaskResultGetter();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return getter.GetResult(task);
|
return getter.GetResult(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Type GetTaskResultType(Type taskType)
|
||||||
|
{
|
||||||
|
// It might be something derived from Task or Task<T>, so we have to scan
|
||||||
|
// up the inheritance hierarchy to find the Task or Task<T>
|
||||||
|
while (taskType != typeof(Task) &&
|
||||||
|
(!taskType.IsGenericType || taskType.GetGenericTypeDefinition() != typeof(Task<>)))
|
||||||
|
{
|
||||||
|
taskType = taskType.BaseType
|
||||||
|
?? throw new ArgumentException($"The type '{taskType.FullName}' is not inherited from '{typeof(Task).FullName}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return taskType.IsGenericType
|
||||||
|
? taskType.GetGenericArguments().Single()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
interface ITcsResultSetter
|
interface ITcsResultSetter
|
||||||
{
|
{
|
||||||
|
Type ResultType { get; }
|
||||||
void SetResult(object taskCompletionSource, object result);
|
void SetResult(object taskCompletionSource, object result);
|
||||||
void SetException(object taskCompletionSource, Exception exception);
|
void SetException(object taskCompletionSource, Exception exception);
|
||||||
}
|
}
|
||||||
|
|
@ -67,10 +82,18 @@ namespace Microsoft.JSInterop
|
||||||
|
|
||||||
private class TcsResultSetter<T> : ITcsResultSetter
|
private class TcsResultSetter<T> : ITcsResultSetter
|
||||||
{
|
{
|
||||||
|
public Type ResultType => typeof(T);
|
||||||
|
|
||||||
public void SetResult(object tcs, object result)
|
public void SetResult(object tcs, object result)
|
||||||
{
|
{
|
||||||
var typedTcs = (TaskCompletionSource<T>)tcs;
|
var typedTcs = (TaskCompletionSource<T>)tcs;
|
||||||
typedTcs.SetResult((T)result);
|
|
||||||
|
// If necessary, attempt a cast
|
||||||
|
var typedResult = result is T resultT
|
||||||
|
? resultT
|
||||||
|
: (T)Convert.ChangeType(result, typeof(T));
|
||||||
|
|
||||||
|
typedTcs.SetResult(typedResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetException(object tcs, Exception exception)
|
public void SetException(object tcs, Exception exception)
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,32 @@ namespace Mono.WebAssembly.Interop
|
||||||
InternalCalls.InvokeJSMarshalled(out _, ref asyncHandle, identifier, argsJson);
|
InternalCalls.InvokeJSMarshalled(out _, ref asyncHandle, identifier, argsJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invoked via Mono's JS interop mechanism (invoke_method)
|
||||||
|
private static string InvokeDotNet(string assemblyName, string methodIdentifier, string dotNetObjectId, string argsJson)
|
||||||
|
=> DotNetDispatcher.Invoke(assemblyName, methodIdentifier, dotNetObjectId == null ? default : long.Parse(dotNetObjectId), argsJson);
|
||||||
|
|
||||||
|
// Invoked via Mono's JS interop mechanism (invoke_method)
|
||||||
|
private static void BeginInvokeDotNet(string callId, string assemblyNameOrDotNetObjectId, string methodIdentifier, string argsJson)
|
||||||
|
{
|
||||||
|
// Figure out whether 'assemblyNameOrDotNetObjectId' is the assembly name or the instance ID
|
||||||
|
// We only need one for any given call. This helps to work around the limitation that we can
|
||||||
|
// only pass a maximum of 4 args in a call from JS to Mono WebAssembly.
|
||||||
|
string assemblyName;
|
||||||
|
long dotNetObjectId;
|
||||||
|
if (char.IsDigit(assemblyNameOrDotNetObjectId[0]))
|
||||||
|
{
|
||||||
|
dotNetObjectId = long.Parse(assemblyNameOrDotNetObjectId);
|
||||||
|
assemblyName = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dotNetObjectId = default;
|
||||||
|
assemblyName = assemblyNameOrDotNetObjectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson);
|
||||||
|
}
|
||||||
|
|
||||||
#region Custom MonoWebAssemblyJSRuntime methods
|
#region Custom MonoWebAssemblyJSRuntime methods
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using BasicTestApp;
|
using BasicTestApp;
|
||||||
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
|
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
|
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
|
||||||
|
|
@ -33,45 +31,63 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
||||||
["VoidParameterless"] = "[]",
|
["VoidParameterless"] = "[]",
|
||||||
["VoidWithOneParameter"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]",
|
["VoidWithOneParameter"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]",
|
||||||
["VoidWithTwoParameters"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]",
|
["VoidWithTwoParameters"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]",
|
||||||
["VoidWithThreeParameters"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,6]",
|
["VoidWithThreeParameters"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,123]",
|
||||||
["VoidWithFourParameters"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,8,16]",
|
["VoidWithFourParameters"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,123,16]",
|
||||||
["VoidWithFiveParameters"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,10,20,40]",
|
["VoidWithFiveParameters"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,123,20,40]",
|
||||||
["VoidWithSixParameters"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,12,24,48,6.25]",
|
["VoidWithSixParameters"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,123,24,48,6.25]",
|
||||||
["VoidWithSevenParameters"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,14,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
|
["VoidWithSevenParameters"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,123,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
|
||||||
["VoidWithEightParameters"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,16,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
|
["VoidWithEightParameters"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,123,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
|
||||||
["VoidParameterlessAsync"] = "[]",
|
["VoidParameterlessAsync"] = "[]",
|
||||||
["VoidWithOneParameterAsync"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]",
|
["VoidWithOneParameterAsync"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]",
|
||||||
["VoidWithTwoParametersAsync"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]",
|
["VoidWithTwoParametersAsync"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]",
|
||||||
["VoidWithThreeParametersAsync"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,6]",
|
["VoidWithThreeParametersAsync"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,123]",
|
||||||
["VoidWithFourParametersAsync"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,8,16]",
|
["VoidWithFourParametersAsync"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,123,16]",
|
||||||
["VoidWithFiveParametersAsync"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,10,20,40]",
|
["VoidWithFiveParametersAsync"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,123,20,40]",
|
||||||
["VoidWithSixParametersAsync"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,12,24,48,6.25]",
|
["VoidWithSixParametersAsync"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,123,24,48,6.25]",
|
||||||
["VoidWithSevenParametersAsync"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,14,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
|
["VoidWithSevenParametersAsync"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,123,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
|
||||||
["VoidWithEightParametersAsync"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,16,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
|
["VoidWithEightParametersAsync"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,123,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
|
||||||
["result1"] = @"[0.1,0.2]",
|
["result1"] = @"[0.1,0.2]",
|
||||||
["result2"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]",
|
["result2"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]",
|
||||||
["result3"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]",
|
["result3"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]",
|
||||||
["result4"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,6]",
|
["result4"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,123]",
|
||||||
["result5"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,8,16]",
|
["result5"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,123,16]",
|
||||||
["result6"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,10,20,40]",
|
["result6"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,123,20,40]",
|
||||||
["result7"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,12,24,48,6.25]",
|
["result7"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,123,24,48,6.25]",
|
||||||
["result8"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,14,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
|
["result8"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,123,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
|
||||||
["result9"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,16,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
|
["result9"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,123,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
|
||||||
["result1Async"] = @"[0.1,0.2]",
|
["result1Async"] = @"[0.1,0.2]",
|
||||||
["result2Async"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]",
|
["result2Async"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]",
|
||||||
["result3Async"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]",
|
["result3Async"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]",
|
||||||
["result4Async"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,6]",
|
["result4Async"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,123]",
|
||||||
["result5Async"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,8,16]",
|
["result5Async"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,123,16]",
|
||||||
["result6Async"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,10,20,40]",
|
["result6Async"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,123,20,40]",
|
||||||
["result7Async"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,12,24,48,6.25]",
|
["result7Async"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,123,24,48,6.25]",
|
||||||
["result8Async"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,14,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
|
["result8Async"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,123,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
|
||||||
["result9Async"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,16,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
|
["result9Async"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,123,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
|
||||||
["ThrowException"] = @"""System.InvalidOperationException: Threw an exception!",
|
["ThrowException"] = @"""System.InvalidOperationException: Threw an exception!",
|
||||||
["AsyncThrowSyncException"] = @"""System.InvalidOperationException: Threw a sync exception!",
|
["AsyncThrowSyncException"] = @"""System.InvalidOperationException: Threw a sync exception!",
|
||||||
["AsyncThrowAsyncException"] = @"""System.InvalidOperationException: Threw an async exception!",
|
["AsyncThrowAsyncException"] = @"""System.InvalidOperationException: Threw an async exception!",
|
||||||
["ExceptionFromSyncMethod"] = "Function threw an exception!",
|
["ExceptionFromSyncMethod"] = "Function threw an exception!",
|
||||||
["SyncExceptionFromAsyncMethod"] = "Function threw a sync exception!",
|
["SyncExceptionFromAsyncMethod"] = "Function threw a sync exception!",
|
||||||
["AsyncExceptionFromAsyncMethod"] = "Function threw an async exception!",
|
["AsyncExceptionFromAsyncMethod"] = "Function threw an async exception!",
|
||||||
|
["resultReturnDotNetObjectByRefSync"] = "1000",
|
||||||
|
["resultReturnDotNetObjectByRefAsync"] = "1001",
|
||||||
|
["instanceMethodThisTypeName"] = @"""JavaScriptInterop""",
|
||||||
|
["instanceMethodStringValueUpper"] = @"""MY STRING""",
|
||||||
|
["instanceMethodIncomingByRef"] = "123",
|
||||||
|
["instanceMethodOutgoingByRef"] = "1234",
|
||||||
|
["instanceMethodThisTypeNameAsync"] = @"""JavaScriptInterop""",
|
||||||
|
["instanceMethodStringValueUpperAsync"] = @"""MY STRING""",
|
||||||
|
["instanceMethodIncomingByRefAsync"] = "123",
|
||||||
|
["instanceMethodOutgoingByRefAsync"] = "1234",
|
||||||
|
["stringValueUpperSync"] = "MY STRING",
|
||||||
|
["testDtoNonSerializedValueSync"] = "99999",
|
||||||
|
["testDtoSync"] = "Same",
|
||||||
|
["stringValueUpperAsync"] = "MY STRING",
|
||||||
|
["testDtoNonSerializedValueAsync"] = "99999",
|
||||||
|
["testDtoAsync"] = "Same",
|
||||||
|
["returnPrimitive"] = "123",
|
||||||
|
["returnPrimitiveAsync"] = "123",
|
||||||
};
|
};
|
||||||
var actualValues = new Dictionary<string, string>();
|
var actualValues = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop.Test
|
namespace Microsoft.JSInterop.Test
|
||||||
|
|
@ -11,13 +12,15 @@ namespace Microsoft.JSInterop.Test
|
||||||
{
|
{
|
||||||
private readonly static string thisAssemblyName
|
private readonly static string thisAssemblyName
|
||||||
= typeof(DotNetDispatcherTest).Assembly.GetName().Name;
|
= typeof(DotNetDispatcherTest).Assembly.GetName().Name;
|
||||||
|
private readonly TestJSRuntime jsRuntime
|
||||||
|
= new TestJSRuntime();
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CannotInvokeWithEmptyAssemblyName()
|
public void CannotInvokeWithEmptyAssemblyName()
|
||||||
{
|
{
|
||||||
var ex = Assert.Throws<ArgumentException>(() =>
|
var ex = Assert.Throws<ArgumentException>(() =>
|
||||||
{
|
{
|
||||||
DotNetDispatcher.Invoke(" ", "SomeMethod", "[]");
|
DotNetDispatcher.Invoke(" ", "SomeMethod", default, "[]");
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message);
|
Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message);
|
||||||
|
|
@ -29,7 +32,7 @@ namespace Microsoft.JSInterop.Test
|
||||||
{
|
{
|
||||||
var ex = Assert.Throws<ArgumentException>(() =>
|
var ex = Assert.Throws<ArgumentException>(() =>
|
||||||
{
|
{
|
||||||
DotNetDispatcher.Invoke("SomeAssembly", " ", "[]");
|
DotNetDispatcher.Invoke("SomeAssembly", " ", default, "[]");
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message);
|
Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message);
|
||||||
|
|
@ -42,75 +45,172 @@ namespace Microsoft.JSInterop.Test
|
||||||
var assemblyName = "Some.Fake.Assembly";
|
var assemblyName = "Some.Fake.Assembly";
|
||||||
var ex = Assert.Throws<ArgumentException>(() =>
|
var ex = Assert.Throws<ArgumentException>(() =>
|
||||||
{
|
{
|
||||||
DotNetDispatcher.Invoke(assemblyName, "SomeMethod", null);
|
DotNetDispatcher.Invoke(assemblyName, "SomeMethod", default, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.Equal($"There is no loaded assembly with the name '{assemblyName}'.", ex.Message);
|
Assert.Equal($"There is no loaded assembly with the name '{assemblyName}'.", ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Currently it's also not possible to invoke instance or generic methods.
|
// Note: Currently it's also not possible to invoke generic methods.
|
||||||
// That's not something determined by DotNetDispatcher, but rather by the fact that we
|
// That's not something determined by DotNetDispatcher, but rather by the fact that we
|
||||||
// don't pass any 'target' or close over the generics in the reflection code.
|
// don't close over the generics in the reflection code.
|
||||||
// Not defining this behavior through unit tests because the default outcome is
|
// Not defining this behavior through unit tests because the default outcome is
|
||||||
// fine (an exception stating what info is missing), plus we're likely to add support
|
// fine (an exception stating what info is missing).
|
||||||
// for invoking instance methods in the near future.
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("MethodOnInternalType")]
|
[InlineData("MethodOnInternalType")]
|
||||||
[InlineData("PrivateMethod")]
|
[InlineData("PrivateMethod")]
|
||||||
[InlineData("ProtectedMethod")]
|
[InlineData("ProtectedMethod")]
|
||||||
[InlineData("MethodWithoutAttribute")] // That's not really its identifier; just making the point that there's no way to invoke it
|
[InlineData("StaticMethodWithoutAttribute")] // That's not really its identifier; just making the point that there's no way to invoke it
|
||||||
|
[InlineData("InstanceMethodWithoutAttribute")] // That's not really its identifier; just making the point that there's no way to invoke it
|
||||||
public void CannotInvokeUnsuitableMethods(string methodIdentifier)
|
public void CannotInvokeUnsuitableMethods(string methodIdentifier)
|
||||||
{
|
{
|
||||||
var ex = Assert.Throws<ArgumentException>(() =>
|
var ex = Assert.Throws<ArgumentException>(() =>
|
||||||
{
|
{
|
||||||
DotNetDispatcher.Invoke(thisAssemblyName, methodIdentifier, null);
|
DotNetDispatcher.Invoke(thisAssemblyName, methodIdentifier, default, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.Equal($"The assembly '{thisAssemblyName}' does not contain a public method with [JSInvokableAttribute(\"{methodIdentifier}\")].", ex.Message);
|
Assert.Equal($"The assembly '{thisAssemblyName}' does not contain a public method with [JSInvokableAttribute(\"{methodIdentifier}\")].", ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanInvokeStaticVoidMethod()
|
public Task CanInvokeStaticVoidMethod() => WithJSRuntime(jsRuntime =>
|
||||||
{
|
{
|
||||||
// Arrange/Act
|
// Arrange/Act
|
||||||
SomePublicType.DidInvokeMyInvocableVoid = false;
|
SomePublicType.DidInvokeMyInvocableStaticVoid = false;
|
||||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticVoid", null);
|
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticVoid", default, null);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Null(resultJson);
|
Assert.Null(resultJson);
|
||||||
Assert.True(SomePublicType.DidInvokeMyInvocableVoid);
|
Assert.True(SomePublicType.DidInvokeMyInvocableStaticVoid);
|
||||||
}
|
});
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanInvokeStaticNonVoidMethod()
|
public Task CanInvokeStaticNonVoidMethod() => WithJSRuntime(jsRuntime =>
|
||||||
{
|
{
|
||||||
// Arrange/Act
|
// Arrange/Act
|
||||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticNonVoid", null);
|
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticNonVoid", default, null);
|
||||||
var result = Json.Deserialize<TestDTO>(resultJson);
|
var result = Json.Deserialize<TestDTO>(resultJson);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("Test", result.StringVal);
|
Assert.Equal("Test", result.StringVal);
|
||||||
Assert.Equal(123, result.IntVal);
|
Assert.Equal(123, result.IntVal);
|
||||||
}
|
});
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanInvokeStaticWithParams()
|
public Task CanInvokeStaticNonVoidMethodWithoutCustomIdentifier() => WithJSRuntime(jsRuntime =>
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange/Act
|
||||||
var argsJson = Json.Serialize(new object[] {
|
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, nameof(SomePublicType.InvokableMethodWithoutCustomIdentifier), default, null);
|
||||||
new TestDTO { StringVal = "Another string", IntVal = 456 },
|
|
||||||
new[] { 100, 200 }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", argsJson);
|
|
||||||
var result = Json.Deserialize<TestDTO>(resultJson);
|
var result = Json.Deserialize<TestDTO>(resultJson);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("ANOTHER STRING", result.StringVal);
|
Assert.Equal("InvokableMethodWithoutCustomIdentifier", result.StringVal);
|
||||||
Assert.Equal(756, result.IntVal);
|
Assert.Equal(456, result.IntVal);
|
||||||
}
|
});
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public Task CanInvokeStaticWithParams() => WithJSRuntime(jsRuntime =>
|
||||||
|
{
|
||||||
|
// Arrange: Track a .NET object to use as an arg
|
||||||
|
var arg3 = new TestDTO { IntVal = 999, StringVal = "My string" };
|
||||||
|
jsRuntime.Invoke<object>("unimportant", new DotNetObjectRef(arg3));
|
||||||
|
|
||||||
|
// Arrange: Remaining args
|
||||||
|
var argsJson = Json.Serialize(new object[] {
|
||||||
|
new TestDTO { StringVal = "Another string", IntVal = 456 },
|
||||||
|
new[] { 100, 200 },
|
||||||
|
"__dotNetObject:1"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", default, argsJson);
|
||||||
|
var result = Json.Deserialize<object[]>(resultJson);
|
||||||
|
|
||||||
|
// Assert: First result value marshalled via JSON
|
||||||
|
var resultDto1 = (TestDTO)jsRuntime.ArgSerializerStrategy.DeserializeObject(result[0], typeof(TestDTO));
|
||||||
|
Assert.Equal("ANOTHER STRING", resultDto1.StringVal);
|
||||||
|
Assert.Equal(756, resultDto1.IntVal);
|
||||||
|
|
||||||
|
// Assert: Second result value marshalled by ref
|
||||||
|
var resultDto2Ref = (string)result[1];
|
||||||
|
Assert.Equal("__dotNetObject:2", resultDto2Ref);
|
||||||
|
var resultDto2 = (TestDTO)jsRuntime.ArgSerializerStrategy.FindDotNetObject(2);
|
||||||
|
Assert.Equal("MY STRING", resultDto2.StringVal);
|
||||||
|
Assert.Equal(1299, resultDto2.IntVal);
|
||||||
|
});
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public Task CanInvokeInstanceVoidMethod() => WithJSRuntime(jsRuntime =>
|
||||||
|
{
|
||||||
|
// Arrange: Track some instance
|
||||||
|
var targetInstance = new SomePublicType();
|
||||||
|
jsRuntime.Invoke<object>("unimportant", new DotNetObjectRef(targetInstance));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var resultJson = DotNetDispatcher.Invoke(null, "InvokableInstanceVoid", 1, null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(resultJson);
|
||||||
|
Assert.True(targetInstance.DidInvokeMyInvocableInstanceVoid);
|
||||||
|
});
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public Task CannotUseDotNetObjectRefAfterDisposal() => WithJSRuntime(jsRuntime =>
|
||||||
|
{
|
||||||
|
// This test addresses the case where the developer calls objectRef.Dispose()
|
||||||
|
// from .NET code, as opposed to .dispose() from JS code
|
||||||
|
|
||||||
|
// Arrange: Track some instance, then dispose it
|
||||||
|
var targetInstance = new SomePublicType();
|
||||||
|
var objectRef = new DotNetObjectRef(targetInstance);
|
||||||
|
jsRuntime.Invoke<object>("unimportant", objectRef);
|
||||||
|
objectRef.Dispose();
|
||||||
|
|
||||||
|
// Act/Assert
|
||||||
|
var ex = Assert.Throws<ArgumentException>(
|
||||||
|
() => DotNetDispatcher.Invoke(null, "InvokableInstanceVoid", 1, null));
|
||||||
|
Assert.StartsWith("There is no tracked object with id '1'.", ex.Message);
|
||||||
|
});
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public Task CannotUseDotNetObjectRefAfterReleaseDotNetObject() => WithJSRuntime(jsRuntime =>
|
||||||
|
{
|
||||||
|
// This test addresses the case where the developer calls .dispose()
|
||||||
|
// from JS code, as opposed to objectRef.Dispose() from .NET code
|
||||||
|
|
||||||
|
// Arrange: Track some instance, then dispose it
|
||||||
|
var targetInstance = new SomePublicType();
|
||||||
|
var objectRef = new DotNetObjectRef(targetInstance);
|
||||||
|
jsRuntime.Invoke<object>("unimportant", objectRef);
|
||||||
|
DotNetDispatcher.ReleaseDotNetObject(1);
|
||||||
|
|
||||||
|
// Act/Assert
|
||||||
|
var ex = Assert.Throws<ArgumentException>(
|
||||||
|
() => DotNetDispatcher.Invoke(null, "InvokableInstanceVoid", 1, null));
|
||||||
|
Assert.StartsWith("There is no tracked object with id '1'.", ex.Message);
|
||||||
|
});
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public Task CanInvokeInstanceMethodWithParams() => WithJSRuntime(jsRuntime =>
|
||||||
|
{
|
||||||
|
// Arrange: Track some instance plus another object we'll pass as a param
|
||||||
|
var targetInstance = new SomePublicType();
|
||||||
|
var arg2 = new TestDTO { IntVal = 1234, StringVal = "My string" };
|
||||||
|
jsRuntime.Invoke<object>("unimportant",
|
||||||
|
new DotNetObjectRef(targetInstance),
|
||||||
|
new DotNetObjectRef(arg2));
|
||||||
|
var argsJson = "[\"myvalue\",\"__dotNetObject:2\"]";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var resultJson = DotNetDispatcher.Invoke(null, "InvokableInstanceMethod", 1, argsJson);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("[\"You passed myvalue\",\"__dotNetObject:3\"]", resultJson);
|
||||||
|
var resultDto = (TestDTO)jsRuntime.ArgSerializerStrategy.FindDotNetObject(3);
|
||||||
|
Assert.Equal(1235, resultDto.IntVal);
|
||||||
|
Assert.Equal("MY STRING", resultDto.StringVal);
|
||||||
|
});
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CannotInvokeWithIncorrectNumberOfParams()
|
public void CannotInvokeWithIncorrectNumberOfParams()
|
||||||
|
|
@ -121,10 +221,73 @@ namespace Microsoft.JSInterop.Test
|
||||||
// Act/Assert
|
// Act/Assert
|
||||||
var ex = Assert.Throws<ArgumentException>(() =>
|
var ex = Assert.Throws<ArgumentException>(() =>
|
||||||
{
|
{
|
||||||
DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", argsJson);
|
DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", default, argsJson);
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.Equal("In call to 'InvocableStaticWithParams', expected 2 parameters but received 4.", ex.Message);
|
Assert.Equal("In call to 'InvocableStaticWithParams', expected 3 parameters but received 4.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public Task CanInvokeAsyncMethod() => WithJSRuntime(async jsRuntime =>
|
||||||
|
{
|
||||||
|
// Arrange: Track some instance plus another object we'll pass as a param
|
||||||
|
var targetInstance = new SomePublicType();
|
||||||
|
var arg2 = new TestDTO { IntVal = 1234, StringVal = "My string" };
|
||||||
|
jsRuntime.Invoke<object>("unimportant", new DotNetObjectRef(targetInstance), new DotNetObjectRef(arg2));
|
||||||
|
|
||||||
|
// Arrange: all args
|
||||||
|
var argsJson = Json.Serialize(new object[]
|
||||||
|
{
|
||||||
|
new TestDTO { IntVal = 1000, StringVal = "String via JSON" },
|
||||||
|
"__dotNetObject:2"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var callId = "123";
|
||||||
|
var resultTask = jsRuntime.NextInvocationTask;
|
||||||
|
DotNetDispatcher.BeginInvoke(callId, null, "InvokableAsyncMethod", 1, argsJson);
|
||||||
|
await resultTask;
|
||||||
|
var result = Json.Deserialize<SimpleJson.JsonArray>(jsRuntime.LastInvocationArgsJson);
|
||||||
|
var resultValue = (SimpleJson.JsonArray)result[2];
|
||||||
|
|
||||||
|
// Assert: Correct info to complete the async call
|
||||||
|
Assert.Equal(0, jsRuntime.LastInvocationAsyncHandle); // 0 because it doesn't want a further callback from JS to .NET
|
||||||
|
Assert.Equal("DotNet.jsCallDispatcher.endInvokeDotNetFromJS", jsRuntime.LastInvocationIdentifier);
|
||||||
|
Assert.Equal(3, result.Count);
|
||||||
|
Assert.Equal(callId, result[0]);
|
||||||
|
Assert.True((bool)result[1]); // Success flag
|
||||||
|
|
||||||
|
// Assert: First result value marshalled via JSON
|
||||||
|
var resultDto1 = (TestDTO)jsRuntime.ArgSerializerStrategy.DeserializeObject(resultValue[0], typeof(TestDTO));
|
||||||
|
Assert.Equal("STRING VIA JSON", resultDto1.StringVal);
|
||||||
|
Assert.Equal(2000, resultDto1.IntVal);
|
||||||
|
|
||||||
|
// Assert: Second result value marshalled by ref
|
||||||
|
var resultDto2Ref = (string)resultValue[1];
|
||||||
|
Assert.Equal("__dotNetObject:3", resultDto2Ref);
|
||||||
|
var resultDto2 = (TestDTO)jsRuntime.ArgSerializerStrategy.FindDotNetObject(3);
|
||||||
|
Assert.Equal("MY STRING", resultDto2.StringVal);
|
||||||
|
Assert.Equal(2468, resultDto2.IntVal);
|
||||||
|
});
|
||||||
|
|
||||||
|
Task WithJSRuntime(Action<TestJSRuntime> testCode)
|
||||||
|
{
|
||||||
|
return WithJSRuntime(jsRuntime =>
|
||||||
|
{
|
||||||
|
testCode(jsRuntime);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task WithJSRuntime(Func<TestJSRuntime, Task> testCode)
|
||||||
|
{
|
||||||
|
// Since the tests rely on the asynclocal JSRuntime.Current, ensure we
|
||||||
|
// are on a distinct async context with a non-null JSRuntime.Current
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
var runtime = new TestJSRuntime();
|
||||||
|
JSRuntime.SetCurrentJSRuntime(runtime);
|
||||||
|
await testCode(runtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class SomeInteralType
|
internal class SomeInteralType
|
||||||
|
|
@ -134,15 +297,17 @@ namespace Microsoft.JSInterop.Test
|
||||||
|
|
||||||
public class SomePublicType
|
public class SomePublicType
|
||||||
{
|
{
|
||||||
public static bool DidInvokeMyInvocableVoid;
|
public static bool DidInvokeMyInvocableStaticVoid;
|
||||||
|
public bool DidInvokeMyInvocableInstanceVoid;
|
||||||
|
|
||||||
[JSInvokable("PrivateMethod")] private void MyPrivateMethod() { }
|
[JSInvokable("PrivateMethod")] private static void MyPrivateMethod() { }
|
||||||
[JSInvokable("ProtectedMethod")] protected void MyProtectedMethod() { }
|
[JSInvokable("ProtectedMethod")] protected static void MyProtectedMethod() { }
|
||||||
protected void MethodWithoutAttribute() { }
|
protected static void StaticMethodWithoutAttribute() { }
|
||||||
|
protected static void InstanceMethodWithoutAttribute() { }
|
||||||
|
|
||||||
[JSInvokable("InvocableStaticVoid")] public static void MyInvocableVoid()
|
[JSInvokable("InvocableStaticVoid")] public static void MyInvocableVoid()
|
||||||
{
|
{
|
||||||
DidInvokeMyInvocableVoid = true;
|
DidInvokeMyInvocableStaticVoid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable("InvocableStaticNonVoid")]
|
[JSInvokable("InvocableStaticNonVoid")]
|
||||||
|
|
@ -150,8 +315,65 @@ namespace Microsoft.JSInterop.Test
|
||||||
=> new TestDTO { StringVal = "Test", IntVal = 123 };
|
=> new TestDTO { StringVal = "Test", IntVal = 123 };
|
||||||
|
|
||||||
[JSInvokable("InvocableStaticWithParams")]
|
[JSInvokable("InvocableStaticWithParams")]
|
||||||
public static TestDTO MyInvocableWithParams(TestDTO dto, int[] incrementAmounts)
|
public static object[] MyInvocableWithParams(TestDTO dtoViaJson, int[] incrementAmounts, TestDTO dtoByRef)
|
||||||
=> new TestDTO { StringVal = dto.StringVal.ToUpperInvariant(), IntVal = dto.IntVal + incrementAmounts.Sum() };
|
=> new object[]
|
||||||
|
{
|
||||||
|
new TestDTO // Return via JSON marshalling
|
||||||
|
{
|
||||||
|
StringVal = dtoViaJson.StringVal.ToUpperInvariant(),
|
||||||
|
IntVal = dtoViaJson.IntVal + incrementAmounts.Sum()
|
||||||
|
},
|
||||||
|
new DotNetObjectRef(new TestDTO // Return by ref
|
||||||
|
{
|
||||||
|
StringVal = dtoByRef.StringVal.ToUpperInvariant(),
|
||||||
|
IntVal = dtoByRef.IntVal + incrementAmounts.Sum()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public static TestDTO InvokableMethodWithoutCustomIdentifier()
|
||||||
|
=> new TestDTO { StringVal = "InvokableMethodWithoutCustomIdentifier", IntVal = 456 };
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public void InvokableInstanceVoid()
|
||||||
|
{
|
||||||
|
DidInvokeMyInvocableInstanceVoid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public object[] InvokableInstanceMethod(string someString, TestDTO someDTO)
|
||||||
|
{
|
||||||
|
// Returning an array to make the point that object references
|
||||||
|
// can be embedded anywhere in the result
|
||||||
|
return new object[]
|
||||||
|
{
|
||||||
|
$"You passed {someString}",
|
||||||
|
new DotNetObjectRef(new TestDTO
|
||||||
|
{
|
||||||
|
IntVal = someDTO.IntVal + 1,
|
||||||
|
StringVal = someDTO.StringVal.ToUpperInvariant()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public async Task<object[]> InvokableAsyncMethod(TestDTO dtoViaJson, TestDTO dtoByRef)
|
||||||
|
{
|
||||||
|
await Task.Delay(50);
|
||||||
|
return new object[]
|
||||||
|
{
|
||||||
|
new TestDTO // Return via JSON
|
||||||
|
{
|
||||||
|
StringVal = dtoViaJson.StringVal.ToUpperInvariant(),
|
||||||
|
IntVal = dtoViaJson.IntVal * 2,
|
||||||
|
},
|
||||||
|
new DotNetObjectRef(new TestDTO // Return by ref
|
||||||
|
{
|
||||||
|
StringVal = dtoByRef.StringVal.ToUpperInvariant(),
|
||||||
|
IntVal = dtoByRef.IntVal * 2,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestDTO
|
public class TestDTO
|
||||||
|
|
@ -159,5 +381,33 @@ namespace Microsoft.JSInterop.Test
|
||||||
public string StringVal { get; set; }
|
public string StringVal { get; set; }
|
||||||
public int IntVal { get; set; }
|
public int IntVal { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class TestJSRuntime : JSInProcessRuntimeBase
|
||||||
|
{
|
||||||
|
private TaskCompletionSource<object> _nextInvocationTcs = new TaskCompletionSource<object>();
|
||||||
|
public Task NextInvocationTask => _nextInvocationTcs.Task;
|
||||||
|
public long LastInvocationAsyncHandle { get; private set; }
|
||||||
|
public string LastInvocationIdentifier { get; private set; }
|
||||||
|
public string LastInvocationArgsJson { get; private set; }
|
||||||
|
|
||||||
|
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
||||||
|
{
|
||||||
|
LastInvocationAsyncHandle = asyncHandle;
|
||||||
|
LastInvocationIdentifier = identifier;
|
||||||
|
LastInvocationArgsJson = argsJson;
|
||||||
|
_nextInvocationTcs.SetResult(null);
|
||||||
|
_nextInvocationTcs = new TaskCompletionSource<object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string InvokeJS(string identifier, string argsJson)
|
||||||
|
{
|
||||||
|
LastInvocationAsyncHandle = default;
|
||||||
|
LastInvocationIdentifier = identifier;
|
||||||
|
LastInvocationArgsJson = argsJson;
|
||||||
|
_nextInvocationTcs.SetResult(null);
|
||||||
|
_nextInvocationTcs = new TaskCompletionSource<object>();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.JSInterop.Test
|
||||||
|
{
|
||||||
|
public class DotNetObjectRefTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void CanAccessValue()
|
||||||
|
{
|
||||||
|
var obj = new object();
|
||||||
|
Assert.Same(obj, new DotNetObjectRef(obj).Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanAssociateWithSameRuntimeMultipleTimes()
|
||||||
|
{
|
||||||
|
var objRef = new DotNetObjectRef(new object());
|
||||||
|
var jsRuntime = new TestJsRuntime();
|
||||||
|
objRef.EnsureAttachedToJsRuntime(jsRuntime);
|
||||||
|
objRef.EnsureAttachedToJsRuntime(jsRuntime);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CannotAssociateWithDifferentRuntimes()
|
||||||
|
{
|
||||||
|
var objRef = new DotNetObjectRef(new object());
|
||||||
|
var jsRuntime1 = new TestJsRuntime();
|
||||||
|
var jsRuntime2 = new TestJsRuntime();
|
||||||
|
objRef.EnsureAttachedToJsRuntime(jsRuntime1);
|
||||||
|
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(
|
||||||
|
() => objRef.EnsureAttachedToJsRuntime(jsRuntime2));
|
||||||
|
Assert.Contains("Do not attempt to re-use", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NotifiesAssociatedJsRuntimeOfDisposal()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var objRef = new DotNetObjectRef(new object());
|
||||||
|
var jsRuntime = new TestJsRuntime();
|
||||||
|
objRef.EnsureAttachedToJsRuntime(jsRuntime);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
objRef.Dispose();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(new[] { objRef }, jsRuntime.UntrackedRefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestJsRuntime : IJSRuntime
|
||||||
|
{
|
||||||
|
public List<DotNetObjectRef> UntrackedRefs = new List<DotNetObjectRef>();
|
||||||
|
|
||||||
|
public Task<T> InvokeAsync<T>(string identifier, params object[] args)
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
|
||||||
|
public void UntrackObjectRef(DotNetObjectRef dotNetObjectRef)
|
||||||
|
=> UntrackedRefs.Add(dotNetObjectRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.JSInterop.Test
|
||||||
|
{
|
||||||
|
public class JSInProcessRuntimeBaseTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void DispatchesSyncCallsAndDeserializesResults()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var runtime = new TestJSInProcessRuntime
|
||||||
|
{
|
||||||
|
NextResultJson = Json.Serialize(
|
||||||
|
new TestDTO { IntValue = 123, StringValue = "Hello" })
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var syncResult = runtime.Invoke<TestDTO>("test identifier 1", "arg1", 123, true );
|
||||||
|
var call = runtime.InvokeCalls.Single();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(123, syncResult.IntValue);
|
||||||
|
Assert.Equal("Hello", syncResult.StringValue);
|
||||||
|
Assert.Equal("test identifier 1", call.Identifier);
|
||||||
|
Assert.Equal("[\"arg1\",123,true]", call.ArgsJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SerializesDotNetObjectWrappersInKnownFormat()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var runtime = new TestJSInProcessRuntime { NextResultJson = null };
|
||||||
|
var obj1 = new object();
|
||||||
|
var obj2 = new object();
|
||||||
|
var obj3 = new object();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
// Showing we can pass the DotNetObject either as top-level args or nested
|
||||||
|
var syncResult = runtime.Invoke<object>("test identifier",
|
||||||
|
new DotNetObjectRef(obj1),
|
||||||
|
new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "obj2", new DotNetObjectRef(obj2) },
|
||||||
|
{ "obj3", new DotNetObjectRef(obj3) }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert: Handles null result string
|
||||||
|
Assert.Null(syncResult);
|
||||||
|
|
||||||
|
// Assert: Serialized as expected
|
||||||
|
var call = runtime.InvokeCalls.Single();
|
||||||
|
Assert.Equal("test identifier", call.Identifier);
|
||||||
|
Assert.Equal("[\"__dotNetObject:1\",{\"obj2\":\"__dotNetObject:2\",\"obj3\":\"__dotNetObject:3\"}]", call.ArgsJson);
|
||||||
|
|
||||||
|
// Assert: Objects were tracked
|
||||||
|
Assert.Same(obj1, runtime.ArgSerializerStrategy.FindDotNetObject(1));
|
||||||
|
Assert.Same(obj2, runtime.ArgSerializerStrategy.FindDotNetObject(2));
|
||||||
|
Assert.Same(obj3, runtime.ArgSerializerStrategy.FindDotNetObject(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SyncCallResultCanIncludeDotNetObjects()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var runtime = new TestJSInProcessRuntime
|
||||||
|
{
|
||||||
|
NextResultJson = "[\"__dotNetObject:2\",\"__dotNetObject:1\"]"
|
||||||
|
};
|
||||||
|
var obj1 = new object();
|
||||||
|
var obj2 = new object();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var syncResult = runtime.Invoke<object[]>("test identifier",
|
||||||
|
new DotNetObjectRef(obj1),
|
||||||
|
"some other arg",
|
||||||
|
new DotNetObjectRef(obj2));
|
||||||
|
var call = runtime.InvokeCalls.Single();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(new[] { obj2, obj1 }, syncResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestDTO
|
||||||
|
{
|
||||||
|
public int IntValue { get; set; }
|
||||||
|
public string StringValue { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestJSInProcessRuntime : JSInProcessRuntimeBase
|
||||||
|
{
|
||||||
|
public List<InvokeArgs> InvokeCalls { get; set; } = new List<InvokeArgs>();
|
||||||
|
|
||||||
|
public string NextResultJson { get; set; }
|
||||||
|
|
||||||
|
protected override string InvokeJS(string identifier, string argsJson)
|
||||||
|
{
|
||||||
|
InvokeCalls.Add(new InvokeArgs { Identifier = identifier, ArgsJson = argsJson });
|
||||||
|
return NextResultJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InvokeArgs
|
||||||
|
{
|
||||||
|
public string Identifier { get; set; }
|
||||||
|
public string ArgsJson { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
||||||
|
=> throw new NotImplementedException("This test only covers sync calls");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.JSInterop.Internal;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop.Test
|
namespace Microsoft.JSInterop.Test
|
||||||
|
|
@ -98,7 +100,57 @@ namespace Microsoft.JSInterop.Test
|
||||||
});
|
});
|
||||||
Assert.Equal($"There is no pending task with handle '{asyncHandle}'.", ex.Message);
|
Assert.Equal($"There is no pending task with handle '{asyncHandle}'.", ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SerializesDotNetObjectWrappersInKnownFormat()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var runtime = new TestJSRuntime();
|
||||||
|
var obj1 = new object();
|
||||||
|
var obj2 = new object();
|
||||||
|
var obj3 = new object();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
// Showing we can pass the DotNetObject either as top-level args or nested
|
||||||
|
var obj1Ref = new DotNetObjectRef(obj1);
|
||||||
|
var obj1DifferentRef = new DotNetObjectRef(obj1);
|
||||||
|
runtime.InvokeAsync<object>("test identifier",
|
||||||
|
obj1Ref,
|
||||||
|
new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "obj2", new DotNetObjectRef(obj2) },
|
||||||
|
{ "obj3", new DotNetObjectRef(obj3) },
|
||||||
|
{ "obj1SameRef", obj1Ref },
|
||||||
|
{ "obj1DifferentRef", obj1DifferentRef },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert: Serialized as expected
|
||||||
|
var call = runtime.BeginInvokeCalls.Single();
|
||||||
|
Assert.Equal("test identifier", call.Identifier);
|
||||||
|
Assert.Equal("[\"__dotNetObject:1\",{\"obj2\":\"__dotNetObject:2\",\"obj3\":\"__dotNetObject:3\",\"obj1SameRef\":\"__dotNetObject:1\",\"obj1DifferentRef\":\"__dotNetObject:4\"}]", call.ArgsJson);
|
||||||
|
|
||||||
|
// Assert: Objects were tracked
|
||||||
|
Assert.Same(obj1, runtime.ArgSerializerStrategy.FindDotNetObject(1));
|
||||||
|
Assert.Same(obj2, runtime.ArgSerializerStrategy.FindDotNetObject(2));
|
||||||
|
Assert.Same(obj3, runtime.ArgSerializerStrategy.FindDotNetObject(3));
|
||||||
|
Assert.Same(obj1, runtime.ArgSerializerStrategy.FindDotNetObject(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SupportsCustomSerializationForArguments()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var runtime = new TestJSRuntime();
|
||||||
|
|
||||||
|
// Arrange/Act
|
||||||
|
runtime.InvokeAsync<object>("test identifier",
|
||||||
|
new WithCustomArgSerializer());
|
||||||
|
|
||||||
|
// Asssert
|
||||||
|
var call = runtime.BeginInvokeCalls.Single();
|
||||||
|
Assert.Equal("[{\"key1\":\"value1\",\"key2\":123}]", call.ArgsJson);
|
||||||
|
}
|
||||||
|
|
||||||
class TestJSRuntime : JSRuntimeBase
|
class TestJSRuntime : JSRuntimeBase
|
||||||
{
|
{
|
||||||
public List<BeginInvokeAsyncArgs> BeginInvokeCalls = new List<BeginInvokeAsyncArgs>();
|
public List<BeginInvokeAsyncArgs> BeginInvokeCalls = new List<BeginInvokeAsyncArgs>();
|
||||||
|
|
@ -123,5 +175,17 @@ namespace Microsoft.JSInterop.Test
|
||||||
public void OnEndInvoke(long asyncHandle, bool succeeded, object resultOrException)
|
public void OnEndInvoke(long asyncHandle, bool succeeded, object resultOrException)
|
||||||
=> EndInvokeJS(asyncHandle, succeeded, resultOrException);
|
=> EndInvokeJS(asyncHandle, succeeded, resultOrException);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class WithCustomArgSerializer : ICustomArgSerializer
|
||||||
|
{
|
||||||
|
public object ToJsonPrimitive()
|
||||||
|
{
|
||||||
|
return new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "key1", "value1" },
|
||||||
|
{ "key2", 123 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,9 @@ namespace Microsoft.JSInterop.Test
|
||||||
{
|
{
|
||||||
public Task<T> InvokeAsync<T>(string identifier, params object[] args)
|
public Task<T> InvokeAsync<T>(string identifier, params object[] args)
|
||||||
=> throw new NotImplementedException();
|
=> throw new NotImplementedException();
|
||||||
|
|
||||||
|
public void UntrackObjectRef(DotNetObjectRef dotNetObjectRef)
|
||||||
|
=> throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -191,16 +191,6 @@ namespace Microsoft.JSInterop.Test
|
||||||
exception.Message);
|
exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void SupportsInternalCustomSerializer()
|
|
||||||
{
|
|
||||||
// Arrange/Act
|
|
||||||
var json = Json.Serialize(new WithCustomSerializer());
|
|
||||||
|
|
||||||
// Asssert
|
|
||||||
Assert.Equal("{\"key1\":\"value1\",\"key2\":123}", json);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test cases based on https://github.com/JamesNK/Newtonsoft.Json/blob/122afba9908832bd5ac207164ee6c303bfd65cf1/Src/Newtonsoft.Json.Tests/Utilities/StringUtilsTests.cs#L41
|
// Test cases based on https://github.com/JamesNK/Newtonsoft.Json/blob/122afba9908832bd5ac207164ee6c303bfd65cf1/Src/Newtonsoft.Json.Tests/Utilities/StringUtilsTests.cs#L41
|
||||||
// The only difference is that our logic doesn't have to handle space-separated words,
|
// The only difference is that our logic doesn't have to handle space-separated words,
|
||||||
// because we're only use this for camelcasing .NET member names
|
// because we're only use this for camelcasing .NET member names
|
||||||
|
|
@ -273,18 +263,6 @@ namespace Microsoft.JSInterop.Test
|
||||||
|
|
||||||
enum Hobbies { Reading = 1, Swordfighting = 2 }
|
enum Hobbies { Reading = 1, Swordfighting = 2 }
|
||||||
|
|
||||||
class WithCustomSerializer : ICustomJsonSerializer
|
|
||||||
{
|
|
||||||
public object ToJsonPrimitive()
|
|
||||||
{
|
|
||||||
return new Dictionary<string, object>
|
|
||||||
{
|
|
||||||
{ "key1", "value1" },
|
|
||||||
{ "key2", 123 },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma warning disable 0649
|
#pragma warning disable 0649
|
||||||
class ClashingProperties
|
class ClashingProperties
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,20 @@
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h1>.NET to JS calls: passing .NET object by ref, receiving .NET object by ref</h1>
|
||||||
|
@foreach (var kvp in ReceiveDotNetObjectByRefResult)
|
||||||
|
{
|
||||||
|
<h2>@(kvp.Key)Sync</h2>
|
||||||
|
<p id="@(kvp.Key)Sync">@kvp.Value</p>
|
||||||
|
}
|
||||||
|
@foreach (var kvp in ReceiveDotNetObjectByRefAsyncResult)
|
||||||
|
{
|
||||||
|
<h2>@(kvp.Key)Async</h2>
|
||||||
|
<p id="@(kvp.Key)Async">@kvp.Value</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h1>Return values and exceptions thrown from .NET</h1>
|
<h1>Return values and exceptions thrown from .NET</h1>
|
||||||
@foreach (var returnValue in ReturnValues)
|
@foreach (var returnValue in ReturnValues)
|
||||||
|
|
@ -44,14 +58,23 @@
|
||||||
public JSException SyncExceptionFromAsyncMethod { get; set; }
|
public JSException SyncExceptionFromAsyncMethod { get; set; }
|
||||||
public JSException AsyncExceptionFromAsyncMethod { get; set; }
|
public JSException AsyncExceptionFromAsyncMethod { get; set; }
|
||||||
|
|
||||||
|
public IDictionary<string, object> ReceiveDotNetObjectByRefResult { get; set; } = new Dictionary<string, object>();
|
||||||
|
public IDictionary<string, object> ReceiveDotNetObjectByRefAsyncResult { get; set; } = new Dictionary<string, object>();
|
||||||
|
|
||||||
public bool DoneWithInterop { get; set; }
|
public bool DoneWithInterop { get; set; }
|
||||||
|
|
||||||
public async Task InvokeInteropAsync()
|
public async Task InvokeInteropAsync()
|
||||||
{
|
{
|
||||||
var inProcRuntime = ((IJSInProcessRuntime)JSRuntime.Current);
|
var inProcRuntime = ((IJSInProcessRuntime)JSRuntime.Current);
|
||||||
|
var testDTOTOPassByRef = new TestDTO(nonSerializedValue: 123);
|
||||||
|
|
||||||
|
var instanceMethodsTarget = new JavaScriptInterop();
|
||||||
|
|
||||||
Console.WriteLine("Starting interop invocations.");
|
Console.WriteLine("Starting interop invocations.");
|
||||||
await JSRuntime.Current.InvokeAsync<object>("jsInteropTests.invokeDotNetInteropMethodsAsync");
|
await JSRuntime.Current.InvokeAsync<object>(
|
||||||
|
"jsInteropTests.invokeDotNetInteropMethodsAsync",
|
||||||
|
new DotNetObjectRef(testDTOTOPassByRef),
|
||||||
|
new DotNetObjectRef(instanceMethodsTarget));
|
||||||
Console.WriteLine("Showing interop invocation results.");
|
Console.WriteLine("Showing interop invocation results.");
|
||||||
var collectResults = inProcRuntime.Invoke<Dictionary<string,string>>("jsInteropTests.collectInteropResults");
|
var collectResults = inProcRuntime.Invoke<Dictionary<string,string>>("jsInteropTests.collectInteropResults");
|
||||||
|
|
||||||
|
|
@ -91,6 +114,20 @@
|
||||||
AsyncExceptionFromAsyncMethod = e;
|
AsyncExceptionFromAsyncMethod = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var passDotNetObjectByRef = new TestDTO(99999);
|
||||||
|
var passDotNetObjectByRefArg = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "stringValue", "My string" },
|
||||||
|
{ "testDto", new DotNetObjectRef(passDotNetObjectByRef) },
|
||||||
|
};
|
||||||
|
ReceiveDotNetObjectByRefResult = inProcRuntime.Invoke<Dictionary<string, object>>("receiveDotNetObjectByRef", passDotNetObjectByRefArg);
|
||||||
|
ReceiveDotNetObjectByRefAsyncResult = await JSRuntime.Current.InvokeAsync<Dictionary<string, object>>("receiveDotNetObjectByRefAsync", passDotNetObjectByRefArg);
|
||||||
|
ReceiveDotNetObjectByRefResult["testDto"] = ReceiveDotNetObjectByRefResult["testDto"] == passDotNetObjectByRef ? "Same" : "Different";
|
||||||
|
ReceiveDotNetObjectByRefAsyncResult["testDto"] = ReceiveDotNetObjectByRefAsyncResult["testDto"] == passDotNetObjectByRef ? "Same" : "Different";
|
||||||
|
|
||||||
|
ReturnValues["returnPrimitive"] = inProcRuntime.Invoke<int>("returnPrimitive").ToString();
|
||||||
|
ReturnValues["returnPrimitiveAsync"] = (await JSRuntime.Current.InvokeAsync<int>("returnPrimitiveAsync")).ToString();
|
||||||
|
|
||||||
Invocations = invocations;
|
Invocations = invocations;
|
||||||
DoneWithInterop = true;
|
DoneWithInterop = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace BasicTestApp.InteropTest
|
namespace BasicTestApp.InteropTest
|
||||||
|
|
@ -13,33 +12,33 @@ namespace BasicTestApp.InteropTest
|
||||||
{
|
{
|
||||||
public static IDictionary<string, object[]> Invocations = new Dictionary<string, object[]>();
|
public static IDictionary<string, object[]> Invocations = new Dictionary<string, object[]>();
|
||||||
|
|
||||||
[JSInvokable(nameof(ThrowException))]
|
[JSInvokable]
|
||||||
public static void ThrowException() => throw new InvalidOperationException("Threw an exception!");
|
public static void ThrowException() => throw new InvalidOperationException("Threw an exception!");
|
||||||
|
|
||||||
[JSInvokable(nameof(AsyncThrowSyncException))]
|
[JSInvokable]
|
||||||
public static Task AsyncThrowSyncException()
|
public static Task AsyncThrowSyncException()
|
||||||
=> throw new InvalidOperationException("Threw a sync exception!");
|
=> throw new InvalidOperationException("Threw a sync exception!");
|
||||||
|
|
||||||
[JSInvokable(nameof(AsyncThrowAsyncException))]
|
[JSInvokable]
|
||||||
public static async Task AsyncThrowAsyncException()
|
public static async Task AsyncThrowAsyncException()
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
throw new InvalidOperationException("Threw an async exception!");
|
throw new InvalidOperationException("Threw an async exception!");
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(VoidParameterless))]
|
[JSInvokable]
|
||||||
public static void VoidParameterless()
|
public static void VoidParameterless()
|
||||||
{
|
{
|
||||||
Invocations[nameof(VoidParameterless)] = new object[0];
|
Invocations[nameof(VoidParameterless)] = new object[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(VoidWithOneParameter))]
|
[JSInvokable]
|
||||||
public static void VoidWithOneParameter(ComplexParameter parameter1)
|
public static void VoidWithOneParameter(ComplexParameter parameter1)
|
||||||
{
|
{
|
||||||
Invocations[nameof(VoidWithOneParameter)] = new object[] { parameter1 };
|
Invocations[nameof(VoidWithOneParameter)] = new object[] { parameter1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(VoidWithTwoParameters))]
|
[JSInvokable]
|
||||||
public static void VoidWithTwoParameters(
|
public static void VoidWithTwoParameters(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2)
|
byte parameter2)
|
||||||
|
|
@ -47,88 +46,88 @@ namespace BasicTestApp.InteropTest
|
||||||
Invocations[nameof(VoidWithTwoParameters)] = new object[] { parameter1, parameter2 };
|
Invocations[nameof(VoidWithTwoParameters)] = new object[] { parameter1, parameter2 };
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(VoidWithThreeParameters))]
|
[JSInvokable]
|
||||||
public static void VoidWithThreeParameters(
|
public static void VoidWithThreeParameters(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3)
|
TestDTO parameter3)
|
||||||
{
|
{
|
||||||
Invocations[nameof(VoidWithThreeParameters)] = new object[] { parameter1, parameter2, parameter3 };
|
Invocations[nameof(VoidWithThreeParameters)] = new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue() };
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(VoidWithFourParameters))]
|
[JSInvokable]
|
||||||
public static void VoidWithFourParameters(
|
public static void VoidWithFourParameters(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4)
|
int parameter4)
|
||||||
{
|
{
|
||||||
Invocations[nameof(VoidWithFourParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4 };
|
Invocations[nameof(VoidWithFourParameters)] = new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4 };
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(VoidWithFiveParameters))]
|
[JSInvokable]
|
||||||
public static void VoidWithFiveParameters(
|
public static void VoidWithFiveParameters(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4,
|
int parameter4,
|
||||||
long parameter5)
|
long parameter5)
|
||||||
{
|
{
|
||||||
Invocations[nameof(VoidWithFiveParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5 };
|
Invocations[nameof(VoidWithFiveParameters)] = new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4, parameter5 };
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(VoidWithSixParameters))]
|
[JSInvokable]
|
||||||
public static void VoidWithSixParameters(
|
public static void VoidWithSixParameters(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4,
|
int parameter4,
|
||||||
long parameter5,
|
long parameter5,
|
||||||
float parameter6)
|
float parameter6)
|
||||||
{
|
{
|
||||||
Invocations[nameof(VoidWithSixParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6 };
|
Invocations[nameof(VoidWithSixParameters)] = new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4, parameter5, parameter6 };
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(VoidWithSevenParameters))]
|
[JSInvokable]
|
||||||
public static void VoidWithSevenParameters(
|
public static void VoidWithSevenParameters(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4,
|
int parameter4,
|
||||||
long parameter5,
|
long parameter5,
|
||||||
float parameter6,
|
float parameter6,
|
||||||
List<double> parameter7)
|
List<double> parameter7)
|
||||||
{
|
{
|
||||||
Invocations[nameof(VoidWithSevenParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7 };
|
Invocations[nameof(VoidWithSevenParameters)] = new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4, parameter5, parameter6, parameter7 };
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(VoidWithEightParameters))]
|
[JSInvokable]
|
||||||
public static void VoidWithEightParameters(
|
public static void VoidWithEightParameters(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4,
|
int parameter4,
|
||||||
long parameter5,
|
long parameter5,
|
||||||
float parameter6,
|
float parameter6,
|
||||||
List<double> parameter7,
|
List<double> parameter7,
|
||||||
Segment parameter8)
|
Segment parameter8)
|
||||||
{
|
{
|
||||||
Invocations[nameof(VoidWithEightParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7, parameter8 };
|
Invocations[nameof(VoidWithEightParameters)] = new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4, parameter5, parameter6, parameter7, parameter8 };
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(ReturnArray))]
|
[JSInvokable]
|
||||||
public static decimal[] ReturnArray()
|
public static decimal[] ReturnArray()
|
||||||
{
|
{
|
||||||
return new decimal[] { 0.1M, 0.2M };
|
return new decimal[] { 0.1M, 0.2M };
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(EchoOneParameter))]
|
[JSInvokable]
|
||||||
public static object[] EchoOneParameter(ComplexParameter parameter1)
|
public static object[] EchoOneParameter(ComplexParameter parameter1)
|
||||||
{
|
{
|
||||||
return new object[] { parameter1 };
|
return new object[] { parameter1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(EchoTwoParameters))]
|
[JSInvokable]
|
||||||
public static object[] EchoTwoParameters(
|
public static object[] EchoTwoParameters(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2)
|
byte parameter2)
|
||||||
|
|
@ -136,88 +135,88 @@ namespace BasicTestApp.InteropTest
|
||||||
return new object[] { parameter1, parameter2 };
|
return new object[] { parameter1, parameter2 };
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(EchoThreeParameters))]
|
[JSInvokable]
|
||||||
public static object[] EchoThreeParameters(
|
public static object[] EchoThreeParameters(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3)
|
TestDTO parameter3)
|
||||||
{
|
{
|
||||||
return new object[] { parameter1, parameter2, parameter3 };
|
return new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue() };
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(EchoFourParameters))]
|
[JSInvokable]
|
||||||
public static object[] EchoFourParameters(
|
public static object[] EchoFourParameters(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4)
|
int parameter4)
|
||||||
{
|
{
|
||||||
return new object[] { parameter1, parameter2, parameter3, parameter4 };
|
return new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4 };
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(EchoFiveParameters))]
|
[JSInvokable]
|
||||||
public static object[] EchoFiveParameters(
|
public static object[] EchoFiveParameters(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4,
|
int parameter4,
|
||||||
long parameter5)
|
long parameter5)
|
||||||
{
|
{
|
||||||
return new object[] { parameter1, parameter2, parameter3, parameter4, parameter5 };
|
return new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4, parameter5 };
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(EchoSixParameters))]
|
[JSInvokable]
|
||||||
public static object[] EchoSixParameters(ComplexParameter parameter1,
|
public static object[] EchoSixParameters(ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4,
|
int parameter4,
|
||||||
long parameter5,
|
long parameter5,
|
||||||
float parameter6)
|
float parameter6)
|
||||||
{
|
{
|
||||||
return new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6 };
|
return new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4, parameter5, parameter6 };
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(EchoSevenParameters))]
|
[JSInvokable]
|
||||||
public static object[] EchoSevenParameters(ComplexParameter parameter1,
|
public static object[] EchoSevenParameters(ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4,
|
int parameter4,
|
||||||
long parameter5,
|
long parameter5,
|
||||||
float parameter6,
|
float parameter6,
|
||||||
List<double> parameter7)
|
List<double> parameter7)
|
||||||
{
|
{
|
||||||
return new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7 };
|
return new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4, parameter5, parameter6, parameter7 };
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(EchoEightParameters))]
|
[JSInvokable]
|
||||||
public static object[] EchoEightParameters(
|
public static object[] EchoEightParameters(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4,
|
int parameter4,
|
||||||
long parameter5,
|
long parameter5,
|
||||||
float parameter6,
|
float parameter6,
|
||||||
List<double> parameter7,
|
List<double> parameter7,
|
||||||
Segment parameter8)
|
Segment parameter8)
|
||||||
{
|
{
|
||||||
return new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7, parameter8 };
|
return new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4, parameter5, parameter6, parameter7, parameter8 };
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(VoidParameterlessAsync))]
|
[JSInvokable]
|
||||||
public static Task VoidParameterlessAsync()
|
public static Task VoidParameterlessAsync()
|
||||||
{
|
{
|
||||||
Invocations[nameof(VoidParameterlessAsync)] = new object[0];
|
Invocations[nameof(VoidParameterlessAsync)] = new object[0];
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(VoidWithOneParameterAsync))]
|
[JSInvokable]
|
||||||
public static Task VoidWithOneParameterAsync(ComplexParameter parameter1)
|
public static Task VoidWithOneParameterAsync(ComplexParameter parameter1)
|
||||||
{
|
{
|
||||||
Invocations[nameof(VoidWithOneParameterAsync)] = new object[] { parameter1 };
|
Invocations[nameof(VoidWithOneParameterAsync)] = new object[] { parameter1 };
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(VoidWithTwoParametersAsync))]
|
[JSInvokable]
|
||||||
public static Task VoidWithTwoParametersAsync(
|
public static Task VoidWithTwoParametersAsync(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2)
|
byte parameter2)
|
||||||
|
|
@ -226,94 +225,94 @@ namespace BasicTestApp.InteropTest
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(VoidWithThreeParametersAsync))]
|
[JSInvokable]
|
||||||
public static Task VoidWithThreeParametersAsync(
|
public static Task VoidWithThreeParametersAsync(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3)
|
TestDTO parameter3)
|
||||||
{
|
{
|
||||||
Invocations[nameof(VoidWithThreeParametersAsync)] = new object[] { parameter1, parameter2, parameter3 };
|
Invocations[nameof(VoidWithThreeParametersAsync)] = new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue() };
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(VoidWithFourParametersAsync))]
|
[JSInvokable]
|
||||||
public static Task VoidWithFourParametersAsync(
|
public static Task VoidWithFourParametersAsync(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4)
|
int parameter4)
|
||||||
{
|
{
|
||||||
Invocations[nameof(VoidWithFourParametersAsync)] = new object[] { parameter1, parameter2, parameter3, parameter4 };
|
Invocations[nameof(VoidWithFourParametersAsync)] = new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4 };
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(VoidWithFiveParametersAsync))]
|
[JSInvokable]
|
||||||
public static Task VoidWithFiveParametersAsync(
|
public static Task VoidWithFiveParametersAsync(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4,
|
int parameter4,
|
||||||
long parameter5)
|
long parameter5)
|
||||||
{
|
{
|
||||||
Invocations[nameof(VoidWithFiveParametersAsync)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5 };
|
Invocations[nameof(VoidWithFiveParametersAsync)] = new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4, parameter5 };
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(VoidWithSixParametersAsync))]
|
[JSInvokable]
|
||||||
public static Task VoidWithSixParametersAsync(
|
public static Task VoidWithSixParametersAsync(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4,
|
int parameter4,
|
||||||
long parameter5,
|
long parameter5,
|
||||||
float parameter6)
|
float parameter6)
|
||||||
{
|
{
|
||||||
Invocations[nameof(VoidWithSixParametersAsync)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6 };
|
Invocations[nameof(VoidWithSixParametersAsync)] = new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4, parameter5, parameter6 };
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(VoidWithSevenParametersAsync))]
|
[JSInvokable]
|
||||||
public static Task VoidWithSevenParametersAsync(
|
public static Task VoidWithSevenParametersAsync(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4,
|
int parameter4,
|
||||||
long parameter5,
|
long parameter5,
|
||||||
float parameter6,
|
float parameter6,
|
||||||
List<double> parameter7)
|
List<double> parameter7)
|
||||||
{
|
{
|
||||||
Invocations[nameof(VoidWithSevenParametersAsync)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7 };
|
Invocations[nameof(VoidWithSevenParametersAsync)] = new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4, parameter5, parameter6, parameter7 };
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(VoidWithEightParametersAsync))]
|
[JSInvokable]
|
||||||
public static Task VoidWithEightParametersAsync(
|
public static Task VoidWithEightParametersAsync(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4,
|
int parameter4,
|
||||||
long parameter5,
|
long parameter5,
|
||||||
float parameter6,
|
float parameter6,
|
||||||
List<double> parameter7,
|
List<double> parameter7,
|
||||||
Segment parameter8)
|
Segment parameter8)
|
||||||
{
|
{
|
||||||
Invocations[nameof(VoidWithEightParametersAsync)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7, parameter8 };
|
Invocations[nameof(VoidWithEightParametersAsync)] = new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4, parameter5, parameter6, parameter7, parameter8 };
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(ReturnArrayAsync))]
|
[JSInvokable]
|
||||||
public static Task<decimal[]> ReturnArrayAsync()
|
public static Task<decimal[]> ReturnArrayAsync()
|
||||||
{
|
{
|
||||||
return Task.FromResult(new decimal[] { 0.1M, 0.2M });
|
return Task.FromResult(new decimal[] { 0.1M, 0.2M });
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(EchoOneParameterAsync))]
|
[JSInvokable]
|
||||||
public static Task<object[]> EchoOneParameterAsync(ComplexParameter parameter1)
|
public static Task<object[]> EchoOneParameterAsync(ComplexParameter parameter1)
|
||||||
{
|
{
|
||||||
return Task.FromResult(new object[] { parameter1 });
|
return Task.FromResult(new object[] { parameter1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(EchoTwoParametersAsync))]
|
[JSInvokable]
|
||||||
public static Task<object[]> EchoTwoParametersAsync(
|
public static Task<object[]> EchoTwoParametersAsync(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2)
|
byte parameter2)
|
||||||
|
|
@ -321,72 +320,128 @@ namespace BasicTestApp.InteropTest
|
||||||
return Task.FromResult(new object[] { parameter1, parameter2 });
|
return Task.FromResult(new object[] { parameter1, parameter2 });
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(EchoThreeParametersAsync))]
|
[JSInvokable]
|
||||||
public static Task<object[]> EchoThreeParametersAsync(
|
public static Task<object[]> EchoThreeParametersAsync(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3)
|
TestDTO parameter3)
|
||||||
{
|
{
|
||||||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3 });
|
return Task.FromResult(new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue() });
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(EchoFourParametersAsync))]
|
[JSInvokable]
|
||||||
public static Task<object[]> EchoFourParametersAsync(
|
public static Task<object[]> EchoFourParametersAsync(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4)
|
int parameter4)
|
||||||
{
|
{
|
||||||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3, parameter4 });
|
return Task.FromResult(new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4 });
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(EchoFiveParametersAsync))]
|
[JSInvokable]
|
||||||
public static Task<object[]> EchoFiveParametersAsync(
|
public static Task<object[]> EchoFiveParametersAsync(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4,
|
int parameter4,
|
||||||
long parameter5)
|
long parameter5)
|
||||||
{
|
{
|
||||||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3, parameter4, parameter5 });
|
return Task.FromResult(new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4, parameter5 });
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(EchoSixParametersAsync))]
|
[JSInvokable]
|
||||||
public static Task<object[]> EchoSixParametersAsync(ComplexParameter parameter1,
|
public static Task<object[]> EchoSixParametersAsync(ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4,
|
int parameter4,
|
||||||
long parameter5,
|
long parameter5,
|
||||||
float parameter6)
|
float parameter6)
|
||||||
{
|
{
|
||||||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6 });
|
return Task.FromResult(new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4, parameter5, parameter6 });
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(EchoSevenParametersAsync))]
|
[JSInvokable]
|
||||||
public static Task<object[]> EchoSevenParametersAsync(
|
public static Task<object[]> EchoSevenParametersAsync(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4,
|
int parameter4,
|
||||||
long parameter5,
|
long parameter5,
|
||||||
float parameter6,
|
float parameter6,
|
||||||
List<double> parameter7)
|
List<double> parameter7)
|
||||||
{
|
{
|
||||||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7 });
|
return Task.FromResult(new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4, parameter5, parameter6, parameter7 });
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable(nameof(EchoEightParametersAsync))]
|
[JSInvokable]
|
||||||
public static Task<object[]> EchoEightParametersAsync(
|
public static Task<object[]> EchoEightParametersAsync(
|
||||||
ComplexParameter parameter1,
|
ComplexParameter parameter1,
|
||||||
byte parameter2,
|
byte parameter2,
|
||||||
short parameter3,
|
TestDTO parameter3,
|
||||||
int parameter4,
|
int parameter4,
|
||||||
long parameter5,
|
long parameter5,
|
||||||
float parameter6,
|
float parameter6,
|
||||||
List<double> parameter7,
|
List<double> parameter7,
|
||||||
Segment parameter8)
|
Segment parameter8)
|
||||||
{
|
{
|
||||||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7, parameter8 });
|
return Task.FromResult(new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4, parameter5, parameter6, parameter7, parameter8 });
|
||||||
|
}
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public static Dictionary<string, object> ReturnDotNetObjectByRef()
|
||||||
|
{
|
||||||
|
return new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "Some sync instance", new DotNetObjectRef(new TestDTO(1000)) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public static async Task<Dictionary<string, object>> ReturnDotNetObjectByRefAsync()
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
return new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "Some async instance", new DotNetObjectRef(new TestDTO(1001)) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public static int ExtractNonSerializedValue(TestDTO objectByRef)
|
||||||
|
{
|
||||||
|
return objectByRef.GetNonSerializedValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public Dictionary<string, object> InstanceMethod(Dictionary<string, object> dict)
|
||||||
|
{
|
||||||
|
// This method shows we can pass in values marshalled both as JSON (the dict itself)
|
||||||
|
// and by ref (the incoming dtoByRef), plus that we can return values marshalled as
|
||||||
|
// JSON (the returned dictionary) and by ref (the outgoingByRef value)
|
||||||
|
return new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "thisTypeName", GetType().Name },
|
||||||
|
{ "stringValueUpper", ((string)dict["stringValue"]).ToUpperInvariant() },
|
||||||
|
{ "incomingByRef", ((TestDTO)dict["dtoByRef"]).GetNonSerializedValue() },
|
||||||
|
{ "outgoingByRef", new DotNetObjectRef(new TestDTO(1234)) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public async Task<Dictionary<string, object>> InstanceMethodAsync(Dictionary<string, object> dict)
|
||||||
|
{
|
||||||
|
// This method shows we can pass in values marshalled both as JSON (the dict itself)
|
||||||
|
// and by ref (the incoming dtoByRef), plus that we can return values marshalled as
|
||||||
|
// JSON (the returned dictionary) and by ref (the outgoingByRef value)
|
||||||
|
await Task.Yield();
|
||||||
|
return new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "thisTypeName", GetType().Name },
|
||||||
|
{ "stringValueUpper", ((string)dict["stringValue"]).ToUpperInvariant() },
|
||||||
|
{ "incomingByRef", ((TestDTO)dict["dtoByRef"]).GetNonSerializedValue() },
|
||||||
|
{ "outgoingByRef", new DotNetObjectRef(new TestDTO(1234)) },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace BasicTestApp.InteropTest
|
||||||
|
{
|
||||||
|
public class TestDTO
|
||||||
|
{
|
||||||
|
// JSON serialization won't include this in its output, nor will the JSON
|
||||||
|
// deserializer be able to populate it. So if the value is retained, this
|
||||||
|
// shows we're passing the object by reference, not via JSON marshalling.
|
||||||
|
private readonly int _nonSerializedValue;
|
||||||
|
|
||||||
|
public TestDTO(int nonSerializedValue)
|
||||||
|
{
|
||||||
|
_nonSerializedValue = nonSerializedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetNonSerializedValue()
|
||||||
|
=> _nonSerializedValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,59 +4,85 @@
|
||||||
var results = {};
|
var results = {};
|
||||||
var assemblyName = 'BasicTestApp';
|
var assemblyName = 'BasicTestApp';
|
||||||
|
|
||||||
function invokeDotNetInteropMethodsAsync() {
|
function invokeDotNetInteropMethodsAsync(dotNetObjectByRef, instanceMethodsTarget) {
|
||||||
console.log('Invoking void sync methods.');
|
console.log('Invoking void sync methods.');
|
||||||
DotNet.invokeMethod(assemblyName, 'VoidParameterless');
|
DotNet.invokeMethod(assemblyName, 'VoidParameterless');
|
||||||
DotNet.invokeMethod(assemblyName, 'VoidWithOneParameter', ...createArgumentList(1));
|
DotNet.invokeMethod(assemblyName, 'VoidWithOneParameter', ...createArgumentList(1, dotNetObjectByRef));
|
||||||
DotNet.invokeMethod(assemblyName, 'VoidWithTwoParameters', ...createArgumentList(2));
|
DotNet.invokeMethod(assemblyName, 'VoidWithTwoParameters', ...createArgumentList(2, dotNetObjectByRef));
|
||||||
DotNet.invokeMethod(assemblyName, 'VoidWithThreeParameters', ...createArgumentList(3));
|
DotNet.invokeMethod(assemblyName, 'VoidWithThreeParameters', ...createArgumentList(3, dotNetObjectByRef));
|
||||||
DotNet.invokeMethod(assemblyName, 'VoidWithFourParameters', ...createArgumentList(4));
|
DotNet.invokeMethod(assemblyName, 'VoidWithFourParameters', ...createArgumentList(4, dotNetObjectByRef));
|
||||||
DotNet.invokeMethod(assemblyName, 'VoidWithFiveParameters', ...createArgumentList(5));
|
DotNet.invokeMethod(assemblyName, 'VoidWithFiveParameters', ...createArgumentList(5, dotNetObjectByRef));
|
||||||
DotNet.invokeMethod(assemblyName, 'VoidWithSixParameters', ...createArgumentList(6));
|
DotNet.invokeMethod(assemblyName, 'VoidWithSixParameters', ...createArgumentList(6, dotNetObjectByRef));
|
||||||
DotNet.invokeMethod(assemblyName, 'VoidWithSevenParameters', ...createArgumentList(7));
|
DotNet.invokeMethod(assemblyName, 'VoidWithSevenParameters', ...createArgumentList(7, dotNetObjectByRef));
|
||||||
DotNet.invokeMethod(assemblyName, 'VoidWithEightParameters', ...createArgumentList(8));
|
DotNet.invokeMethod(assemblyName, 'VoidWithEightParameters', ...createArgumentList(8, dotNetObjectByRef));
|
||||||
|
|
||||||
console.log('Invoking returning sync methods.');
|
console.log('Invoking returning sync methods.');
|
||||||
results['result1'] = DotNet.invokeMethod(assemblyName, 'ReturnArray');
|
results['result1'] = DotNet.invokeMethod(assemblyName, 'ReturnArray');
|
||||||
results['result2'] = DotNet.invokeMethod(assemblyName, 'EchoOneParameter', ...createArgumentList(1));
|
results['result2'] = DotNet.invokeMethod(assemblyName, 'EchoOneParameter', ...createArgumentList(1, dotNetObjectByRef));
|
||||||
results['result3'] = DotNet.invokeMethod(assemblyName, 'EchoTwoParameters', ...createArgumentList(2));
|
results['result3'] = DotNet.invokeMethod(assemblyName, 'EchoTwoParameters', ...createArgumentList(2, dotNetObjectByRef));
|
||||||
results['result4'] = DotNet.invokeMethod(assemblyName, 'EchoThreeParameters', ...createArgumentList(3));
|
results['result4'] = DotNet.invokeMethod(assemblyName, 'EchoThreeParameters', ...createArgumentList(3, dotNetObjectByRef));
|
||||||
results['result5'] = DotNet.invokeMethod(assemblyName, 'EchoFourParameters', ...createArgumentList(4));
|
results['result5'] = DotNet.invokeMethod(assemblyName, 'EchoFourParameters', ...createArgumentList(4, dotNetObjectByRef));
|
||||||
results['result6'] = DotNet.invokeMethod(assemblyName, 'EchoFiveParameters', ...createArgumentList(5));
|
results['result6'] = DotNet.invokeMethod(assemblyName, 'EchoFiveParameters', ...createArgumentList(5, dotNetObjectByRef));
|
||||||
results['result7'] = DotNet.invokeMethod(assemblyName, 'EchoSixParameters', ...createArgumentList(6));
|
results['result7'] = DotNet.invokeMethod(assemblyName, 'EchoSixParameters', ...createArgumentList(6, dotNetObjectByRef));
|
||||||
results['result8'] = DotNet.invokeMethod(assemblyName, 'EchoSevenParameters', ...createArgumentList(7));
|
results['result8'] = DotNet.invokeMethod(assemblyName, 'EchoSevenParameters', ...createArgumentList(7, dotNetObjectByRef));
|
||||||
results['result9'] = DotNet.invokeMethod(assemblyName, 'EchoEightParameters', ...createArgumentList(8));
|
results['result9'] = DotNet.invokeMethod(assemblyName, 'EchoEightParameters', ...createArgumentList(8, dotNetObjectByRef));
|
||||||
|
|
||||||
|
var returnDotNetObjectByRefResult = DotNet.invokeMethod(assemblyName, 'ReturnDotNetObjectByRef');
|
||||||
|
results['resultReturnDotNetObjectByRefSync'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', returnDotNetObjectByRefResult['Some sync instance']);
|
||||||
|
|
||||||
|
var instanceMethodResult = instanceMethodsTarget.invokeMethod('InstanceMethod', {
|
||||||
|
stringValue: 'My string',
|
||||||
|
dtoByRef: dotNetObjectByRef
|
||||||
|
});
|
||||||
|
results['instanceMethodThisTypeName'] = instanceMethodResult.thisTypeName;
|
||||||
|
results['instanceMethodStringValueUpper'] = instanceMethodResult.stringValueUpper;
|
||||||
|
results['instanceMethodIncomingByRef'] = instanceMethodResult.incomingByRef;
|
||||||
|
results['instanceMethodOutgoingByRef'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', instanceMethodResult.outgoingByRef);
|
||||||
|
|
||||||
console.log('Invoking void async methods.');
|
console.log('Invoking void async methods.');
|
||||||
return DotNet.invokeMethodAsync(assemblyName, 'VoidParameterlessAsync')
|
return DotNet.invokeMethodAsync(assemblyName, 'VoidParameterlessAsync')
|
||||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithOneParameterAsync', ...createArgumentList(1)))
|
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithOneParameterAsync', ...createArgumentList(1, dotNetObjectByRef)))
|
||||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithTwoParametersAsync', ...createArgumentList(2)))
|
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithTwoParametersAsync', ...createArgumentList(2, dotNetObjectByRef)))
|
||||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithThreeParametersAsync', ...createArgumentList(3)))
|
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithThreeParametersAsync', ...createArgumentList(3, dotNetObjectByRef)))
|
||||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithFourParametersAsync', ...createArgumentList(4)))
|
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithFourParametersAsync', ...createArgumentList(4, dotNetObjectByRef)))
|
||||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithFiveParametersAsync', ...createArgumentList(5)))
|
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithFiveParametersAsync', ...createArgumentList(5, dotNetObjectByRef)))
|
||||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithSixParametersAsync', ...createArgumentList(6)))
|
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithSixParametersAsync', ...createArgumentList(6, dotNetObjectByRef)))
|
||||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithSevenParametersAsync', ...createArgumentList(7)))
|
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithSevenParametersAsync', ...createArgumentList(7, dotNetObjectByRef)))
|
||||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithEightParametersAsync', ...createArgumentList(8)))
|
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithEightParametersAsync', ...createArgumentList(8, dotNetObjectByRef)))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log('Invoking returning async methods.');
|
console.log('Invoking returning async methods.');
|
||||||
return DotNet.invokeMethodAsync(assemblyName, 'ReturnArrayAsync')
|
return DotNet.invokeMethodAsync(assemblyName, 'ReturnArrayAsync')
|
||||||
.then(r => results['result1Async'] = r)
|
.then(r => results['result1Async'] = r)
|
||||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoOneParameterAsync', ...createArgumentList(1)))
|
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoOneParameterAsync', ...createArgumentList(1, dotNetObjectByRef)))
|
||||||
.then(r => results['result2Async'] = r)
|
.then(r => results['result2Async'] = r)
|
||||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoTwoParametersAsync', ...createArgumentList(2)))
|
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoTwoParametersAsync', ...createArgumentList(2, dotNetObjectByRef)))
|
||||||
.then(r => results['result3Async'] = r)
|
.then(r => results['result3Async'] = r)
|
||||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoThreeParametersAsync', ...createArgumentList(3)))
|
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoThreeParametersAsync', ...createArgumentList(3, dotNetObjectByRef)))
|
||||||
.then(r => results['result4Async'] = r)
|
.then(r => results['result4Async'] = r)
|
||||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoFourParametersAsync', ...createArgumentList(4)))
|
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoFourParametersAsync', ...createArgumentList(4, dotNetObjectByRef)))
|
||||||
.then(r => results['result5Async'] = r)
|
.then(r => results['result5Async'] = r)
|
||||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoFiveParametersAsync', ...createArgumentList(5)))
|
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoFiveParametersAsync', ...createArgumentList(5, dotNetObjectByRef)))
|
||||||
.then(r => results['result6Async'] = r)
|
.then(r => results['result6Async'] = r)
|
||||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoSixParametersAsync', ...createArgumentList(6)))
|
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoSixParametersAsync', ...createArgumentList(6, dotNetObjectByRef)))
|
||||||
.then(r => results['result7Async'] = r)
|
.then(r => results['result7Async'] = r)
|
||||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoSevenParametersAsync', ...createArgumentList(7)))
|
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoSevenParametersAsync', ...createArgumentList(7, dotNetObjectByRef)))
|
||||||
.then(r => results['result8Async'] = r)
|
.then(r => results['result8Async'] = r)
|
||||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoEightParametersAsync', ...createArgumentList(8)))
|
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoEightParametersAsync', ...createArgumentList(8, dotNetObjectByRef)))
|
||||||
.then(r => results['result9Async'] = r);
|
.then(r => results['result9Async'] = r)
|
||||||
|
.then(() => DotNet.invokeMethodAsync(assemblyName, 'ReturnDotNetObjectByRefAsync'))
|
||||||
|
.then(r => {
|
||||||
|
results['resultReturnDotNetObjectByRefAsync'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', r['Some async instance']);
|
||||||
|
})
|
||||||
|
.then(() => instanceMethodsTarget.invokeMethodAsync('InstanceMethodAsync', {
|
||||||
|
stringValue: 'My string',
|
||||||
|
dtoByRef: dotNetObjectByRef
|
||||||
|
}))
|
||||||
|
.then(r => {
|
||||||
|
results['instanceMethodThisTypeNameAsync'] = r.thisTypeName;
|
||||||
|
results['instanceMethodStringValueUpperAsync'] = r.stringValueUpper;
|
||||||
|
results['instanceMethodIncomingByRefAsync'] = r.incomingByRef;
|
||||||
|
results['instanceMethodOutgoingByRefAsync'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', r.outgoingByRef);
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log('Invoking methods that throw exceptions');
|
console.log('Invoking methods that throw exceptions');
|
||||||
|
|
@ -79,7 +105,7 @@ function invokeDotNetInteropMethodsAsync() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createArgumentList(argumentNumber){
|
function createArgumentList(argumentNumber, dotNetObjectByRef){
|
||||||
const array = new Array(argumentNumber);
|
const array = new Array(argumentNumber);
|
||||||
if (argumentNumber === 0) {
|
if (argumentNumber === 0) {
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -101,7 +127,7 @@ function createArgumentList(argumentNumber){
|
||||||
array[i] = argumentNumber;
|
array[i] = argumentNumber;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
array[i] = argumentNumber * 2;
|
array[i] = dotNetObjectByRef;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
array[i] = argumentNumber * 4;
|
array[i] = argumentNumber * 4;
|
||||||
|
|
@ -136,9 +162,25 @@ window.jsInteropTests = {
|
||||||
collectInteropResults: collectInteropResults,
|
collectInteropResults: collectInteropResults,
|
||||||
functionThrowsException: functionThrowsException,
|
functionThrowsException: functionThrowsException,
|
||||||
asyncFunctionThrowsSyncException: asyncFunctionThrowsSyncException,
|
asyncFunctionThrowsSyncException: asyncFunctionThrowsSyncException,
|
||||||
asyncFunctionThrowsAsyncException: asyncFunctionThrowsAsyncException
|
asyncFunctionThrowsAsyncException: asyncFunctionThrowsAsyncException,
|
||||||
|
returnPrimitive: returnPrimitive,
|
||||||
|
returnPrimitiveAsync: returnPrimitiveAsync,
|
||||||
|
receiveDotNetObjectByRef: receiveDotNetObjectByRef,
|
||||||
|
receiveDotNetObjectByRefAsync: receiveDotNetObjectByRefAsync,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function returnPrimitive() {
|
||||||
|
return 123;
|
||||||
|
}
|
||||||
|
|
||||||
|
function returnPrimitiveAsync() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
setTimeout(function () {
|
||||||
|
resolve(123);
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function functionThrowsException() {
|
function functionThrowsException() {
|
||||||
throw new Error('Function threw an exception!');
|
throw new Error('Function threw an exception!');
|
||||||
}
|
}
|
||||||
|
|
@ -163,3 +205,29 @@ function collectInteropResults() {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function receiveDotNetObjectByRef(incomingData) {
|
||||||
|
const stringValue = incomingData.stringValue;
|
||||||
|
const testDto = incomingData.testDto;
|
||||||
|
|
||||||
|
// To verify we received a proper reference to testDto, pass it back into .NET
|
||||||
|
// to have it evaluate something that only .NET can know
|
||||||
|
const testDtoNonSerializedValue = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', testDto);
|
||||||
|
|
||||||
|
// To show we can return a .NET object by ref anywhere in a complex structure,
|
||||||
|
// return it among other values
|
||||||
|
return {
|
||||||
|
stringValueUpper: stringValue.toUpperCase(),
|
||||||
|
testDtoNonSerializedValue: testDtoNonSerializedValue,
|
||||||
|
testDto: testDto
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function receiveDotNetObjectByRefAsync(incomingData) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
setTimeout(function () {
|
||||||
|
const promiseResult = receiveDotNetObjectByRef(incomingData);
|
||||||
|
resolve(promiseResult);
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue