Simplify JSRuntime method signature (dotnet/extensions#2188)

\n\nCommit migrated from 9c392a92ef
This commit is contained in:
Pranav K 2019-08-16 16:19:23 -07:00 committed by GitHub
parent ef83e3359d
commit d46d569b81
12 changed files with 249 additions and 122 deletions

View File

@ -50,7 +50,7 @@ namespace Microsoft.JSInterop
protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
protected internal System.Text.Json.JsonSerializerOptions JsonSerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson);
protected internal abstract void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId);
protected internal abstract void EndInvokeDotNet(Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, in Microsoft.JSInterop.Infrastructure.DotNetInvocationResult invocationResult);
public System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args) { throw null; }
public System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, System.Threading.CancellationToken cancellationToken, object[] args) { throw null; }
}
@ -72,8 +72,31 @@ namespace Microsoft.JSInterop.Infrastructure
{
public static partial class DotNetDispatcher
{
public static void BeginInvokeDotNet(Microsoft.JSInterop.JSRuntime jsRuntime, string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { }
public static void BeginInvokeDotNet(Microsoft.JSInterop.JSRuntime jsRuntime, Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, string argsJson) { }
public static void EndInvokeJS(Microsoft.JSInterop.JSRuntime jsRuntime, string arguments) { }
public static string Invoke(Microsoft.JSInterop.JSRuntime jsRuntime, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; }
public static string Invoke(Microsoft.JSInterop.JSRuntime jsRuntime, in Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, string argsJson) { throw null; }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct DotNetInvocationInfo
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public DotNetInvocationInfo(string assemblyName, string methodIdentifier, long dotNetObjectId, string callId) { throw null; }
public string AssemblyName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public string CallId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public long DotNetObjectId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public string MethodIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct DotNetInvocationResult
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public DotNetInvocationResult(System.Exception exception, string errorKind) { throw null; }
public DotNetInvocationResult(object result) { throw null; }
public string ErrorKind { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public System.Exception Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public object Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public bool Success { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
}
}

View File

@ -50,7 +50,7 @@ namespace Microsoft.JSInterop
protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
protected internal System.Text.Json.JsonSerializerOptions JsonSerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson);
protected internal abstract void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId);
protected internal abstract void EndInvokeDotNet(Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, in Microsoft.JSInterop.Infrastructure.DotNetInvocationResult invocationResult);
public System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args) { throw null; }
public System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, System.Threading.CancellationToken cancellationToken, object[] args) { throw null; }
}
@ -72,8 +72,31 @@ namespace Microsoft.JSInterop.Infrastructure
{
public static partial class DotNetDispatcher
{
public static void BeginInvokeDotNet(Microsoft.JSInterop.JSRuntime jsRuntime, string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { }
public static void BeginInvokeDotNet(Microsoft.JSInterop.JSRuntime jsRuntime, Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, string argsJson) { }
public static void EndInvokeJS(Microsoft.JSInterop.JSRuntime jsRuntime, string arguments) { }
public static string Invoke(Microsoft.JSInterop.JSRuntime jsRuntime, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; }
public static string Invoke(Microsoft.JSInterop.JSRuntime jsRuntime, in Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, string argsJson) { throw null; }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct DotNetInvocationInfo
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public DotNetInvocationInfo(string assemblyName, string methodIdentifier, long dotNetObjectId, string callId) { throw null; }
public string AssemblyName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public string CallId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public long DotNetObjectId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public string MethodIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct DotNetInvocationResult
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public DotNetInvocationResult(System.Exception exception, string errorKind) { throw null; }
public DotNetInvocationResult(object result) { throw null; }
public string ErrorKind { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public System.Exception Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public object Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public bool Success { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
}
}

View File

@ -28,12 +28,10 @@ namespace Microsoft.JSInterop.Infrastructure
/// Receives a call from JS to .NET, locating and invoking the specified method.
/// </summary>
/// <param name="jsRuntime">The <see cref="JSRuntime"/>.</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="invocationInfo">The <see cref="DotNetInvocationInfo"/>.</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(JSRuntime jsRuntime, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson)
public static string Invoke(JSRuntime jsRuntime, in DotNetInvocationInfo invocationInfo, 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
@ -41,12 +39,12 @@ namespace Microsoft.JSInterop.Infrastructure
// because there would be nobody to police that. This method *is* the police.
IDotNetObjectReference targetInstance = default;
if (dotNetObjectId != default)
if (invocationInfo.DotNetObjectId != default)
{
targetInstance = jsRuntime.GetObjectReference(dotNetObjectId);
targetInstance = jsRuntime.GetObjectReference(invocationInfo.DotNetObjectId);
}
var syncResult = InvokeSynchronously(jsRuntime, assemblyName, methodIdentifier, targetInstance, argsJson);
var syncResult = InvokeSynchronously(jsRuntime, invocationInfo, targetInstance, argsJson);
if (syncResult == null)
{
return null;
@ -59,13 +57,10 @@ namespace Microsoft.JSInterop.Infrastructure
/// Receives a call from JS to .NET, locating and invoking the specified method asynchronously.
/// </summary>
/// <param name="jsRuntime">The <see cref="JSRuntime"/>.</param>
/// <param name="callId">A value identifying the asynchronous call that should be passed back with the result, or null if no result notification is required.</param>
/// <param name="assemblyName">The assembly containing the method to be invoked.</param>
/// <param name="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="invocationInfo">The <see cref="DotNetInvocationInfo"/>.</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 BeginInvokeDotNet(JSRuntime jsRuntime, string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson)
public static void BeginInvokeDotNet(JSRuntime jsRuntime, DotNetInvocationInfo invocationInfo, 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
@ -75,18 +70,19 @@ namespace Microsoft.JSInterop.Infrastructure
// Using ExceptionDispatchInfo here throughout because we want to always preserve
// original stack traces.
var callId = invocationInfo.CallId;
object syncResult = null;
ExceptionDispatchInfo syncException = null;
IDotNetObjectReference targetInstance = null;
try
{
if (dotNetObjectId != default)
if (invocationInfo.DotNetObjectId != default)
{
targetInstance = jsRuntime.GetObjectReference(dotNetObjectId);
targetInstance = jsRuntime.GetObjectReference(invocationInfo.DotNetObjectId);
}
syncResult = InvokeSynchronously(jsRuntime, assemblyName, methodIdentifier, targetInstance, argsJson);
syncResult = InvokeSynchronously(jsRuntime, invocationInfo, targetInstance, argsJson);
}
catch (Exception ex)
{
@ -101,7 +97,7 @@ namespace Microsoft.JSInterop.Infrastructure
else if (syncException != null)
{
// Threw synchronously, let's respond.
jsRuntime.EndInvokeDotNet(callId, false, syncException, assemblyName, methodIdentifier, dotNetObjectId);
jsRuntime.EndInvokeDotNet(invocationInfo, new DotNetInvocationResult(syncException.SourceException, "InvocationFailure"));
}
else if (syncResult is Task task)
{
@ -111,23 +107,27 @@ namespace Microsoft.JSInterop.Infrastructure
{
if (t.Exception != null)
{
var exception = t.Exception.GetBaseException();
jsRuntime.EndInvokeDotNet(callId, false, ExceptionDispatchInfo.Capture(exception), assemblyName, methodIdentifier, dotNetObjectId);
var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(t.Exception.GetBaseException());
var dispatchResult = new DotNetInvocationResult(exceptionDispatchInfo.SourceException, "InvocationFailure");
jsRuntime.EndInvokeDotNet(invocationInfo, dispatchResult);
}
var result = TaskGenericsUtil.GetTaskResult(task);
jsRuntime.EndInvokeDotNet(callId, true, result, assemblyName, methodIdentifier, dotNetObjectId);
jsRuntime.EndInvokeDotNet(invocationInfo, new DotNetInvocationResult(result));
}, TaskScheduler.Current);
}
else
{
jsRuntime.EndInvokeDotNet(callId, true, syncResult, assemblyName, methodIdentifier, dotNetObjectId);
var dispatchResult = new DotNetInvocationResult(syncResult);
jsRuntime.EndInvokeDotNet(invocationInfo, dispatchResult);
}
}
private static object InvokeSynchronously(JSRuntime jsRuntime, string assemblyName, string methodIdentifier, IDotNetObjectReference objectReference, string argsJson)
private static object InvokeSynchronously(JSRuntime jsRuntime, in DotNetInvocationInfo callInfo, IDotNetObjectReference objectReference, string argsJson)
{
var assemblyName = callInfo.AssemblyName;
var methodIdentifier = callInfo.MethodIdentifier;
AssemblyKey assemblyKey;
if (objectReference is null)
{

View File

@ -0,0 +1,48 @@
// 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.Infrastructure
{
/// <summary>
/// Information about a JSInterop call from JavaScript to .NET.
/// </summary>
public readonly struct DotNetInvocationInfo
{
/// <summary>
/// Initializes a new instance of <see cref="DotNetInvocationInfo"/>.
/// </summary>
/// <param name="assemblyName">The name of the assembly containing the method.</param>
/// <param name="methodIdentifier">The identifier of the method to be invoked.</param>
/// <param name="dotNetObjectId">The object identifier for instance method calls.</param>
/// <param name="callId">The call identifier.</param>
public DotNetInvocationInfo(string assemblyName, string methodIdentifier, long dotNetObjectId, string callId)
{
CallId = callId;
AssemblyName = assemblyName;
MethodIdentifier = methodIdentifier;
DotNetObjectId = dotNetObjectId;
}
/// <summary>
/// Gets the name of the assembly containing the method.
/// Only one of <see cref="DotNetObjectId"/> or <see cref="AssemblyName"/> may be specified.
/// </summary>
public string AssemblyName { get; }
/// <summary>
/// Gets the identifier of the method to be invoked. This is the value specified in the <see cref="JSInvokableAttribute"/>.
/// </summary>
public string MethodIdentifier { get; }
/// <summary>
/// Gets the object identifier for instance method calls.
/// Only one of <see cref="DotNetObjectId"/> or <see cref="AssemblyName"/> may be specified.
/// </summary>
public long DotNetObjectId { get; }
/// <summary>
/// Gets the call identifier. This value is <see langword="null"/> when the client does not expect a value to be returned.
/// </summary>
public string CallId { get; }
}
}

View File

@ -0,0 +1,55 @@
using System;
namespace Microsoft.JSInterop.Infrastructure
{
/// <summary>
/// Result of a .NET invocation that is returned to JavaScript.
/// </summary>
public readonly struct DotNetInvocationResult
{
/// <summary>
/// Constructor for a failed invocation.
/// </summary>
/// <param name="exception">The <see cref="System.Exception"/> that caused the failure.</param>
/// <param name="errorKind">The error kind.</param>
public DotNetInvocationResult(Exception exception, string errorKind)
{
Result = default;
Exception = exception ?? throw new ArgumentNullException(nameof(exception));
ErrorKind = errorKind;
Success = false;
}
/// <summary>
/// Constructor for a successful invocation.
/// </summary>
/// <param name="result">The result.</param>
public DotNetInvocationResult(object result)
{
Result = result;
Exception = default;
ErrorKind = default;
Success = true;
}
/// <summary>
/// Gets the <see cref="System.Exception"/> that caused the failure.
/// </summary>
public Exception Exception { get; }
/// <summary>
/// Gets the error kind.
/// </summary>
public string ErrorKind { get; }
/// <summary>
/// Gets the result of a successful invocation.
/// </summary>
public object Result { get; }
/// <summary>
/// <see langword="true"/> if the invocation succeeded, otherwise <see langword="false"/>.
/// </summary>
public bool Success { get; }
}
}

View File

@ -150,19 +150,11 @@ namespace Microsoft.JSInterop
/// <summary>
/// Completes an async JS interop call from JavaScript to .NET
/// </summary>
/// <param name="callId">The id of the JavaScript callback to execute on completion.</param>
/// <param name="success">Whether the operation succeeded or not.</param>
/// <param name="resultOrError">The result of the operation or an object containing error details.</param>
/// <param name="assemblyName">The name of the method assembly if the invocation was for a static method.</param>
/// <param name="methodIdentifier">The identifier for the method within the assembly.</param>
/// <param name="dotNetObjectId">The tracking id of the dotnet object if the invocation was for an instance method.</param>
/// <param name="invocationInfo">The <see cref="DotNetInvocationInfo"/>.</param>
/// <param name="invocationResult">The <see cref="DotNetInvocationResult"/>.</param>
protected internal abstract void EndInvokeDotNet(
string callId,
bool success,
object resultOrError,
string assemblyName,
string methodIdentifier,
long dotNetObjectId);
DotNetInvocationInfo invocationInfo,
in DotNetInvocationResult invocationResult);
internal void EndInvokeJS(long taskId, bool succeeded, ref Utf8JsonReader jsonReader)
{

View File

@ -20,7 +20,7 @@ namespace Microsoft.JSInterop.Infrastructure
{
var ex = Assert.Throws<ArgumentException>(() =>
{
DotNetDispatcher.Invoke(new TestJSRuntime(), " ", "SomeMethod", default, "[]");
DotNetDispatcher.Invoke(new TestJSRuntime(), new DotNetInvocationInfo(" ", "SomeMethod", default, default), "[]");
});
Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message);
@ -32,7 +32,7 @@ namespace Microsoft.JSInterop.Infrastructure
{
var ex = Assert.Throws<ArgumentException>(() =>
{
DotNetDispatcher.Invoke(new TestJSRuntime(), "SomeAssembly", " ", default, "[]");
DotNetDispatcher.Invoke(new TestJSRuntime(), new DotNetInvocationInfo("SomeAssembly", " ", default, default), "[]");
});
Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message);
@ -45,7 +45,7 @@ namespace Microsoft.JSInterop.Infrastructure
var assemblyName = "Some.Fake.Assembly";
var ex = Assert.Throws<ArgumentException>(() =>
{
DotNetDispatcher.Invoke(new TestJSRuntime(), assemblyName, "SomeMethod", default, null);
DotNetDispatcher.Invoke(new TestJSRuntime(), new DotNetInvocationInfo(assemblyName, "SomeMethod", default, default), null);
});
Assert.Equal($"There is no loaded assembly with the name '{assemblyName}'.", ex.Message);
@ -67,7 +67,7 @@ namespace Microsoft.JSInterop.Infrastructure
{
var ex = Assert.Throws<ArgumentException>(() =>
{
DotNetDispatcher.Invoke(new TestJSRuntime(), thisAssemblyName, methodIdentifier, default, null);
DotNetDispatcher.Invoke(new TestJSRuntime(), new DotNetInvocationInfo(thisAssemblyName, methodIdentifier, default, default), null);
});
Assert.Equal($"The assembly '{thisAssemblyName}' does not contain a public method with [JSInvokableAttribute(\"{methodIdentifier}\")].", ex.Message);
@ -79,7 +79,7 @@ namespace Microsoft.JSInterop.Infrastructure
// Arrange/Act
var jsRuntime = new TestJSRuntime();
SomePublicType.DidInvokeMyInvocableStaticVoid = false;
var resultJson = DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, "InvocableStaticVoid", default, null);
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticVoid", default, default), null);
// Assert
Assert.Null(resultJson);
@ -91,7 +91,7 @@ namespace Microsoft.JSInterop.Infrastructure
{
// Arrange/Act
var jsRuntime = new TestJSRuntime();
var resultJson = DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, "InvocableStaticNonVoid", default, null);
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticNonVoid", default, default), null);
var result = JsonSerializer.Deserialize<TestDTO>(resultJson, jsRuntime.JsonSerializerOptions);
// Assert
@ -104,7 +104,7 @@ namespace Microsoft.JSInterop.Infrastructure
{
// Arrange/Act
var jsRuntime = new TestJSRuntime();
var resultJson = DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, nameof(SomePublicType.InvokableMethodWithoutCustomIdentifier), default, null);
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, nameof(SomePublicType.InvokableMethodWithoutCustomIdentifier), default, default), null);
var result = JsonSerializer.Deserialize<TestDTO>(resultJson, jsRuntime.JsonSerializerOptions);
// Assert
@ -130,7 +130,7 @@ namespace Microsoft.JSInterop.Infrastructure
}, jsRuntime.JsonSerializerOptions);
// Act
var resultJson = DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, "InvocableStaticWithParams", default, argsJson);
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticWithParams", default, default), argsJson);
var result = JsonDocument.Parse(resultJson);
var root = result.RootElement;
@ -171,7 +171,7 @@ namespace Microsoft.JSInterop.Infrastructure
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() =>
DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, method, default, argsJson));
DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, method, default, default), argsJson));
Assert.Equal($"In call to '{method}', parameter of type '{nameof(TestDTO)}' at index 3 must be declared as type 'DotNetObjectRef<TestDTO>' to receive the incoming value.", ex.Message);
}
@ -185,7 +185,7 @@ namespace Microsoft.JSInterop.Infrastructure
jsRuntime.Invoke<object>("unimportant", objectRef);
// Act
var resultJson = DotNetDispatcher.Invoke(jsRuntime, null, "InvokableInstanceVoid", 1, null);
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, "InvokableInstanceVoid", 1, default), null);
// Assert
Assert.Null(resultJson);
@ -202,7 +202,7 @@ namespace Microsoft.JSInterop.Infrastructure
jsRuntime.Invoke<object>("unimportant", objectRef);
// Act
var resultJson = DotNetDispatcher.Invoke(jsRuntime, null, "BaseClassInvokableInstanceVoid", 1, null);
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, "BaseClassInvokableInstanceVoid", 1, default), null);
// Assert
Assert.Null(resultJson);
@ -219,7 +219,7 @@ namespace Microsoft.JSInterop.Infrastructure
jsRuntime.Invoke<object>("unimportant", objectRef);
// Act
DotNetDispatcher.BeginInvokeDotNet(jsRuntime, null, null, "__Dispose", objectRef.ObjectId, null);
DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(null, "__Dispose", objectRef.ObjectId, default), null);
// Assert
Assert.True(objectRef.Disposed);
@ -240,7 +240,7 @@ namespace Microsoft.JSInterop.Infrastructure
// Act/Assert
var ex = Assert.Throws<ArgumentException>(
() => DotNetDispatcher.Invoke(jsRuntime, null, "InvokableInstanceVoid", 1, null));
() => DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, "InvokableInstanceVoid", 1, default), null));
Assert.StartsWith("There is no tracked object with id '1'.", ex.Message);
}
@ -259,7 +259,7 @@ namespace Microsoft.JSInterop.Infrastructure
// Act/Assert
var ex = Assert.Throws<ArgumentException>(
() => DotNetDispatcher.Invoke(jsRuntime, null, "InvokableInstanceVoid", 1, null));
() => DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, "InvokableInstanceVoid", 1, default), null));
Assert.StartsWith("There is no tracked object with id '1'.", ex.Message);
}
@ -346,7 +346,7 @@ namespace Microsoft.JSInterop.Infrastructure
var argsJson = "[\"myvalue\",{\"__dotNetObject\":2}]";
// Act
var resultJson = DotNetDispatcher.Invoke(jsRuntime, null, "InvokableInstanceMethod", 1, argsJson);
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, "InvokableInstanceMethod", 1, default), argsJson);
// Assert
Assert.Equal("[\"You passed myvalue\",{\"__dotNetObject\":3}]", resultJson);
@ -369,7 +369,7 @@ namespace Microsoft.JSInterop.Infrastructure
// Act/Assert
var ex = Assert.Throws<ArgumentException>(() =>
{
DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, "InvocableStaticWithParams", default, argsJson);
DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticWithParams", default, default), argsJson);
});
Assert.Equal("The call to 'InvocableStaticWithParams' expects '3' parameters, but received '2'.", ex.Message);
@ -392,7 +392,7 @@ namespace Microsoft.JSInterop.Infrastructure
// Act/Assert
var ex = Assert.Throws<JsonException>(() =>
{
DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, "InvocableStaticWithParams", default, argsJson);
DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticWithParams", default, default), argsJson);
});
Assert.Equal("Unexpected JSON token Number. Ensure that the call to `InvocableStaticWithParams' is supplied with exactly '3' parameters.", ex.Message);
@ -419,13 +419,13 @@ namespace Microsoft.JSInterop.Infrastructure
// Act
var callId = "123";
var resultTask = jsRuntime.NextInvocationTask;
DotNetDispatcher.BeginInvokeDotNet(jsRuntime, callId, null, "InvokableAsyncMethod", 1, argsJson);
DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(null, "InvokableAsyncMethod", 1, callId), argsJson);
await resultTask;
// Assert: Correct completion information
Assert.Equal(callId, jsRuntime.LastCompletionCallId);
Assert.True(jsRuntime.LastCompletionStatus);
var result = Assert.IsType<object[]>(jsRuntime.LastCompletionResult);
Assert.True(jsRuntime.LastCompletionResult.Success);
var result = Assert.IsType<object[]>(jsRuntime.LastCompletionResult.Result);
var resultDto1 = Assert.IsType<TestDTO>(result[0]);
Assert.Equal("STRING VIA JSON", resultDto1.StringVal);
@ -447,18 +447,17 @@ namespace Microsoft.JSInterop.Infrastructure
// Act
var callId = "123";
var resultTask = jsRuntime.NextInvocationTask;
DotNetDispatcher.BeginInvokeDotNet(jsRuntime, callId, thisAssemblyName, nameof(ThrowingClass.ThrowingMethod), default, default);
DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, nameof(ThrowingClass.ThrowingMethod), default, callId), default);
await resultTask; // This won't throw, it sets properties on the jsRuntime.
// Assert
Assert.Equal(callId, jsRuntime.LastCompletionCallId);
Assert.False(jsRuntime.LastCompletionStatus); // Fails
Assert.False(jsRuntime.LastCompletionResult.Success); // Fails
// Make sure the method that threw the exception shows up in the call stack
// https://github.com/aspnet/AspNetCore/issues/8612
var exception = jsRuntime.LastCompletionResult is ExceptionDispatchInfo edi ? edi.SourceException.ToString() : null;
Assert.Contains(nameof(ThrowingClass.ThrowingMethod), exception);
Assert.Contains(nameof(ThrowingClass.ThrowingMethod), jsRuntime.LastCompletionResult.Exception.ToString());
}
[Fact]
@ -470,18 +469,17 @@ namespace Microsoft.JSInterop.Infrastructure
// Act
var callId = "123";
var resultTask = jsRuntime.NextInvocationTask;
DotNetDispatcher.BeginInvokeDotNet(jsRuntime, callId, thisAssemblyName, nameof(ThrowingClass.AsyncThrowingMethod), default, default);
DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, nameof(ThrowingClass.AsyncThrowingMethod), default, callId), default);
await resultTask; // This won't throw, it sets properties on the jsRuntime.
// Assert
Assert.Equal(callId, jsRuntime.LastCompletionCallId);
Assert.False(jsRuntime.LastCompletionStatus); // Fails
Assert.False(jsRuntime.LastCompletionResult.Success); // Fails
// Make sure the method that threw the exception shows up in the call stack
// https://github.com/aspnet/AspNetCore/issues/8612
var exception = jsRuntime.LastCompletionResult is ExceptionDispatchInfo edi ? edi.SourceException.ToString() : null;
Assert.Contains(nameof(ThrowingClass.AsyncThrowingMethod), exception);
Assert.Contains(nameof(ThrowingClass.AsyncThrowingMethod), jsRuntime.LastCompletionResult.Exception.ToString());
}
[Fact]
@ -491,15 +489,15 @@ namespace Microsoft.JSInterop.Infrastructure
var jsRuntime = new TestJSRuntime();
var callId = "123";
var resultTask = jsRuntime.NextInvocationTask;
DotNetDispatcher.BeginInvokeDotNet(jsRuntime, callId, thisAssemblyName, "InvocableStaticWithParams", default, "<xml>not json</xml>");
DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticWithParams", default, callId), "<xml>not json</xml>");
await resultTask; // This won't throw, it sets properties on the jsRuntime.
// Assert
Assert.Equal(callId, jsRuntime.LastCompletionCallId);
Assert.False(jsRuntime.LastCompletionStatus); // Fails
var result = Assert.IsType<ExceptionDispatchInfo>(jsRuntime.LastCompletionResult);
Assert.Contains("JsonReaderException: '<' is an invalid start of a value.", result.SourceException.ToString());
Assert.False(jsRuntime.LastCompletionResult.Success); // Fails
var exception = jsRuntime.LastCompletionResult.Exception;
Assert.Contains("JsonReaderException: '<' is an invalid start of a value.", exception.ToString());
}
[Fact]
@ -509,13 +507,13 @@ namespace Microsoft.JSInterop.Infrastructure
var jsRuntime = new TestJSRuntime();
var callId = "123";
var resultTask = jsRuntime.NextInvocationTask;
DotNetDispatcher.BeginInvokeDotNet(jsRuntime, callId, null, "InvokableInstanceVoid", 1, null);
DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(null, "InvokableInstanceVoid", 1, callId), null);
// Assert
Assert.Equal(callId, jsRuntime.LastCompletionCallId);
Assert.False(jsRuntime.LastCompletionStatus); // Fails
var result = Assert.IsType<ExceptionDispatchInfo>(jsRuntime.LastCompletionResult);
Assert.StartsWith("System.ArgumentException: There is no tracked object with id '1'. Perhaps the DotNetObjectReference instance was already disposed.", result.SourceException.ToString());
Assert.False(jsRuntime.LastCompletionResult.Success); // Fails
var exception = jsRuntime.LastCompletionResult.Exception;
Assert.StartsWith("System.ArgumentException: There is no tracked object with id '1'. Perhaps the DotNetObjectReference instance was already disposed.", exception.ToString());
}
[Theory]
@ -801,8 +799,7 @@ namespace Microsoft.JSInterop.Infrastructure
public string LastInvocationArgsJson { get; private set; }
public string LastCompletionCallId { get; private set; }
public bool LastCompletionStatus { get; private set; }
public object LastCompletionResult { get; private set; }
public DotNetInvocationResult LastCompletionResult { get; private set; }
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
{
@ -823,17 +820,10 @@ namespace Microsoft.JSInterop.Infrastructure
return null;
}
protected internal override void EndInvokeDotNet(
string callId,
bool success,
object resultOrError,
string assemblyName,
string methodIdentifier,
long dotNetObjectId)
protected internal override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult)
{
LastCompletionCallId = callId;
LastCompletionStatus = success;
LastCompletionResult = resultOrError;
LastCompletionCallId = invocationInfo.CallId;
LastCompletionResult = invocationResult;
_nextInvocationTcs.SetResult(null);
_nextInvocationTcs = new TaskCompletionSource<object>();
}

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.JSInterop.Infrastructure;
using Xunit;
namespace Microsoft.JSInterop
@ -113,8 +114,8 @@ namespace Microsoft.JSInterop
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
=> throw new NotImplementedException("This test only covers sync calls");
protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId) =>
throw new NotImplementedException("This test only covers sync calls");
protected internal override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult)
=> throw new NotImplementedException("This test only covers sync calls");
}
}
}

View File

@ -8,6 +8,7 @@ using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.JSInterop.Infrastructure;
using Xunit;
namespace Microsoft.JSInterop
@ -296,18 +297,20 @@ namespace Microsoft.JSInterop
var expectedMessage = "An error ocurred while invoking '[Assembly]::Method'. Swapping to 'Development' environment will " +
"display more detailed information about the error that occurred.";
string GetMessage(string assembly, string method) => $"An error ocurred while invoking '[{assembly}]::{method}'. Swapping to 'Development' environment will " +
string GetMessage(DotNetInvocationInfo info) => $"An error ocurred while invoking '[{info.AssemblyName}]::{info.MethodIdentifier}'. Swapping to 'Development' environment will " +
"display more detailed information about the error that occurred.";
var runtime = new TestJSRuntime()
{
OnDotNetException = (e, a, m) => new JSError { Message = GetMessage(a, m) }
OnDotNetException = (invocationInfo) => new JSError { Message = GetMessage(invocationInfo) }
};
var exception = new Exception("Some really sensitive data in here");
var invocation = new DotNetInvocationInfo("Assembly", "Method", 0, "0");
var result = new DotNetInvocationResult(exception, default);
// Act
runtime.EndInvokeDotNet("0", false, exception, "Assembly", "Method", 0);
runtime.EndInvokeDotNet(invocation, result);
// Assert
var call = runtime.EndInvokeDotNetCalls.Single();
@ -356,20 +359,21 @@ namespace Microsoft.JSInterop
public object ResultOrError { get; set; }
}
public Func<Exception, string, string, object> OnDotNetException { get; set; }
public Func<DotNetInvocationInfo, object> OnDotNetException { get; set; }
protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId)
protected internal override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult)
{
if (OnDotNetException != null && !success)
var resultOrError = invocationResult.Success ? invocationResult.Result : invocationResult.Exception;
if (OnDotNetException != null && !invocationResult.Success)
{
resultOrError = OnDotNetException(resultOrError as Exception, assemblyName, methodIdentifier);
resultOrError = OnDotNetException(invocationInfo);
}
EndInvokeDotNetCalls.Add(new EndInvokeDotNetArgs
{
CallId = callId,
Success = success,
ResultOrError = resultOrError
CallId = invocationInfo.CallId,
Success = invocationResult.Success,
ResultOrError = resultOrError,
});
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.JSInterop.Infrastructure;
namespace Microsoft.JSInterop
{
@ -12,7 +13,7 @@ namespace Microsoft.JSInterop
throw new NotImplementedException();
}
protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId)
protected internal override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult)
{
throw new NotImplementedException();
}

View File

@ -7,7 +7,7 @@ namespace Mono.WebAssembly.Interop
{
public MonoWebAssemblyJSRuntime() { }
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) { }
protected override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId) { }
protected override void EndInvokeDotNet(Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo callInfo, in Microsoft.JSInterop.Infrastructure.DotNetInvocationResult dispatchResult) { }
protected static void Initialize(Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime jsRuntime) { }
protected override string InvokeJS(string identifier, string argsJson) { throw null; }
public TRes InvokeUnmarshalled<TRes>(string identifier) { throw null; }

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.ExceptionServices;
using System.Text.Json;
using Microsoft.JSInterop;
using Microsoft.JSInterop.Infrastructure;
@ -53,7 +52,10 @@ namespace Mono.WebAssembly.Interop
// Invoked via Mono's JS interop mechanism (invoke_method)
private static string InvokeDotNet(string assemblyName, string methodIdentifier, string dotNetObjectId, string argsJson)
=> DotNetDispatcher.Invoke(Instance, assemblyName, methodIdentifier, dotNetObjectId == null ? default : long.Parse(dotNetObjectId), argsJson);
{
var callInfo = new DotNetInvocationInfo(assemblyName, methodIdentifier, dotNetObjectId == null ? default : long.Parse(dotNetObjectId), callId: null);
return DotNetDispatcher.Invoke(Instance, callInfo, argsJson);
}
// Invoked via Mono's JS interop mechanism (invoke_method)
private static void EndInvokeJS(string argsJson)
@ -78,32 +80,20 @@ namespace Mono.WebAssembly.Interop
assemblyName = assemblyNameOrDotNetObjectId;
}
DotNetDispatcher.BeginInvokeDotNet(Instance, callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson);
var callInfo = new DotNetInvocationInfo(assemblyName, methodIdentifier, dotNetObjectId, callId);
DotNetDispatcher.BeginInvokeDotNet(Instance, callInfo, argsJson);
}
protected override void EndInvokeDotNet(
string callId,
bool success,
object resultOrError,
string assemblyName,
string methodIdentifier,
long dotNetObjectId)
protected override void EndInvokeDotNet(DotNetInvocationInfo callInfo, in DotNetInvocationResult dispatchResult)
{
// For failures, the common case is to call EndInvokeDotNet with the Exception object.
// For these we'll serialize as something that's useful to receive on the JS side.
// If the value is not an Exception, we'll just rely on it being directly JSON-serializable.
if (!success && resultOrError is Exception ex)
{
resultOrError = ex.ToString();
}
else if (!success && resultOrError is ExceptionDispatchInfo edi)
{
resultOrError = edi.SourceException.ToString();
}
var resultOrError = dispatchResult.Success ? dispatchResult.Result : dispatchResult.Exception.ToString();
// We pass 0 as the async handle because we don't want the JS-side code to
// send back any notification (we're just providing a result for an existing async call)
var args = JsonSerializer.Serialize(new[] { callId, success, resultOrError }, JsonSerializerOptions);
var args = JsonSerializer.Serialize(new[] { callInfo.CallId, dispatchResult.Success, resultOrError }, JsonSerializerOptions);
BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", args);
}