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()
|
||||
.then(async () => {
|
||||
DotNet.attachDispatcher({
|
||||
beginInvokeDotNetFromJS: (callId, assemblyName, methodIdentifier, argsJson) => {
|
||||
connection.send('BeginInvokeDotNetFromJS', callId ? callId.toString() : null, assemblyName, methodIdentifier, argsJson);
|
||||
beginInvokeDotNetFromJS: (callId, assemblyName, methodIdentifier, dotNetObjectId, 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() {
|
||||
const dotNetDispatcherInvokeMethodHandle = findMethod('Microsoft.JSInterop', 'Microsoft.JSInterop', 'DotNetDispatcher', 'Invoke');
|
||||
const dotNetDispatcherBeginInvokeMethodHandle = findMethod('Microsoft.JSInterop', 'Microsoft.JSInterop', 'DotNetDispatcher', 'BeginInvoke');
|
||||
const dotNetDispatcherInvokeMethodHandle = findMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop', 'MonoWebAssemblyJSRuntime', 'InvokeDotNet');
|
||||
const dotNetDispatcherBeginInvokeMethodHandle = findMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop', 'MonoWebAssemblyJSRuntime', 'BeginInvokeDotNet');
|
||||
|
||||
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, [
|
||||
callId ? monoPlatform.toDotNetString(callId.toString()) : null,
|
||||
monoPlatform.toDotNetString(assemblyName),
|
||||
monoPlatform.toDotNetString(assemblyNameOrDotNetObjectId!),
|
||||
monoPlatform.toDotNetString(methodIdentifier),
|
||||
monoPlatform.toDotNetString(argsJson)
|
||||
]);
|
||||
},
|
||||
|
||||
invokeDotNetFromJS: (assemblyName, methodIdentifier, argsJson) => {
|
||||
invokeDotNetFromJS: (assemblyName, methodIdentifier, dotNetObjectId, argsJson) => {
|
||||
const resultJsonStringPtr = monoPlatform.callMethod(dotNetDispatcherInvokeMethodHandle, null, [
|
||||
monoPlatform.toDotNetString(assemblyName),
|
||||
assemblyName ? monoPlatform.toDotNetString(assemblyName) : null,
|
||||
monoPlatform.toDotNetString(methodIdentifier),
|
||||
dotNetObjectId ? monoPlatform.toDotNetString(dotNetObjectId.toString()) : null,
|
||||
monoPlatform.toDotNetString(argsJson)
|
||||
]) as System_String;
|
||||
return resultJsonStringPtr
|
||||
? JSON.parse(monoPlatform.toJavaScriptString(resultJsonStringPtr))
|
||||
? monoPlatform.toJavaScriptString(resultJsonStringPtr)
|
||||
: null;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -51,9 +51,9 @@ namespace Microsoft.AspNetCore.Blazor.Server.Circuits
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ namespace Microsoft.AspNetCore.Blazor.Server.Circuits
|
|||
_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();
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ namespace Microsoft.AspNetCore.Blazor.Server.Circuits
|
|||
{
|
||||
SetCurrentCircuitHost(this);
|
||||
|
||||
DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, argsJson);
|
||||
DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Blazor
|
|||
/// <summary>
|
||||
/// Represents a reference to a rendered element.
|
||||
/// </summary>
|
||||
public readonly struct ElementRef : ICustomJsonSerializer
|
||||
public readonly struct ElementRef : ICustomArgSerializer
|
||||
{
|
||||
static long _nextIdForWebAssemblyOnly = 1;
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Blazor
|
|||
internal static ElementRef CreateWithUniqueId()
|
||||
=> new ElementRef(CreateUniqueId());
|
||||
|
||||
object ICustomJsonSerializer.ToJsonPrimitive()
|
||||
object ICustomArgSerializer.ToJsonPrimitive()
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// 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 System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
|
@ -23,17 +24,27 @@ namespace Microsoft.JSInterop
|
|||
/// </summary>
|
||||
/// <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="dotNetObjectId">For instance method calls, identifies the target object.</param>
|
||||
/// <param name="argsJson">A JSON representation of the parameters.</param>
|
||||
/// <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
|
||||
// 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,
|
||||
// because there would be nobody to police that. This method *is* the police.
|
||||
|
||||
var syncResult = InvokeSynchronously(assemblyName, methodIdentifier, argsJson);
|
||||
return syncResult == null ? null : Json.Serialize(syncResult);
|
||||
// DotNetDispatcher only works with JSRuntimeBase instances.
|
||||
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>
|
||||
|
|
@ -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="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="dotNetObjectId">For instance method calls, identifies the target object.</param>
|
||||
/// <param name="argsJson">A JSON representation of the parameters.</param>
|
||||
/// <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
|
||||
// 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,
|
||||
// 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 (callId != null)
|
||||
|
|
@ -61,11 +82,6 @@ namespace Microsoft.JSInterop
|
|||
var task = syncResult is Task syncResultTask ? syncResultTask : Task.FromResult(syncResult);
|
||||
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
|
||||
{
|
||||
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);
|
||||
|
||||
// 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
|
||||
var serializerStrategy = SimpleJson.SimpleJson.CurrentJsonSerializerStrategy;
|
||||
var runtime = (JSRuntimeBase)JSRuntime.Current;
|
||||
var serializerStrategy = runtime.ArgSerializerStrategy;
|
||||
for (var i = 0; i < suppliedArgsLength; i++)
|
||||
{
|
||||
suppliedArgs[i] = serializerStrategy.DeserializeObject(
|
||||
suppliedArgs[i], parameterTypes[i]);
|
||||
if (parameterTypes[i] == typeof(JSAsyncCallResult))
|
||||
{
|
||||
// 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
|
||||
{
|
||||
return methodInfo.Invoke(null, suppliedArgs);
|
||||
return methodInfo.Invoke(targetInstance, suppliedArgs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -124,10 +160,28 @@ namespace Microsoft.JSInterop
|
|||
/// </summary>
|
||||
/// <param name="asyncHandle">The identifier for the function invocation.</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))]
|
||||
public static void EndInvoke(long asyncHandle, bool succeeded, object resultOrException)
|
||||
=> ((JSRuntimeBase)JSRuntime.Current).EndInvokeJS(asyncHandle, succeeded, resultOrException);
|
||||
public static void EndInvoke(long asyncHandle, bool succeeded, JSAsyncCallResult result)
|
||||
=> ((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)
|
||||
{
|
||||
|
|
@ -156,14 +210,37 @@ namespace Microsoft.JSInterop
|
|||
{
|
||||
// TODO: Consider looking first for assembly-level attributes (i.e., if there are any,
|
||||
// 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()
|
||||
.SelectMany(type => type.GetMethods())
|
||||
.Where(method => method.IsDefined(typeof(JSInvokableAttribute), inherit: false))
|
||||
.ToDictionary(
|
||||
method => method.GetCustomAttribute<JSInvokableAttribute>(false).Identifier,
|
||||
method => (method, method.GetParameters().Select(p => p.ParameterType).ToArray())
|
||||
);
|
||||
.Where(method => method.IsDefined(typeof(JSInvokableAttribute), inherit: false));
|
||||
foreach (var method in invokableMethods)
|
||||
{
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// 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
|
||||
// and boxing of any value-typed properties).
|
||||
|
||||
/// <summary>
|
||||
/// Internal. Intended for framework use only.
|
||||
/// </summary>
|
||||
public interface ICustomJsonSerializer
|
||||
public interface ICustomArgSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal. Intended for framework use only.
|
||||
|
|
@ -18,5 +18,15 @@ namespace Microsoft.JSInterop
|
|||
/// <param name="args">JSON-serializable arguments.</param>
|
||||
/// <returns>An instance of <typeparamref name="T"/> obtained by JSON-deserializing the return value.</returns>
|
||||
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>
|
||||
public T Invoke<T>(string identifier, params object[] args)
|
||||
{
|
||||
var resultJson = InvokeJS(identifier, Json.Serialize(args));
|
||||
return Json.Deserialize<T>(resultJson);
|
||||
var resultJson = InvokeJS(identifier, Json.Serialize(args, ArgSerializerStrategy));
|
||||
return Json.Deserialize<T>(resultJson, ArgSerializerStrategy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -16,11 +16,23 @@ namespace Microsoft.JSInterop
|
|||
/// <summary>
|
||||
/// Gets the identifier for the method. The identifier must be unique within the scope
|
||||
/// 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>
|
||||
public string Identifier { get; }
|
||||
|
||||
/// <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>
|
||||
/// <param name="identifier">An identifier for the method, which must be unique within the scope of the assembly.</param>
|
||||
public JSInvokableAttribute(string identifier)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,20 @@ namespace Microsoft.JSInterop
|
|||
private readonly ConcurrentDictionary<long, object> _pendingTasks
|
||||
= 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>
|
||||
/// Invokes the specified JavaScript function asynchronously.
|
||||
/// </summary>
|
||||
|
|
@ -36,7 +50,10 @@ namespace Microsoft.JSInterop
|
|||
|
||||
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;
|
||||
}
|
||||
catch
|
||||
|
|
@ -70,7 +87,7 @@ namespace Microsoft.JSInterop
|
|||
callId,
|
||||
success,
|
||||
resultOrException
|
||||
}));
|
||||
}, ArgSerializerStrategy));
|
||||
}
|
||||
|
||||
internal void EndInvokeJS(long asyncHandle, bool succeeded, object resultOrException)
|
||||
|
|
@ -82,7 +99,11 @@ namespace Microsoft.JSInterop
|
|||
|
||||
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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -40,13 +40,7 @@ module DotNet {
|
|||
* @returns The result of the operation.
|
||||
*/
|
||||
export function invokeMethod<T>(assemblyName: string, methodIdentifier: string, ...args: any[]): T {
|
||||
const dispatcher = getRequiredDispatcher();
|
||||
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.');
|
||||
}
|
||||
return invokePossibleInstanceMethod<T>(assemblyName, methodIdentifier, null, args);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -58,14 +52,29 @@ module DotNet {
|
|||
* @returns A promise representing the result of the operation.
|
||||
*/
|
||||
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 resultPromise = new Promise<T>((resolve, reject) => {
|
||||
pendingAsyncCalls[asyncCallId] = { resolve, reject };
|
||||
});
|
||||
|
||||
try {
|
||||
const argsJson = JSON.stringify(args);
|
||||
getRequiredDispatcher().beginInvokeDotNetFromJS(asyncCallId, assemblyName, methodIdentifier, argsJson);
|
||||
const argsJson = JSON.stringify(args, argReplacer);
|
||||
getRequiredDispatcher().beginInvokeDotNetFromJS(asyncCallId, assemblyName, methodIdentifier, dotNetObjectId, argsJson);
|
||||
} catch(ex) {
|
||||
// Synchronous failure
|
||||
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.
|
||||
*
|
||||
* @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 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.
|
||||
* @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.
|
||||
*
|
||||
* @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 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.
|
||||
*/
|
||||
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));
|
||||
return result === null || result === undefined
|
||||
? null
|
||||
: JSON.stringify(result);
|
||||
: JSON.stringify(result, argReplacer);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -172,8 +183,8 @@ module DotNet {
|
|||
// On completion, dispatch result back to .NET
|
||||
// Not using "await" because it codegens a lot of boilerplate
|
||||
promise.then(
|
||||
result => getRequiredDispatcher().beginInvokeDotNetFromJS(0, 'Microsoft.JSInterop', 'DotNetDispatcher.EndInvoke', JSON.stringify([asyncHandle, true, result])),
|
||||
error => getRequiredDispatcher().beginInvokeDotNetFromJS(0, 'Microsoft.JSInterop', 'DotNetDispatcher.EndInvoke', JSON.stringify([asyncHandle, false, formatError(error)]))
|
||||
result => getRequiredDispatcher().beginInvokeDotNetFromJS(0, 'Microsoft.JSInterop', 'DotNetDispatcher.EndInvoke', null, JSON.stringify([asyncHandle, true, result], argReplacer)),
|
||||
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.`);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
=> SimpleJson.SimpleJson.SerializeObject(value);
|
||||
|
||||
internal static string Serialize(object value, SimpleJson.IJsonSerializerStrategy serializerStrategy)
|
||||
=> SimpleJson.SimpleJson.SerializeObject(value, serializerStrategy);
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the JSON string, creating an object of the specified generic type.
|
||||
/// </summary>
|
||||
|
|
@ -29,5 +32,8 @@ namespace Microsoft.JSInterop
|
|||
/// <returns>An object of the specified type.</returns>
|
||||
public static T Deserialize<T>(string 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.Text;
|
||||
using Microsoft.JSInterop;
|
||||
using Microsoft.JSInterop.Internal;
|
||||
using SimpleJson.Reflection;
|
||||
|
||||
// ReSharper disable LoopCanBeConvertedToQuery
|
||||
|
|
@ -1538,8 +1537,6 @@ namespace SimpleJson
|
|||
output = input.ToString();
|
||||
else if (input is TimeSpan)
|
||||
output = ((TimeSpan)input).ToString("c");
|
||||
else if (input is ICustomJsonSerializer customJsonSerializer)
|
||||
output = customJsonSerializer.ToJsonPrimitive();
|
||||
else
|
||||
{
|
||||
Enum inputEnum = input as Enum;
|
||||
|
|
|
|||
|
|
@ -22,26 +22,41 @@ namespace Microsoft.JSInterop
|
|||
public static void SetTaskCompletionSourceException(object taskCompletionSource, Exception exception)
|
||||
=> CreateResultSetter(taskCompletionSource).SetException(taskCompletionSource, exception);
|
||||
|
||||
public static Type GetTaskCompletionSourceResultType(object taskCompletionSource)
|
||||
=> CreateResultSetter(taskCompletionSource).ResultType;
|
||||
|
||||
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 = taskType.GetGenericArguments().Single();
|
||||
return (ITaskResultGetter)Activator.CreateInstance(
|
||||
var resultType = GetTaskResultType(taskInstanceType);
|
||||
return resultType == null
|
||||
? new VoidTaskResultGetter()
|
||||
: (ITaskResultGetter)Activator.CreateInstance(
|
||||
typeof(TaskResultGetter<>).MakeGenericType(resultType));
|
||||
}
|
||||
else
|
||||
{
|
||||
return new VoidTaskResultGetter();
|
||||
}
|
||||
});
|
||||
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
|
||||
{
|
||||
Type ResultType { get; }
|
||||
void SetResult(object taskCompletionSource, object result);
|
||||
void SetException(object taskCompletionSource, Exception exception);
|
||||
}
|
||||
|
|
@ -67,10 +82,18 @@ namespace Microsoft.JSInterop
|
|||
|
||||
private class TcsResultSetter<T> : ITcsResultSetter
|
||||
{
|
||||
public Type ResultType => typeof(T);
|
||||
|
||||
public void SetResult(object tcs, object result)
|
||||
{
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,32 @@ namespace Mono.WebAssembly.Interop
|
|||
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
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using BasicTestApp;
|
||||
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
|
||||
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
|
||||
|
|
@ -33,45 +31,63 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
|||
["VoidParameterless"] = "[]",
|
||||
["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]",
|
||||
["VoidWithThreeParameters"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,6]",
|
||||
["VoidWithFourParameters"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,8,16]",
|
||||
["VoidWithFiveParameters"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,10,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]",
|
||||
["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]]",
|
||||
["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}]",
|
||||
["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,123,16]",
|
||||
["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,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,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,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"] = "[]",
|
||||
["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]",
|
||||
["VoidWithThreeParametersAsync"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,6]",
|
||||
["VoidWithFourParametersAsync"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,8,16]",
|
||||
["VoidWithFiveParametersAsync"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,10,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]",
|
||||
["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]]",
|
||||
["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}]",
|
||||
["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,123,16]",
|
||||
["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,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,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,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]",
|
||||
["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]",
|
||||
["result4"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,6]",
|
||||
["result5"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,8,16]",
|
||||
["result6"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,10,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]",
|
||||
["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]]",
|
||||
["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}]",
|
||||
["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,123,16]",
|
||||
["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,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,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,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]",
|
||||
["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]",
|
||||
["result4Async"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,6]",
|
||||
["result5Async"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,8,16]",
|
||||
["result6Async"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,10,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]",
|
||||
["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]]",
|
||||
["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}]",
|
||||
["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,123,16]",
|
||||
["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,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,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,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!",
|
||||
["AsyncThrowSyncException"] = @"""System.InvalidOperationException: Threw a sync exception!",
|
||||
["AsyncThrowAsyncException"] = @"""System.InvalidOperationException: Threw an async exception!",
|
||||
["ExceptionFromSyncMethod"] = "Function threw an exception!",
|
||||
["SyncExceptionFromAsyncMethod"] = "Function threw a sync 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>();
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.JSInterop.Test
|
||||
|
|
@ -11,13 +12,15 @@ namespace Microsoft.JSInterop.Test
|
|||
{
|
||||
private readonly static string thisAssemblyName
|
||||
= typeof(DotNetDispatcherTest).Assembly.GetName().Name;
|
||||
private readonly TestJSRuntime jsRuntime
|
||||
= new TestJSRuntime();
|
||||
|
||||
[Fact]
|
||||
public void CannotInvokeWithEmptyAssemblyName()
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
DotNetDispatcher.Invoke(" ", "SomeMethod", "[]");
|
||||
DotNetDispatcher.Invoke(" ", "SomeMethod", default, "[]");
|
||||
});
|
||||
|
||||
Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message);
|
||||
|
|
@ -29,7 +32,7 @@ namespace Microsoft.JSInterop.Test
|
|||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
DotNetDispatcher.Invoke("SomeAssembly", " ", "[]");
|
||||
DotNetDispatcher.Invoke("SomeAssembly", " ", default, "[]");
|
||||
});
|
||||
|
||||
Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message);
|
||||
|
|
@ -42,75 +45,172 @@ namespace Microsoft.JSInterop.Test
|
|||
var assemblyName = "Some.Fake.Assembly";
|
||||
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);
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// fine (an exception stating what info is missing), plus we're likely to add support
|
||||
// for invoking instance methods in the near future.
|
||||
// fine (an exception stating what info is missing).
|
||||
|
||||
[Theory]
|
||||
[InlineData("MethodOnInternalType")]
|
||||
[InlineData("PrivateMethod")]
|
||||
[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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanInvokeStaticVoidMethod()
|
||||
public Task CanInvokeStaticVoidMethod() => WithJSRuntime(jsRuntime =>
|
||||
{
|
||||
// Arrange/Act
|
||||
SomePublicType.DidInvokeMyInvocableVoid = false;
|
||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticVoid", null);
|
||||
SomePublicType.DidInvokeMyInvocableStaticVoid = false;
|
||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticVoid", default, null);
|
||||
|
||||
// Assert
|
||||
Assert.Null(resultJson);
|
||||
Assert.True(SomePublicType.DidInvokeMyInvocableVoid);
|
||||
}
|
||||
Assert.True(SomePublicType.DidInvokeMyInvocableStaticVoid);
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public void CanInvokeStaticNonVoidMethod()
|
||||
public Task CanInvokeStaticNonVoidMethod() => WithJSRuntime(jsRuntime =>
|
||||
{
|
||||
// Arrange/Act
|
||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticNonVoid", null);
|
||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticNonVoid", default, null);
|
||||
var result = Json.Deserialize<TestDTO>(resultJson);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Test", result.StringVal);
|
||||
Assert.Equal(123, result.IntVal);
|
||||
}
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public void CanInvokeStaticWithParams()
|
||||
public Task CanInvokeStaticNonVoidMethodWithoutCustomIdentifier() => WithJSRuntime(jsRuntime =>
|
||||
{
|
||||
// Arrange
|
||||
var argsJson = Json.Serialize(new object[] {
|
||||
new TestDTO { StringVal = "Another string", IntVal = 456 },
|
||||
new[] { 100, 200 }
|
||||
});
|
||||
|
||||
// Act
|
||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", argsJson);
|
||||
// Arrange/Act
|
||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, nameof(SomePublicType.InvokableMethodWithoutCustomIdentifier), default, null);
|
||||
var result = Json.Deserialize<TestDTO>(resultJson);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("ANOTHER STRING", result.StringVal);
|
||||
Assert.Equal(756, result.IntVal);
|
||||
}
|
||||
Assert.Equal("InvokableMethodWithoutCustomIdentifier", result.StringVal);
|
||||
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]
|
||||
public void CannotInvokeWithIncorrectNumberOfParams()
|
||||
|
|
@ -121,10 +221,73 @@ namespace Microsoft.JSInterop.Test
|
|||
// Act/Assert
|
||||
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
|
||||
|
|
@ -134,15 +297,17 @@ namespace Microsoft.JSInterop.Test
|
|||
|
||||
public class SomePublicType
|
||||
{
|
||||
public static bool DidInvokeMyInvocableVoid;
|
||||
public static bool DidInvokeMyInvocableStaticVoid;
|
||||
public bool DidInvokeMyInvocableInstanceVoid;
|
||||
|
||||
[JSInvokable("PrivateMethod")] private void MyPrivateMethod() { }
|
||||
[JSInvokable("ProtectedMethod")] protected void MyProtectedMethod() { }
|
||||
protected void MethodWithoutAttribute() { }
|
||||
[JSInvokable("PrivateMethod")] private static void MyPrivateMethod() { }
|
||||
[JSInvokable("ProtectedMethod")] protected static void MyProtectedMethod() { }
|
||||
protected static void StaticMethodWithoutAttribute() { }
|
||||
protected static void InstanceMethodWithoutAttribute() { }
|
||||
|
||||
[JSInvokable("InvocableStaticVoid")] public static void MyInvocableVoid()
|
||||
{
|
||||
DidInvokeMyInvocableVoid = true;
|
||||
DidInvokeMyInvocableStaticVoid = true;
|
||||
}
|
||||
|
||||
[JSInvokable("InvocableStaticNonVoid")]
|
||||
|
|
@ -150,8 +315,65 @@ namespace Microsoft.JSInterop.Test
|
|||
=> new TestDTO { StringVal = "Test", IntVal = 123 };
|
||||
|
||||
[JSInvokable("InvocableStaticWithParams")]
|
||||
public static TestDTO MyInvocableWithParams(TestDTO dto, int[] incrementAmounts)
|
||||
=> new TestDTO { StringVal = dto.StringVal.ToUpperInvariant(), IntVal = dto.IntVal + incrementAmounts.Sum() };
|
||||
public static object[] MyInvocableWithParams(TestDTO dtoViaJson, int[] incrementAmounts, TestDTO dtoByRef)
|
||||
=> 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
|
||||
|
|
@ -159,5 +381,33 @@ namespace Microsoft.JSInterop.Test
|
|||
public string StringVal { 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.
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.JSInterop.Test
|
||||
|
|
@ -98,7 +100,57 @@ namespace Microsoft.JSInterop.Test
|
|||
});
|
||||
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
|
||||
{
|
||||
public List<BeginInvokeAsyncArgs> BeginInvokeCalls = new List<BeginInvokeAsyncArgs>();
|
||||
|
|
@ -123,5 +175,17 @@ namespace Microsoft.JSInterop.Test
|
|||
public void OnEndInvoke(long asyncHandle, bool succeeded, object 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)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public void UntrackObjectRef(DotNetObjectRef dotNetObjectRef)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -191,16 +191,6 @@ namespace Microsoft.JSInterop.Test
|
|||
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
|
||||
// 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
|
||||
|
|
@ -273,18 +263,6 @@ namespace Microsoft.JSInterop.Test
|
|||
|
||||
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
|
||||
class ClashingProperties
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,6 +12,20 @@
|
|||
}
|
||||
</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>
|
||||
<h1>Return values and exceptions thrown from .NET</h1>
|
||||
@foreach (var returnValue in ReturnValues)
|
||||
|
|
@ -44,14 +58,23 @@
|
|||
public JSException SyncExceptionFromAsyncMethod { 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 async Task InvokeInteropAsync()
|
||||
{
|
||||
var inProcRuntime = ((IJSInProcessRuntime)JSRuntime.Current);
|
||||
var testDTOTOPassByRef = new TestDTO(nonSerializedValue: 123);
|
||||
|
||||
var instanceMethodsTarget = new JavaScriptInterop();
|
||||
|
||||
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.");
|
||||
var collectResults = inProcRuntime.Invoke<Dictionary<string,string>>("jsInteropTests.collectInteropResults");
|
||||
|
||||
|
|
@ -91,6 +114,20 @@
|
|||
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;
|
||||
DoneWithInterop = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
using Microsoft.JSInterop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BasicTestApp.InteropTest
|
||||
|
|
@ -13,33 +12,33 @@ namespace BasicTestApp.InteropTest
|
|||
{
|
||||
public static IDictionary<string, object[]> Invocations = new Dictionary<string, object[]>();
|
||||
|
||||
[JSInvokable(nameof(ThrowException))]
|
||||
[JSInvokable]
|
||||
public static void ThrowException() => throw new InvalidOperationException("Threw an exception!");
|
||||
|
||||
[JSInvokable(nameof(AsyncThrowSyncException))]
|
||||
[JSInvokable]
|
||||
public static Task AsyncThrowSyncException()
|
||||
=> throw new InvalidOperationException("Threw a sync exception!");
|
||||
|
||||
[JSInvokable(nameof(AsyncThrowAsyncException))]
|
||||
[JSInvokable]
|
||||
public static async Task AsyncThrowAsyncException()
|
||||
{
|
||||
await Task.Yield();
|
||||
throw new InvalidOperationException("Threw an async exception!");
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidParameterless))]
|
||||
[JSInvokable]
|
||||
public static void VoidParameterless()
|
||||
{
|
||||
Invocations[nameof(VoidParameterless)] = new object[0];
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithOneParameter))]
|
||||
[JSInvokable]
|
||||
public static void VoidWithOneParameter(ComplexParameter parameter1)
|
||||
{
|
||||
Invocations[nameof(VoidWithOneParameter)] = new object[] { parameter1 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithTwoParameters))]
|
||||
[JSInvokable]
|
||||
public static void VoidWithTwoParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2)
|
||||
|
|
@ -47,88 +46,88 @@ namespace BasicTestApp.InteropTest
|
|||
Invocations[nameof(VoidWithTwoParameters)] = new object[] { parameter1, parameter2 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithThreeParameters))]
|
||||
[JSInvokable]
|
||||
public static void VoidWithThreeParameters(
|
||||
ComplexParameter parameter1,
|
||||
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(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
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(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
int parameter4,
|
||||
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(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
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(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
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(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7,
|
||||
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()
|
||||
{
|
||||
return new decimal[] { 0.1M, 0.2M };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoOneParameter))]
|
||||
[JSInvokable]
|
||||
public static object[] EchoOneParameter(ComplexParameter parameter1)
|
||||
{
|
||||
return new object[] { parameter1 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoTwoParameters))]
|
||||
[JSInvokable]
|
||||
public static object[] EchoTwoParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2)
|
||||
|
|
@ -136,88 +135,88 @@ namespace BasicTestApp.InteropTest
|
|||
return new object[] { parameter1, parameter2 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoThreeParameters))]
|
||||
[JSInvokable]
|
||||
public static object[] EchoThreeParameters(
|
||||
ComplexParameter parameter1,
|
||||
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(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
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(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
int parameter4,
|
||||
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,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
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,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
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(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7,
|
||||
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()
|
||||
{
|
||||
Invocations[nameof(VoidParameterlessAsync)] = new object[0];
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithOneParameterAsync))]
|
||||
[JSInvokable]
|
||||
public static Task VoidWithOneParameterAsync(ComplexParameter parameter1)
|
||||
{
|
||||
Invocations[nameof(VoidWithOneParameterAsync)] = new object[] { parameter1 };
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithTwoParametersAsync))]
|
||||
[JSInvokable]
|
||||
public static Task VoidWithTwoParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2)
|
||||
|
|
@ -226,94 +225,94 @@ namespace BasicTestApp.InteropTest
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithThreeParametersAsync))]
|
||||
[JSInvokable]
|
||||
public static Task VoidWithThreeParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
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;
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithFourParametersAsync))]
|
||||
[JSInvokable]
|
||||
public static Task VoidWithFourParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
int parameter4)
|
||||
{
|
||||
Invocations[nameof(VoidWithFourParametersAsync)] = new object[] { parameter1, parameter2, parameter3, parameter4 };
|
||||
Invocations[nameof(VoidWithFourParametersAsync)] = new object[] { parameter1, parameter2, parameter3.GetNonSerializedValue(), parameter4 };
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithFiveParametersAsync))]
|
||||
[JSInvokable]
|
||||
public static Task VoidWithFiveParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
int parameter4,
|
||||
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;
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithSixParametersAsync))]
|
||||
[JSInvokable]
|
||||
public static Task VoidWithSixParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
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;
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithSevenParametersAsync))]
|
||||
[JSInvokable]
|
||||
public static Task VoidWithSevenParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
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;
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithEightParametersAsync))]
|
||||
[JSInvokable]
|
||||
public static Task VoidWithEightParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7,
|
||||
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;
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(ReturnArrayAsync))]
|
||||
[JSInvokable]
|
||||
public static Task<decimal[]> ReturnArrayAsync()
|
||||
{
|
||||
return Task.FromResult(new decimal[] { 0.1M, 0.2M });
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoOneParameterAsync))]
|
||||
[JSInvokable]
|
||||
public static Task<object[]> EchoOneParameterAsync(ComplexParameter parameter1)
|
||||
{
|
||||
return Task.FromResult(new object[] { parameter1 });
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoTwoParametersAsync))]
|
||||
[JSInvokable]
|
||||
public static Task<object[]> EchoTwoParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2)
|
||||
|
|
@ -321,72 +320,128 @@ namespace BasicTestApp.InteropTest
|
|||
return Task.FromResult(new object[] { parameter1, parameter2 });
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoThreeParametersAsync))]
|
||||
[JSInvokable]
|
||||
public static Task<object[]> EchoThreeParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
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(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
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(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
int parameter4,
|
||||
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,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
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(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
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(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
TestDTO parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7,
|
||||
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 assemblyName = 'BasicTestApp';
|
||||
|
||||
function invokeDotNetInteropMethodsAsync() {
|
||||
function invokeDotNetInteropMethodsAsync(dotNetObjectByRef, instanceMethodsTarget) {
|
||||
console.log('Invoking void sync methods.');
|
||||
DotNet.invokeMethod(assemblyName, 'VoidParameterless');
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithOneParameter', ...createArgumentList(1));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithTwoParameters', ...createArgumentList(2));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithThreeParameters', ...createArgumentList(3));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithFourParameters', ...createArgumentList(4));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithFiveParameters', ...createArgumentList(5));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithSixParameters', ...createArgumentList(6));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithSevenParameters', ...createArgumentList(7));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithEightParameters', ...createArgumentList(8));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithOneParameter', ...createArgumentList(1, dotNetObjectByRef));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithTwoParameters', ...createArgumentList(2, dotNetObjectByRef));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithThreeParameters', ...createArgumentList(3, dotNetObjectByRef));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithFourParameters', ...createArgumentList(4, dotNetObjectByRef));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithFiveParameters', ...createArgumentList(5, dotNetObjectByRef));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithSixParameters', ...createArgumentList(6, dotNetObjectByRef));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithSevenParameters', ...createArgumentList(7, dotNetObjectByRef));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithEightParameters', ...createArgumentList(8, dotNetObjectByRef));
|
||||
|
||||
console.log('Invoking returning sync methods.');
|
||||
results['result1'] = DotNet.invokeMethod(assemblyName, 'ReturnArray');
|
||||
results['result2'] = DotNet.invokeMethod(assemblyName, 'EchoOneParameter', ...createArgumentList(1));
|
||||
results['result3'] = DotNet.invokeMethod(assemblyName, 'EchoTwoParameters', ...createArgumentList(2));
|
||||
results['result4'] = DotNet.invokeMethod(assemblyName, 'EchoThreeParameters', ...createArgumentList(3));
|
||||
results['result5'] = DotNet.invokeMethod(assemblyName, 'EchoFourParameters', ...createArgumentList(4));
|
||||
results['result6'] = DotNet.invokeMethod(assemblyName, 'EchoFiveParameters', ...createArgumentList(5));
|
||||
results['result7'] = DotNet.invokeMethod(assemblyName, 'EchoSixParameters', ...createArgumentList(6));
|
||||
results['result8'] = DotNet.invokeMethod(assemblyName, 'EchoSevenParameters', ...createArgumentList(7));
|
||||
results['result9'] = DotNet.invokeMethod(assemblyName, 'EchoEightParameters', ...createArgumentList(8));
|
||||
results['result2'] = DotNet.invokeMethod(assemblyName, 'EchoOneParameter', ...createArgumentList(1, dotNetObjectByRef));
|
||||
results['result3'] = DotNet.invokeMethod(assemblyName, 'EchoTwoParameters', ...createArgumentList(2, dotNetObjectByRef));
|
||||
results['result4'] = DotNet.invokeMethod(assemblyName, 'EchoThreeParameters', ...createArgumentList(3, dotNetObjectByRef));
|
||||
results['result5'] = DotNet.invokeMethod(assemblyName, 'EchoFourParameters', ...createArgumentList(4, dotNetObjectByRef));
|
||||
results['result6'] = DotNet.invokeMethod(assemblyName, 'EchoFiveParameters', ...createArgumentList(5, dotNetObjectByRef));
|
||||
results['result7'] = DotNet.invokeMethod(assemblyName, 'EchoSixParameters', ...createArgumentList(6, dotNetObjectByRef));
|
||||
results['result8'] = DotNet.invokeMethod(assemblyName, 'EchoSevenParameters', ...createArgumentList(7, dotNetObjectByRef));
|
||||
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.');
|
||||
return DotNet.invokeMethodAsync(assemblyName, 'VoidParameterlessAsync')
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithOneParameterAsync', ...createArgumentList(1)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithTwoParametersAsync', ...createArgumentList(2)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithThreeParametersAsync', ...createArgumentList(3)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithFourParametersAsync', ...createArgumentList(4)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithFiveParametersAsync', ...createArgumentList(5)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithSixParametersAsync', ...createArgumentList(6)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithSevenParametersAsync', ...createArgumentList(7)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithEightParametersAsync', ...createArgumentList(8)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithOneParameterAsync', ...createArgumentList(1, dotNetObjectByRef)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithTwoParametersAsync', ...createArgumentList(2, dotNetObjectByRef)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithThreeParametersAsync', ...createArgumentList(3, dotNetObjectByRef)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithFourParametersAsync', ...createArgumentList(4, dotNetObjectByRef)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithFiveParametersAsync', ...createArgumentList(5, dotNetObjectByRef)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithSixParametersAsync', ...createArgumentList(6, dotNetObjectByRef)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithSevenParametersAsync', ...createArgumentList(7, dotNetObjectByRef)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithEightParametersAsync', ...createArgumentList(8, dotNetObjectByRef)))
|
||||
.then(() => {
|
||||
console.log('Invoking returning async methods.');
|
||||
return DotNet.invokeMethodAsync(assemblyName, 'ReturnArrayAsync')
|
||||
.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(() => DotNet.invokeMethodAsync(assemblyName, 'EchoTwoParametersAsync', ...createArgumentList(2)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoTwoParametersAsync', ...createArgumentList(2, dotNetObjectByRef)))
|
||||
.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(() => DotNet.invokeMethodAsync(assemblyName, 'EchoFourParametersAsync', ...createArgumentList(4)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoFourParametersAsync', ...createArgumentList(4, dotNetObjectByRef)))
|
||||
.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(() => DotNet.invokeMethodAsync(assemblyName, 'EchoSixParametersAsync', ...createArgumentList(6)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoSixParametersAsync', ...createArgumentList(6, dotNetObjectByRef)))
|
||||
.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(() => DotNet.invokeMethodAsync(assemblyName, 'EchoEightParametersAsync', ...createArgumentList(8)))
|
||||
.then(r => results['result9Async'] = r);
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoEightParametersAsync', ...createArgumentList(8, dotNetObjectByRef)))
|
||||
.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(() => {
|
||||
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);
|
||||
if (argumentNumber === 0) {
|
||||
return [];
|
||||
|
|
@ -101,7 +127,7 @@ function createArgumentList(argumentNumber){
|
|||
array[i] = argumentNumber;
|
||||
break;
|
||||
case 2:
|
||||
array[i] = argumentNumber * 2;
|
||||
array[i] = dotNetObjectByRef;
|
||||
break;
|
||||
case 3:
|
||||
array[i] = argumentNumber * 4;
|
||||
|
|
@ -136,9 +162,25 @@ window.jsInteropTests = {
|
|||
collectInteropResults: collectInteropResults,
|
||||
functionThrowsException: functionThrowsException,
|
||||
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() {
|
||||
throw new Error('Function threw an exception!');
|
||||
}
|
||||
|
|
@ -163,3 +205,29 @@ function collectInteropResults() {
|
|||
|
||||
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