API Review: JSRuntime (dotnet/extensions#2166)
* API Review: JSRuntime
* Rename JSRuntimeBase -> JSRuntime
* Rename JSInProcessRuntimeBase -> JSInProcessRuntime
* Rename DotNetObjectRef -> DotNetObjectReference
* Update JSRuntime to return ValueTask
* Make InvokeAsync APIs that explicitly cancels and API that default cancels more crisp
* Introduce void invoking APIs
* Fixup method names on DotNetDispatcher
\n\nCommit migrated from 93d3ae4485
This commit is contained in:
parent
edd5f54bc3
commit
9372816b7c
|
|
@ -3,19 +3,13 @@
|
||||||
|
|
||||||
namespace Microsoft.JSInterop
|
namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
public static partial class DotNetDispatcher
|
public static partial class DotNetObjectReference
|
||||||
{
|
{
|
||||||
public static void BeginInvoke(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { }
|
public static Microsoft.JSInterop.DotNetObjectReference<TValue> Create<TValue>(TValue value) where TValue : class { throw null; }
|
||||||
public static void EndInvoke(string arguments) { }
|
|
||||||
public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; }
|
|
||||||
}
|
}
|
||||||
public static partial class DotNetObjectRef
|
public sealed partial class DotNetObjectReference<TValue> : System.IDisposable where TValue : class
|
||||||
{
|
{
|
||||||
public static Microsoft.JSInterop.DotNetObjectRef<TValue> Create<TValue>(TValue value) where TValue : class { throw null; }
|
internal DotNetObjectReference() { }
|
||||||
}
|
|
||||||
public sealed partial class DotNetObjectRef<TValue> : System.IDisposable where TValue : class
|
|
||||||
{
|
|
||||||
internal DotNetObjectRef() { }
|
|
||||||
public TValue Value { get { throw null; } }
|
public TValue Value { get { throw null; } }
|
||||||
public void Dispose() { }
|
public void Dispose() { }
|
||||||
}
|
}
|
||||||
|
|
@ -25,38 +19,61 @@ namespace Microsoft.JSInterop
|
||||||
}
|
}
|
||||||
public partial interface IJSRuntime
|
public partial interface IJSRuntime
|
||||||
{
|
{
|
||||||
System.Threading.Tasks.Task<TValue> InvokeAsync<TValue>(string identifier, System.Collections.Generic.IEnumerable<object> args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args);
|
||||||
System.Threading.Tasks.Task<TValue> InvokeAsync<TValue>(string identifier, params object[] args);
|
System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, System.Threading.CancellationToken cancellationToken, object[] args);
|
||||||
}
|
}
|
||||||
public partial class JSException : System.Exception
|
public partial class JSException : System.Exception
|
||||||
{
|
{
|
||||||
public JSException(string message) { }
|
public JSException(string message) { }
|
||||||
public JSException(string message, System.Exception innerException) { }
|
public JSException(string message, System.Exception innerException) { }
|
||||||
}
|
}
|
||||||
public abstract partial class JSInProcessRuntimeBase : Microsoft.JSInterop.JSRuntimeBase, Microsoft.JSInterop.IJSInProcessRuntime, Microsoft.JSInterop.IJSRuntime
|
public abstract partial class JSInProcessRuntime : Microsoft.JSInterop.JSRuntime, Microsoft.JSInterop.IJSInProcessRuntime, Microsoft.JSInterop.IJSRuntime
|
||||||
{
|
{
|
||||||
protected JSInProcessRuntimeBase() { }
|
protected JSInProcessRuntime() { }
|
||||||
protected abstract string InvokeJS(string identifier, string argsJson);
|
protected abstract string InvokeJS(string identifier, string argsJson);
|
||||||
public TValue Invoke<TValue>(string identifier, params object[] args) { throw null; }
|
public TValue Invoke<TValue>(string identifier, params object[] args) { throw null; }
|
||||||
}
|
}
|
||||||
|
public static partial class JSInProcessRuntimeExtensions
|
||||||
|
{
|
||||||
|
public static void InvokeVoid(this Microsoft.JSInterop.IJSInProcessRuntime jsRuntime, string identifier, params object[] args) { }
|
||||||
|
}
|
||||||
[System.AttributeUsageAttribute(System.AttributeTargets.Method, AllowMultiple=true)]
|
[System.AttributeUsageAttribute(System.AttributeTargets.Method, AllowMultiple=true)]
|
||||||
public partial class JSInvokableAttribute : System.Attribute
|
public sealed partial class JSInvokableAttribute : System.Attribute
|
||||||
{
|
{
|
||||||
public JSInvokableAttribute() { }
|
public JSInvokableAttribute() { }
|
||||||
public JSInvokableAttribute(string identifier) { }
|
public JSInvokableAttribute(string identifier) { }
|
||||||
public string Identifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
public string Identifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||||
}
|
}
|
||||||
public static partial class JSRuntime
|
public abstract partial class JSRuntime : Microsoft.JSInterop.IJSRuntime
|
||||||
{
|
{
|
||||||
public static void SetCurrentJSRuntime(Microsoft.JSInterop.IJSRuntime instance) { }
|
protected JSRuntime() { }
|
||||||
}
|
|
||||||
public abstract partial class JSRuntimeBase : Microsoft.JSInterop.IJSRuntime
|
|
||||||
{
|
|
||||||
protected JSRuntimeBase() { }
|
|
||||||
protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||||
protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson);
|
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(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId);
|
||||||
public System.Threading.Tasks.Task<T> InvokeAsync<T>(string identifier, System.Collections.Generic.IEnumerable<object> args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
|
public System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args) { throw null; }
|
||||||
public System.Threading.Tasks.Task<T> InvokeAsync<T>(string identifier, params object[] args) { throw null; }
|
public System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, System.Threading.CancellationToken cancellationToken, object[] args) { throw null; }
|
||||||
|
public static void SetCurrentJSRuntime(Microsoft.JSInterop.IJSRuntime instance) { }
|
||||||
|
}
|
||||||
|
public static partial class JSRuntimeExtensions
|
||||||
|
{
|
||||||
|
public static System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, params object[] args) { throw null; }
|
||||||
|
public static System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.Threading.CancellationToken cancellationToken, params object[] args) { throw null; }
|
||||||
|
[System.Diagnostics.DebuggerStepThroughAttribute]
|
||||||
|
public static System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.TimeSpan timeout, params object[] args) { throw null; }
|
||||||
|
[System.Diagnostics.DebuggerStepThroughAttribute]
|
||||||
|
public static System.Threading.Tasks.ValueTask InvokeVoidAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, params object[] args) { throw null; }
|
||||||
|
[System.Diagnostics.DebuggerStepThroughAttribute]
|
||||||
|
public static System.Threading.Tasks.ValueTask InvokeVoidAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.Threading.CancellationToken cancellationToken, params object[] args) { throw null; }
|
||||||
|
[System.Diagnostics.DebuggerStepThroughAttribute]
|
||||||
|
public static System.Threading.Tasks.ValueTask InvokeVoidAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.TimeSpan timeout, params object[] args) { throw null; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
namespace Microsoft.JSInterop.Infrastructure
|
||||||
|
{
|
||||||
|
public static partial class DotNetDispatcher
|
||||||
|
{
|
||||||
|
public static void BeginInvokeDotNet(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { }
|
||||||
|
public static void EndInvokeJS(string arguments) { }
|
||||||
|
public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,13 @@
|
||||||
|
|
||||||
namespace Microsoft.JSInterop
|
namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
public static partial class DotNetDispatcher
|
public static partial class DotNetObjectReference
|
||||||
{
|
{
|
||||||
public static void BeginInvoke(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { }
|
public static Microsoft.JSInterop.DotNetObjectReference<TValue> Create<TValue>(TValue value) where TValue : class { throw null; }
|
||||||
public static void EndInvoke(string arguments) { }
|
|
||||||
public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; }
|
|
||||||
}
|
}
|
||||||
public static partial class DotNetObjectRef
|
public sealed partial class DotNetObjectReference<TValue> : System.IDisposable where TValue : class
|
||||||
{
|
{
|
||||||
public static Microsoft.JSInterop.DotNetObjectRef<TValue> Create<TValue>(TValue value) where TValue : class { throw null; }
|
internal DotNetObjectReference() { }
|
||||||
}
|
|
||||||
public sealed partial class DotNetObjectRef<TValue> : System.IDisposable where TValue : class
|
|
||||||
{
|
|
||||||
internal DotNetObjectRef() { }
|
|
||||||
public TValue Value { get { throw null; } }
|
public TValue Value { get { throw null; } }
|
||||||
public void Dispose() { }
|
public void Dispose() { }
|
||||||
}
|
}
|
||||||
|
|
@ -25,38 +19,61 @@ namespace Microsoft.JSInterop
|
||||||
}
|
}
|
||||||
public partial interface IJSRuntime
|
public partial interface IJSRuntime
|
||||||
{
|
{
|
||||||
System.Threading.Tasks.Task<TValue> InvokeAsync<TValue>(string identifier, System.Collections.Generic.IEnumerable<object> args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args);
|
||||||
System.Threading.Tasks.Task<TValue> InvokeAsync<TValue>(string identifier, params object[] args);
|
System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, System.Threading.CancellationToken cancellationToken, object[] args);
|
||||||
}
|
}
|
||||||
public partial class JSException : System.Exception
|
public partial class JSException : System.Exception
|
||||||
{
|
{
|
||||||
public JSException(string message) { }
|
public JSException(string message) { }
|
||||||
public JSException(string message, System.Exception innerException) { }
|
public JSException(string message, System.Exception innerException) { }
|
||||||
}
|
}
|
||||||
public abstract partial class JSInProcessRuntimeBase : Microsoft.JSInterop.JSRuntimeBase, Microsoft.JSInterop.IJSInProcessRuntime, Microsoft.JSInterop.IJSRuntime
|
public abstract partial class JSInProcessRuntime : Microsoft.JSInterop.JSRuntime, Microsoft.JSInterop.IJSInProcessRuntime, Microsoft.JSInterop.IJSRuntime
|
||||||
{
|
{
|
||||||
protected JSInProcessRuntimeBase() { }
|
protected JSInProcessRuntime() { }
|
||||||
protected abstract string InvokeJS(string identifier, string argsJson);
|
protected abstract string InvokeJS(string identifier, string argsJson);
|
||||||
public TValue Invoke<TValue>(string identifier, params object[] args) { throw null; }
|
public TValue Invoke<TValue>(string identifier, params object[] args) { throw null; }
|
||||||
}
|
}
|
||||||
|
public static partial class JSInProcessRuntimeExtensions
|
||||||
|
{
|
||||||
|
public static void InvokeVoid(this Microsoft.JSInterop.IJSInProcessRuntime jsRuntime, string identifier, params object[] args) { }
|
||||||
|
}
|
||||||
[System.AttributeUsageAttribute(System.AttributeTargets.Method, AllowMultiple=true)]
|
[System.AttributeUsageAttribute(System.AttributeTargets.Method, AllowMultiple=true)]
|
||||||
public partial class JSInvokableAttribute : System.Attribute
|
public sealed partial class JSInvokableAttribute : System.Attribute
|
||||||
{
|
{
|
||||||
public JSInvokableAttribute() { }
|
public JSInvokableAttribute() { }
|
||||||
public JSInvokableAttribute(string identifier) { }
|
public JSInvokableAttribute(string identifier) { }
|
||||||
public string Identifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
public string Identifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||||
}
|
}
|
||||||
public static partial class JSRuntime
|
public abstract partial class JSRuntime : Microsoft.JSInterop.IJSRuntime
|
||||||
{
|
{
|
||||||
public static void SetCurrentJSRuntime(Microsoft.JSInterop.IJSRuntime instance) { }
|
protected JSRuntime() { }
|
||||||
}
|
|
||||||
public abstract partial class JSRuntimeBase : Microsoft.JSInterop.IJSRuntime
|
|
||||||
{
|
|
||||||
protected JSRuntimeBase() { }
|
|
||||||
protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||||
protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson);
|
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(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId);
|
||||||
public System.Threading.Tasks.Task<T> InvokeAsync<T>(string identifier, System.Collections.Generic.IEnumerable<object> args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
|
public System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args) { throw null; }
|
||||||
public System.Threading.Tasks.Task<T> InvokeAsync<T>(string identifier, params object[] args) { throw null; }
|
public System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, System.Threading.CancellationToken cancellationToken, object[] args) { throw null; }
|
||||||
|
public static void SetCurrentJSRuntime(Microsoft.JSInterop.IJSRuntime instance) { }
|
||||||
|
}
|
||||||
|
public static partial class JSRuntimeExtensions
|
||||||
|
{
|
||||||
|
public static System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, params object[] args) { throw null; }
|
||||||
|
public static System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.Threading.CancellationToken cancellationToken, params object[] args) { throw null; }
|
||||||
|
[System.Diagnostics.DebuggerStepThroughAttribute]
|
||||||
|
public static System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.TimeSpan timeout, params object[] args) { throw null; }
|
||||||
|
[System.Diagnostics.DebuggerStepThroughAttribute]
|
||||||
|
public static System.Threading.Tasks.ValueTask InvokeVoidAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, params object[] args) { throw null; }
|
||||||
|
[System.Diagnostics.DebuggerStepThroughAttribute]
|
||||||
|
public static System.Threading.Tasks.ValueTask InvokeVoidAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.Threading.CancellationToken cancellationToken, params object[] args) { throw null; }
|
||||||
|
[System.Diagnostics.DebuggerStepThroughAttribute]
|
||||||
|
public static System.Threading.Tasks.ValueTask InvokeVoidAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.TimeSpan timeout, params object[] args) { throw null; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
namespace Microsoft.JSInterop.Infrastructure
|
||||||
|
{
|
||||||
|
public static partial class DotNetDispatcher
|
||||||
|
{
|
||||||
|
public static void BeginInvokeDotNet(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { }
|
||||||
|
public static void EndInvokeJS(string arguments) { }
|
||||||
|
public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,23 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.JSInterop.Infrastructure;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop
|
namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides convenience methods to produce a <see cref="DotNetObjectRef{TValue}" />.
|
/// Provides convenience methods to produce a <see cref="DotNetObjectReference{TValue}" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class DotNetObjectRef
|
public static class DotNetObjectReference
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of <see cref="DotNetObjectRef{TValue}" />.
|
/// Creates a new instance of <see cref="DotNetObjectReference{TValue}" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">The reference type to track.</param>
|
/// <param name="value">The reference type to track.</param>
|
||||||
/// <returns>An instance of <see cref="DotNetObjectRef{TValue}" />.</returns>
|
/// <returns>An instance of <see cref="DotNetObjectReference{TValue}" />.</returns>
|
||||||
public static DotNetObjectRef<TValue> Create<TValue>(TValue value) where TValue : class
|
public static DotNetObjectReference<TValue> Create<TValue>(TValue value) where TValue : class
|
||||||
{
|
{
|
||||||
return new DotNetObjectRef<TValue>(DotNetObjectRefManager.Current, value);
|
return new DotNetObjectReference<TValue>(DotNetObjectReferenceManager.Current, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using Microsoft.JSInterop.Infrastructure;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop
|
namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
|
|
@ -14,31 +15,24 @@ namespace Microsoft.JSInterop
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TValue">The type of the value to wrap.</typeparam>
|
/// <typeparam name="TValue">The type of the value to wrap.</typeparam>
|
||||||
[JsonConverter(typeof(DotNetObjectReferenceJsonConverterFactory))]
|
[JsonConverter(typeof(DotNetObjectReferenceJsonConverterFactory))]
|
||||||
public sealed class DotNetObjectRef<TValue> : IDotNetObjectRef, IDisposable where TValue : class
|
public sealed class DotNetObjectReference<TValue> : IDotNetObjectReference, IDisposable where TValue : class
|
||||||
{
|
{
|
||||||
private readonly DotNetObjectRefManager _referenceManager;
|
private readonly DotNetObjectReferenceManager _referenceManager;
|
||||||
private readonly TValue _value;
|
private readonly TValue _value;
|
||||||
private readonly long _objectId;
|
private readonly long _objectId;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of <see cref="DotNetObjectRef{TValue}" />.
|
/// Initializes a new instance of <see cref="DotNetObjectReference{TValue}" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="referenceManager"></param>
|
/// <param name="referenceManager"></param>
|
||||||
/// <param name="value">The value to pass by reference.</param>
|
/// <param name="value">The value to pass by reference.</param>
|
||||||
internal DotNetObjectRef(DotNetObjectRefManager referenceManager, TValue value)
|
internal DotNetObjectReference(DotNetObjectReferenceManager referenceManager, TValue value)
|
||||||
{
|
{
|
||||||
_referenceManager = referenceManager;
|
_referenceManager = referenceManager;
|
||||||
_objectId = _referenceManager.TrackObject(this);
|
_objectId = _referenceManager.TrackObject(this);
|
||||||
_value = value;
|
_value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal DotNetObjectRef(DotNetObjectRefManager referenceManager, long objectId, TValue value)
|
|
||||||
{
|
|
||||||
_referenceManager = referenceManager;
|
|
||||||
_objectId = objectId;
|
|
||||||
_value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the object instance represented by this wrapper.
|
/// Gets the object instance represented by this wrapper.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -60,7 +54,7 @@ namespace Microsoft.JSInterop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object IDotNetObjectRef.Value => Value;
|
object IDotNetObjectReference.Value => Value;
|
||||||
|
|
||||||
internal bool Disposed { get; private set; }
|
internal bool Disposed { get; private set; }
|
||||||
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
|
@ -14,21 +13,28 @@ namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invokes the specified JavaScript function asynchronously.
|
/// Invokes the specified JavaScript function asynchronously.
|
||||||
|
/// <para>
|
||||||
|
/// <see cref="JSRuntime"/> will apply timeouts to this operation based on the value configured in <see cref="JSRuntime.DefaultAsyncTimeout"/>. To dispatch a call with a different timeout, or no timeout,
|
||||||
|
/// consider using <see cref="InvokeAsync{TValue}(string, CancellationToken, object[])" />.
|
||||||
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TValue">The JSON-serializable return type.</typeparam>
|
/// <typeparam name="TValue">The JSON-serializable return type.</typeparam>
|
||||||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
||||||
/// <param name="args">JSON-serializable arguments.</param>
|
/// <param name="args">JSON-serializable arguments.</param>
|
||||||
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
|
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
|
||||||
Task<TValue> InvokeAsync<TValue>(string identifier, params object[] args);
|
ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invokes the specified JavaScript function asynchronously.
|
/// Invokes the specified JavaScript function asynchronously.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TValue">The JSON-serializable return type.</typeparam>
|
/// <typeparam name="TValue">The JSON-serializable return type.</typeparam>
|
||||||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
||||||
|
/// <param name="cancellationToken">
|
||||||
|
/// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts
|
||||||
|
/// (<see cref="JSRuntime.DefaultAsyncTimeout"/>) from being applied.
|
||||||
|
/// </param>
|
||||||
/// <param name="args">JSON-serializable arguments.</param>
|
/// <param name="args">JSON-serializable arguments.</param>
|
||||||
/// <param name="cancellationToken">A cancellation token to signal the cancellation of the operation.</param>
|
|
||||||
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
|
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
|
||||||
Task<TValue> InvokeAsync<TValue>(string identifier, IEnumerable<object> args, CancellationToken cancellationToken = default);
|
ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken, object[] args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop
|
namespace Microsoft.JSInterop.Infrastructure
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides methods that receive incoming calls from JS to .NET.
|
/// Provides methods that receive incoming calls from JS to .NET.
|
||||||
|
|
@ -39,10 +39,10 @@ namespace Microsoft.JSInterop
|
||||||
// the targeted method has [JSInvokable]. It is not itself subject to that restriction,
|
// the targeted method has [JSInvokable]. It is not itself subject to that restriction,
|
||||||
// because there would be nobody to police that. This method *is* the police.
|
// because there would be nobody to police that. This method *is* the police.
|
||||||
|
|
||||||
IDotNetObjectRef targetInstance = default;
|
IDotNetObjectReference targetInstance = default;
|
||||||
if (dotNetObjectId != default)
|
if (dotNetObjectId != default)
|
||||||
{
|
{
|
||||||
targetInstance = DotNetObjectRefManager.Current.FindDotNetObject(dotNetObjectId);
|
targetInstance = DotNetObjectReferenceManager.Current.FindDotNetObject(dotNetObjectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
var syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson);
|
var syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson);
|
||||||
|
|
@ -63,7 +63,7 @@ namespace Microsoft.JSInterop
|
||||||
/// <param name="dotNetObjectId">For instance method calls, identifies the target object.</param>
|
/// <param name="dotNetObjectId">For instance method calls, identifies the target object.</param>
|
||||||
/// <param name="argsJson">A JSON representation of the parameters.</param>
|
/// <param name="argsJson">A JSON representation of the parameters.</param>
|
||||||
/// <returns>A JSON representation of the return value, or null.</returns>
|
/// <returns>A JSON representation of the return value, or null.</returns>
|
||||||
public static void BeginInvoke(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson)
|
public static void BeginInvokeDotNet(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson)
|
||||||
{
|
{
|
||||||
// This method doesn't need [JSInvokable] because the platform is responsible for having
|
// This method doesn't need [JSInvokable] because the platform is responsible for having
|
||||||
// some way to dispatch calls here. The logic inside here is the thing that checks whether
|
// some way to dispatch calls here. The logic inside here is the thing that checks whether
|
||||||
|
|
@ -73,19 +73,19 @@ namespace Microsoft.JSInterop
|
||||||
// DotNetDispatcher only works with JSRuntimeBase instances.
|
// DotNetDispatcher only works with JSRuntimeBase instances.
|
||||||
// If the developer wants to use a totally custom IJSRuntime, then their JS-side
|
// 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.
|
// code has to implement its own way of returning async results.
|
||||||
var jsRuntimeBaseInstance = (JSRuntimeBase)JSRuntime.Current;
|
var jsRuntimeBaseInstance = (JSRuntime)JSRuntime.Current;
|
||||||
|
|
||||||
// Using ExceptionDispatchInfo here throughout because we want to always preserve
|
// Using ExceptionDispatchInfo here throughout because we want to always preserve
|
||||||
// original stack traces.
|
// original stack traces.
|
||||||
object syncResult = null;
|
object syncResult = null;
|
||||||
ExceptionDispatchInfo syncException = null;
|
ExceptionDispatchInfo syncException = null;
|
||||||
IDotNetObjectRef targetInstance = null;
|
IDotNetObjectReference targetInstance = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (dotNetObjectId != default)
|
if (dotNetObjectId != default)
|
||||||
{
|
{
|
||||||
targetInstance = DotNetObjectRefManager.Current.FindDotNetObject(dotNetObjectId);
|
targetInstance = DotNetObjectReferenceManager.Current.FindDotNetObject(dotNetObjectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson);
|
syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson);
|
||||||
|
|
@ -128,7 +128,7 @@ namespace Microsoft.JSInterop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object InvokeSynchronously(string assemblyName, string methodIdentifier, IDotNetObjectRef objectReference, string argsJson)
|
private static object InvokeSynchronously(string assemblyName, string methodIdentifier, IDotNetObjectReference objectReference, string argsJson)
|
||||||
{
|
{
|
||||||
AssemblyKey assemblyKey;
|
AssemblyKey assemblyKey;
|
||||||
if (objectReference is null)
|
if (objectReference is null)
|
||||||
|
|
@ -227,7 +227,7 @@ namespace Microsoft.JSInterop
|
||||||
jsonReader.ValueTextEquals(DotNetObjectRefKey.EncodedUtf8Bytes))
|
jsonReader.ValueTextEquals(DotNetObjectRefKey.EncodedUtf8Bytes))
|
||||||
{
|
{
|
||||||
// The JSON payload has the shape we expect from a DotNetObjectRef instance.
|
// The JSON payload has the shape we expect from a DotNetObjectRef instance.
|
||||||
return !parameterType.IsGenericType || parameterType.GetGenericTypeDefinition() != typeof(DotNetObjectRef<>);
|
return !parameterType.IsGenericType || parameterType.GetGenericTypeDefinition() != typeof(DotNetObjectReference<>);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -239,9 +239,9 @@ namespace Microsoft.JSInterop
|
||||||
/// associated <see cref="Task"/> as completed.
|
/// associated <see cref="Task"/> as completed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// All exceptions from <see cref="EndInvoke"/> are caught
|
/// All exceptions from <see cref="EndInvokeJS"/> are caught
|
||||||
/// are delivered via JS interop to the JavaScript side when it requests confirmation, as
|
/// are delivered via JS interop to the JavaScript side when it requests confirmation, as
|
||||||
/// the mechanism to call <see cref="EndInvoke"/> relies on
|
/// the mechanism to call <see cref="EndInvokeJS"/> relies on
|
||||||
/// using JS->.NET interop. This overload is meant for directly triggering completion callbacks
|
/// using JS->.NET interop. This overload is meant for directly triggering completion callbacks
|
||||||
/// for .NET -> JS operations without going through JS interop, so the callsite for this
|
/// for .NET -> JS operations without going through JS interop, so the callsite for this
|
||||||
/// method is responsible for handling any possible exception generated from the arguments
|
/// method is responsible for handling any possible exception generated from the arguments
|
||||||
|
|
@ -252,13 +252,13 @@ namespace Microsoft.JSInterop
|
||||||
/// This method can throw any exception either from the argument received or as a result
|
/// This method can throw any exception either from the argument received or as a result
|
||||||
/// of executing any callback synchronously upon completion.
|
/// of executing any callback synchronously upon completion.
|
||||||
/// </exception>
|
/// </exception>
|
||||||
public static void EndInvoke(string arguments)
|
public static void EndInvokeJS(string arguments)
|
||||||
{
|
{
|
||||||
var jsRuntimeBase = (JSRuntimeBase)JSRuntime.Current;
|
var jsRuntimeBase = (JSRuntime)JSRuntime.Current;
|
||||||
ParseEndInvokeArguments(jsRuntimeBase, arguments);
|
ParseEndInvokeArguments(jsRuntimeBase, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void ParseEndInvokeArguments(JSRuntimeBase jsRuntimeBase, string arguments)
|
internal static void ParseEndInvokeArguments(JSRuntime jsRuntimeBase, string arguments)
|
||||||
{
|
{
|
||||||
var utf8JsonBytes = Encoding.UTF8.GetBytes(arguments);
|
var utf8JsonBytes = Encoding.UTF8.GetBytes(arguments);
|
||||||
|
|
||||||
|
|
@ -5,13 +5,13 @@ using System;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop
|
namespace Microsoft.JSInterop.Infrastructure
|
||||||
{
|
{
|
||||||
internal sealed class DotNetObjectReferenceJsonConverter<TValue> : JsonConverter<DotNetObjectRef<TValue>> where TValue : class
|
internal sealed class DotNetObjectReferenceJsonConverter<TValue> : JsonConverter<DotNetObjectReference<TValue>> where TValue : class
|
||||||
{
|
{
|
||||||
private static JsonEncodedText DotNetObjectRefKey => DotNetDispatcher.DotNetObjectRefKey;
|
private static JsonEncodedText DotNetObjectRefKey => DotNetDispatcher.DotNetObjectRefKey;
|
||||||
|
|
||||||
public override DotNetObjectRef<TValue> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
public override DotNetObjectReference<TValue> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
long dotNetObjectId = 0;
|
long dotNetObjectId = 0;
|
||||||
|
|
||||||
|
|
@ -40,11 +40,11 @@ namespace Microsoft.JSInterop
|
||||||
throw new JsonException($"Required property {DotNetObjectRefKey} not found.");
|
throw new JsonException($"Required property {DotNetObjectRefKey} not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var referenceManager = DotNetObjectRefManager.Current;
|
var referenceManager = DotNetObjectReferenceManager.Current;
|
||||||
return (DotNetObjectRef<TValue>)referenceManager.FindDotNetObject(dotNetObjectId);
|
return (DotNetObjectReference<TValue>)referenceManager.FindDotNetObject(dotNetObjectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, DotNetObjectRef<TValue> value, JsonSerializerOptions options)
|
public override void Write(Utf8JsonWriter writer, DotNetObjectReference<TValue> value, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
writer.WriteStartObject();
|
writer.WriteStartObject();
|
||||||
writer.WriteNumber(DotNetObjectRefKey, value.ObjectId);
|
writer.WriteNumber(DotNetObjectRefKey, value.ObjectId);
|
||||||
|
|
@ -5,13 +5,13 @@ using System;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop
|
namespace Microsoft.JSInterop.Infrastructure
|
||||||
{
|
{
|
||||||
internal sealed class DotNetObjectReferenceJsonConverterFactory : JsonConverterFactory
|
internal sealed class DotNetObjectReferenceJsonConverterFactory : JsonConverterFactory
|
||||||
{
|
{
|
||||||
public override bool CanConvert(Type typeToConvert)
|
public override bool CanConvert(Type typeToConvert)
|
||||||
{
|
{
|
||||||
return typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(DotNetObjectRef<>);
|
return typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(DotNetObjectReference<>);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions jsonSerializerOptions)
|
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions jsonSerializerOptions)
|
||||||
|
|
@ -5,27 +5,27 @@ using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop
|
namespace Microsoft.JSInterop.Infrastructure
|
||||||
{
|
{
|
||||||
internal class DotNetObjectRefManager
|
internal class DotNetObjectReferenceManager
|
||||||
{
|
{
|
||||||
private long _nextId = 0; // 0 signals no object, but we increment prior to assignment. The first tracked object should have id 1
|
private long _nextId = 0; // 0 signals no object, but we increment prior to assignment. The first tracked object should have id 1
|
||||||
private readonly ConcurrentDictionary<long, IDotNetObjectRef> _trackedRefsById = new ConcurrentDictionary<long, IDotNetObjectRef>();
|
private readonly ConcurrentDictionary<long, IDotNetObjectReference> _trackedRefsById = new ConcurrentDictionary<long, IDotNetObjectReference>();
|
||||||
|
|
||||||
public static DotNetObjectRefManager Current
|
public static DotNetObjectReferenceManager Current
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!(JSRuntime.Current is JSRuntimeBase jsRuntimeBase))
|
if (!(JSRuntime.Current is JSRuntime jsRuntime))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("JSRuntime must be set up correctly and must be an instance of JSRuntimeBase to use DotNetObjectRef.");
|
throw new InvalidOperationException("JSRuntime must be set up correctly and must be an instance of JSRuntimeBase to use DotNetObjectReference.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsRuntimeBase.ObjectRefManager;
|
return jsRuntime.ObjectRefManager;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long TrackObject(IDotNetObjectRef dotNetObjectRef)
|
public long TrackObject(IDotNetObjectReference dotNetObjectRef)
|
||||||
{
|
{
|
||||||
var dotNetObjectId = Interlocked.Increment(ref _nextId);
|
var dotNetObjectId = Interlocked.Increment(ref _nextId);
|
||||||
_trackedRefsById[dotNetObjectId] = dotNetObjectRef;
|
_trackedRefsById[dotNetObjectId] = dotNetObjectRef;
|
||||||
|
|
@ -33,7 +33,7 @@ namespace Microsoft.JSInterop
|
||||||
return dotNetObjectId;
|
return dotNetObjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDotNetObjectRef FindDotNetObject(long dotNetObjectId)
|
public IDotNetObjectReference FindDotNetObject(long dotNetObjectId)
|
||||||
{
|
{
|
||||||
return _trackedRefsById.TryGetValue(dotNetObjectId, out var dotNetObjectRef)
|
return _trackedRefsById.TryGetValue(dotNetObjectId, out var dotNetObjectRef)
|
||||||
? dotNetObjectRef
|
? dotNetObjectRef
|
||||||
|
|
@ -45,7 +45,7 @@ namespace Microsoft.JSInterop
|
||||||
/// Stops tracking the specified .NET object reference.
|
/// Stops tracking the specified .NET object reference.
|
||||||
/// This may be invoked either by disposing a DotNetObjectRef in .NET code, or via JS interop by calling "dispose" on the corresponding instance in JavaScript code
|
/// This may be invoked either by disposing a DotNetObjectRef in .NET code, or via JS interop by calling "dispose" on the corresponding instance in JavaScript code
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dotNetObjectId">The ID of the <see cref="DotNetObjectRef{TValue}"/>.</param>
|
/// <param name="dotNetObjectId">The ID of the <see cref="DotNetObjectReference{TValue}"/>.</param>
|
||||||
public void ReleaseDotNetObject(long dotNetObjectId) => _trackedRefsById.TryRemove(dotNetObjectId, out _);
|
public void ReleaseDotNetObject(long dotNetObjectId) => _trackedRefsById.TryRemove(dotNetObjectId, out _);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,9 +3,9 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop
|
namespace Microsoft.JSInterop.Infrastructure
|
||||||
{
|
{
|
||||||
internal interface IDotNetObjectRef : IDisposable
|
internal interface IDotNetObjectReference : IDisposable
|
||||||
{
|
{
|
||||||
object Value { get; }
|
object Value { get; }
|
||||||
}
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ using System.Collections.Concurrent;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop
|
namespace Microsoft.JSInterop.Infrastructure
|
||||||
{
|
{
|
||||||
internal static class TaskGenericsUtil
|
internal static class TaskGenericsUtil
|
||||||
{
|
{
|
||||||
|
|
@ -8,7 +8,7 @@ namespace Microsoft.JSInterop
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Abstract base class for an in-process JavaScript runtime.
|
/// Abstract base class for an in-process JavaScript runtime.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class JSInProcessRuntimeBase : JSRuntimeBase, IJSInProcessRuntime
|
public abstract class JSInProcessRuntime : JSRuntime, IJSInProcessRuntime
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invokes the specified JavaScript function synchronously.
|
/// Invokes the specified JavaScript function synchronously.
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.JSInterop
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extensions for <see cref="IJSInProcessRuntime"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class JSInProcessRuntimeExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the specified JavaScript function synchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsRuntime">The <see cref="IJSInProcessRuntime"/>.</param>
|
||||||
|
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
||||||
|
/// <param name="args">JSON-serializable arguments.</param>
|
||||||
|
public static void InvokeVoid(this IJSInProcessRuntime jsRuntime, string identifier, params object[] args)
|
||||||
|
{
|
||||||
|
if (jsRuntime == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(jsRuntime));
|
||||||
|
}
|
||||||
|
|
||||||
|
jsRuntime.Invoke<object>(identifier, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,7 @@ namespace Microsoft.JSInterop
|
||||||
/// from untrusted callers. All inputs should be validated carefully.
|
/// from untrusted callers. All inputs should be validated carefully.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
|
||||||
public class JSInvokableAttribute : Attribute
|
public sealed class JSInvokableAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the identifier for the method. The identifier must be unique within the scope
|
/// Gets the identifier for the method. The identifier must be unique within the scope
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,38 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.JSInterop.Infrastructure;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop
|
namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides mechanisms for accessing the current <see cref="IJSRuntime"/>.
|
/// Abstract base class for a JavaScript runtime.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class JSRuntime
|
public abstract partial class JSRuntime : IJSRuntime
|
||||||
{
|
{
|
||||||
private static readonly AsyncLocal<IJSRuntime> _currentJSRuntime = new AsyncLocal<IJSRuntime>();
|
private static readonly AsyncLocal<IJSRuntime> _currentJSRuntime = new AsyncLocal<IJSRuntime>();
|
||||||
|
|
||||||
internal static IJSRuntime Current => _currentJSRuntime.Value;
|
internal static IJSRuntime Current => _currentJSRuntime.Value;
|
||||||
|
|
||||||
|
private long _nextPendingTaskId = 1; // Start at 1 because zero signals "no response needed"
|
||||||
|
private readonly ConcurrentDictionary<long, object> _pendingTasks
|
||||||
|
= new ConcurrentDictionary<long, object>();
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<long, CancellationTokenRegistration> _cancellationRegistrations =
|
||||||
|
new ConcurrentDictionary<long, CancellationTokenRegistration>();
|
||||||
|
|
||||||
|
internal DotNetObjectReferenceManager ObjectRefManager { get; } = new DotNetObjectReferenceManager();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the default timeout for asynchronous JavaScript calls.
|
||||||
|
/// </summary>
|
||||||
|
protected TimeSpan? DefaultAsyncTimeout { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the current JS runtime to the supplied instance.
|
/// Sets the current JS runtime to the supplied instance.
|
||||||
///
|
///
|
||||||
|
|
@ -26,5 +45,151 @@ namespace Microsoft.JSInterop
|
||||||
_currentJSRuntime.Value = instance
|
_currentJSRuntime.Value = instance
|
||||||
?? throw new ArgumentNullException(nameof(instance));
|
?? throw new ArgumentNullException(nameof(instance));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the specified JavaScript function asynchronously.
|
||||||
|
/// <para>
|
||||||
|
/// <see cref="JSRuntime"/> will apply timeouts to this operation based on the value configured in <see cref="DefaultAsyncTimeout"/>. To dispatch a call with a different, or no timeout,
|
||||||
|
/// consider using <see cref="InvokeAsync{TValue}(string, CancellationToken, object[])" />.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TValue">The JSON-serializable return type.</typeparam>
|
||||||
|
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
||||||
|
/// <param name="args">JSON-serializable arguments.</param>
|
||||||
|
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
|
||||||
|
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args)
|
||||||
|
{
|
||||||
|
if (DefaultAsyncTimeout.HasValue)
|
||||||
|
{
|
||||||
|
return InvokeWithDefaultCancellation<TValue>(identifier, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return InvokeAsync<TValue>(identifier, CancellationToken.None, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the specified JavaScript function asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TValue">The JSON-serializable return type.</typeparam>
|
||||||
|
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
||||||
|
/// <param name="cancellationToken">
|
||||||
|
/// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts
|
||||||
|
/// (<see cref="JSRuntime.DefaultAsyncTimeout"/>) from being applied.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="args">JSON-serializable arguments.</param>
|
||||||
|
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
|
||||||
|
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken, object[] args)
|
||||||
|
{
|
||||||
|
var taskId = Interlocked.Increment(ref _nextPendingTaskId);
|
||||||
|
var tcs = new TaskCompletionSource<TValue>(TaskContinuationOptions.RunContinuationsAsynchronously);
|
||||||
|
if (cancellationToken != default)
|
||||||
|
{
|
||||||
|
_cancellationRegistrations[taskId] = cancellationToken.Register(() =>
|
||||||
|
{
|
||||||
|
tcs.TrySetCanceled(cancellationToken);
|
||||||
|
CleanupTasksAndRegistrations(taskId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_pendingTasks[taskId] = tcs;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
tcs.TrySetCanceled(cancellationToken);
|
||||||
|
CleanupTasksAndRegistrations(taskId);
|
||||||
|
|
||||||
|
return new ValueTask<TValue>(tcs.Task);
|
||||||
|
}
|
||||||
|
|
||||||
|
var argsJson = args?.Any() == true ?
|
||||||
|
JsonSerializer.Serialize(args, JsonSerializerOptionsProvider.Options) :
|
||||||
|
null;
|
||||||
|
BeginInvokeJS(taskId, identifier, argsJson);
|
||||||
|
|
||||||
|
return new ValueTask<TValue>(tcs.Task);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
CleanupTasksAndRegistrations(taskId);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CleanupTasksAndRegistrations(long taskId)
|
||||||
|
{
|
||||||
|
_pendingTasks.TryRemove(taskId, out _);
|
||||||
|
if (_cancellationRegistrations.TryRemove(taskId, out var registration))
|
||||||
|
{
|
||||||
|
registration.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask<T> InvokeWithDefaultCancellation<T>(string identifier, object[] args)
|
||||||
|
{
|
||||||
|
using (var cts = new CancellationTokenSource(DefaultAsyncTimeout.Value))
|
||||||
|
{
|
||||||
|
// We need to await here due to the using
|
||||||
|
return await InvokeAsync<T>(identifier, cts.Token, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begins an asynchronous function invocation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="taskId">The identifier for the function invocation, or zero if no async callback is required.</param>
|
||||||
|
/// <param name="identifier">The identifier for the function to invoke.</param>
|
||||||
|
/// <param name="argsJson">A JSON representation of the arguments.</param>
|
||||||
|
protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson);
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
protected internal abstract void EndInvokeDotNet(
|
||||||
|
string callId,
|
||||||
|
bool success,
|
||||||
|
object resultOrError,
|
||||||
|
string assemblyName,
|
||||||
|
string methodIdentifier,
|
||||||
|
long dotNetObjectId);
|
||||||
|
|
||||||
|
internal void EndInvokeJS(long taskId, bool succeeded, ref Utf8JsonReader jsonReader)
|
||||||
|
{
|
||||||
|
if (!_pendingTasks.TryRemove(taskId, out var tcs))
|
||||||
|
{
|
||||||
|
// We should simply return if we can't find an id for the invocation.
|
||||||
|
// This likely means that the method that initiated the call defined a timeout and stopped waiting.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CleanupTasksAndRegistrations(taskId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (succeeded)
|
||||||
|
{
|
||||||
|
var resultType = TaskGenericsUtil.GetTaskCompletionSourceResultType(tcs);
|
||||||
|
|
||||||
|
var result = JsonSerializer.Deserialize(ref jsonReader, resultType, JsonSerializerOptionsProvider.Options);
|
||||||
|
TaskGenericsUtil.SetTaskCompletionSourceResult(tcs, result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var exceptionText = jsonReader.GetString() ?? string.Empty;
|
||||||
|
TaskGenericsUtil.SetTaskCompletionSourceException(tcs, new JSException(exceptionText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
var message = $"An exception occurred executing JS interop: {exception.Message}. See InnerException for more details.";
|
||||||
|
TaskGenericsUtil.SetTaskCompletionSourceException(tcs, new JSException(message, exception));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,174 +0,0 @@
|
||||||
// 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.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.JSInterop
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Abstract base class for a JavaScript runtime.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class JSRuntimeBase : IJSRuntime
|
|
||||||
{
|
|
||||||
private long _nextPendingTaskId = 1; // Start at 1 because zero signals "no response needed"
|
|
||||||
private readonly ConcurrentDictionary<long, object> _pendingTasks
|
|
||||||
= new ConcurrentDictionary<long, object>();
|
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<long, CancellationTokenRegistration> _cancellationRegistrations =
|
|
||||||
new ConcurrentDictionary<long, CancellationTokenRegistration>();
|
|
||||||
|
|
||||||
internal DotNetObjectRefManager ObjectRefManager { get; } = new DotNetObjectRefManager();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the default timeout for asynchronous JavaScript calls.
|
|
||||||
/// </summary>
|
|
||||||
protected TimeSpan? DefaultAsyncTimeout { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invokes the specified JavaScript function asynchronously.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The JSON-serializable return type.</typeparam>
|
|
||||||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
|
||||||
/// <param name="args">JSON-serializable arguments.</param>
|
|
||||||
/// <param name="cancellationToken">A cancellation token to signal the cancellation of the operation.</param>
|
|
||||||
/// <returns>An instance of <typeparamref name="T"/> obtained by JSON-deserializing the return value.</returns>
|
|
||||||
public Task<T> InvokeAsync<T>(string identifier, IEnumerable<object> args, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var taskId = Interlocked.Increment(ref _nextPendingTaskId);
|
|
||||||
var tcs = new TaskCompletionSource<T>(TaskContinuationOptions.RunContinuationsAsynchronously);
|
|
||||||
if (cancellationToken != default)
|
|
||||||
{
|
|
||||||
_cancellationRegistrations[taskId] = cancellationToken.Register(() =>
|
|
||||||
{
|
|
||||||
tcs.TrySetCanceled(cancellationToken);
|
|
||||||
CleanupTasksAndRegistrations(taskId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_pendingTasks[taskId] = tcs;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
tcs.TrySetCanceled(cancellationToken);
|
|
||||||
CleanupTasksAndRegistrations(taskId);
|
|
||||||
|
|
||||||
return tcs.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
var argsJson = args?.Any() == true ?
|
|
||||||
JsonSerializer.Serialize(args, JsonSerializerOptionsProvider.Options) :
|
|
||||||
null;
|
|
||||||
BeginInvokeJS(taskId, identifier, argsJson);
|
|
||||||
|
|
||||||
return tcs.Task;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
CleanupTasksAndRegistrations(taskId);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CleanupTasksAndRegistrations(long taskId)
|
|
||||||
{
|
|
||||||
_pendingTasks.TryRemove(taskId, out _);
|
|
||||||
if (_cancellationRegistrations.TryRemove(taskId, out var registration))
|
|
||||||
{
|
|
||||||
registration.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invokes the specified JavaScript function asynchronously.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The JSON-serializable return type.</typeparam>
|
|
||||||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
|
||||||
/// <param name="args">JSON-serializable arguments.</param>
|
|
||||||
/// <returns>An instance of <typeparamref name="T"/> obtained by JSON-deserializing the return value.</returns>
|
|
||||||
public Task<T> InvokeAsync<T>(string identifier, params object[] args)
|
|
||||||
{
|
|
||||||
if (!DefaultAsyncTimeout.HasValue)
|
|
||||||
{
|
|
||||||
return InvokeAsync<T>(identifier, args, default);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return InvokeWithDefaultCancellation<T>(identifier, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<T> InvokeWithDefaultCancellation<T>(string identifier, IEnumerable<object> args)
|
|
||||||
{
|
|
||||||
using (var cts = new CancellationTokenSource(DefaultAsyncTimeout.Value))
|
|
||||||
{
|
|
||||||
// We need to await here due to the using
|
|
||||||
return await InvokeAsync<T>(identifier, args, cts.Token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Begins an asynchronous function invocation.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="taskId">The identifier for the function invocation, or zero if no async callback is required.</param>
|
|
||||||
/// <param name="identifier">The identifier for the function to invoke.</param>
|
|
||||||
/// <param name="argsJson">A JSON representation of the arguments.</param>
|
|
||||||
protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson);
|
|
||||||
|
|
||||||
/// <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>
|
|
||||||
protected internal abstract void EndInvokeDotNet(
|
|
||||||
string callId,
|
|
||||||
bool success,
|
|
||||||
object resultOrError,
|
|
||||||
string assemblyName,
|
|
||||||
string methodIdentifier,
|
|
||||||
long dotNetObjectId);
|
|
||||||
|
|
||||||
internal void EndInvokeJS(long taskId, bool succeeded, ref Utf8JsonReader jsonReader)
|
|
||||||
{
|
|
||||||
if (!_pendingTasks.TryRemove(taskId, out var tcs))
|
|
||||||
{
|
|
||||||
// We should simply return if we can't find an id for the invocation.
|
|
||||||
// This likely means that the method that initiated the call defined a timeout and stopped waiting.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CleanupTasksAndRegistrations(taskId);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (succeeded)
|
|
||||||
{
|
|
||||||
var resultType = TaskGenericsUtil.GetTaskCompletionSourceResultType(tcs);
|
|
||||||
|
|
||||||
var result = JsonSerializer.Deserialize(ref jsonReader, resultType, JsonSerializerOptionsProvider.Options);
|
|
||||||
TaskGenericsUtil.SetTaskCompletionSourceResult(tcs, result);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var exceptionText = jsonReader.GetString() ?? string.Empty;
|
|
||||||
TaskGenericsUtil.SetTaskCompletionSourceException(tcs, new JSException(exceptionText));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
var message = $"An exception occurred executing JS interop: {exception.Message}. See InnerException for more details.";
|
|
||||||
TaskGenericsUtil.SetTaskCompletionSourceException(tcs, new JSException(message, exception));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
// 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;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.JSInterop
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extensions for <see cref="IJSRuntime"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class JSRuntimeExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the specified JavaScript function asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsRuntime">The <see cref="IJSRuntime"/>.</param>
|
||||||
|
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
||||||
|
/// <param name="args">JSON-serializable arguments.</param>
|
||||||
|
/// <returns>A <see cref="ValueTask"/> that represents the asynchronous invocation operation.</returns>
|
||||||
|
public static async ValueTask InvokeVoidAsync(this IJSRuntime jsRuntime, string identifier, params object[] args)
|
||||||
|
{
|
||||||
|
if (jsRuntime is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(jsRuntime));
|
||||||
|
}
|
||||||
|
|
||||||
|
await jsRuntime.InvokeAsync<object>(identifier, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the specified JavaScript function asynchronously.
|
||||||
|
/// <para>
|
||||||
|
/// <see cref="JSRuntime"/> will apply timeouts to this operation based on the value configured in <see cref="JSRuntime.DefaultAsyncTimeout"/>. To dispatch a call with a different timeout, or no timeout,
|
||||||
|
/// consider using <see cref="IJSRuntime.InvokeAsync{TValue}(string, CancellationToken, object[])" />.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsRuntime">The <see cref="IJSRuntime"/>.</param>
|
||||||
|
/// <typeparam name="TValue">The JSON-serializable return type.</typeparam>
|
||||||
|
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
||||||
|
/// <param name="args">JSON-serializable arguments.</param>
|
||||||
|
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
|
||||||
|
public static ValueTask<TValue> InvokeAsync<TValue>(this IJSRuntime jsRuntime, string identifier, params object[] args)
|
||||||
|
{
|
||||||
|
if (jsRuntime is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(jsRuntime));
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsRuntime.InvokeAsync<TValue>(identifier, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the specified JavaScript function asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TValue">The JSON-serializable return type.</typeparam>
|
||||||
|
/// <param name="jsRuntime">The <see cref="IJSRuntime"/>.</param>
|
||||||
|
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
||||||
|
/// <param name="cancellationToken">
|
||||||
|
/// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts
|
||||||
|
/// (<see cref="JSRuntime.DefaultAsyncTimeout"/>) from being applied.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="args">JSON-serializable arguments.</param>
|
||||||
|
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
|
||||||
|
public static ValueTask<TValue> InvokeAsync<TValue>(this IJSRuntime jsRuntime, string identifier, CancellationToken cancellationToken, params object[] args)
|
||||||
|
{
|
||||||
|
if (jsRuntime is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(jsRuntime));
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsRuntime.InvokeAsync<TValue>(identifier, cancellationToken, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the specified JavaScript function asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsRuntime">The <see cref="IJSRuntime"/>.</param>
|
||||||
|
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
||||||
|
/// <param name="cancellationToken">
|
||||||
|
/// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts
|
||||||
|
/// (<see cref="JSRuntime.DefaultAsyncTimeout"/>) from being applied.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="args">JSON-serializable arguments.</param>
|
||||||
|
/// <returns>A <see cref="ValueTask"/> that represents the asynchronous invocation operation.</returns>
|
||||||
|
public static async ValueTask InvokeVoidAsync(this IJSRuntime jsRuntime, string identifier, CancellationToken cancellationToken, params object[] args)
|
||||||
|
{
|
||||||
|
if (jsRuntime is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(jsRuntime));
|
||||||
|
}
|
||||||
|
|
||||||
|
await jsRuntime.InvokeAsync<object>(identifier, cancellationToken, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the specified JavaScript function asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsRuntime">The <see cref="IJSRuntime"/>.</param>
|
||||||
|
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
||||||
|
/// <param name="timeout">The duration after which to cancel the async operation. Overrides default timeouts (<see cref="JSRuntime.DefaultAsyncTimeout"/>).</param>
|
||||||
|
/// <param name="args">JSON-serializable arguments.</param>
|
||||||
|
/// <returns>A <see cref="ValueTask"/> that represents the asynchronous invocation operation.</returns>
|
||||||
|
public static async ValueTask<TValue> InvokeAsync<TValue>(this IJSRuntime jsRuntime, string identifier, TimeSpan timeout, params object[] args)
|
||||||
|
{
|
||||||
|
if (jsRuntime is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(jsRuntime));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
using var cancellationTokenSource = timeout == Timeout.InfiniteTimeSpan ? null : new CancellationTokenSource(timeout);
|
||||||
|
var cancellationToken = cancellationTokenSource?.Token ?? CancellationToken.None;
|
||||||
|
|
||||||
|
return await jsRuntime.InvokeAsync<TValue>(identifier, cancellationToken, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the specified JavaScript function asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsRuntime">The <see cref="IJSRuntime"/>.</param>
|
||||||
|
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
||||||
|
/// <param name="timeout">The duration after which to cancel the async operation. Overrides default timeouts (<see cref="JSRuntime.DefaultAsyncTimeout"/>).</param>
|
||||||
|
/// <param name="args">JSON-serializable arguments.</param>
|
||||||
|
/// <returns>A <see cref="ValueTask"/> that represents the asynchronous invocation operation.</returns>
|
||||||
|
public static async ValueTask InvokeVoidAsync(this IJSRuntime jsRuntime, string identifier, TimeSpan timeout, params object[] args)
|
||||||
|
{
|
||||||
|
if (jsRuntime is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(jsRuntime));
|
||||||
|
}
|
||||||
|
|
||||||
|
using var cancellationTokenSource = timeout == Timeout.InfiniteTimeSpan ? null : new CancellationTokenSource(timeout);
|
||||||
|
var cancellationToken = cancellationTokenSource?.Token ?? CancellationToken.None;
|
||||||
|
|
||||||
|
await jsRuntime.InvokeAsync<object>(identifier, cancellationToken, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,20 +8,20 @@ using static Microsoft.JSInterop.TestJSRuntime;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop
|
namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
public class DotNetObjectRefTest
|
public class DotNetObjectReferenceTest
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public Task CanAccessValue() => WithJSRuntime(_ =>
|
public Task CanAccessValue() => WithJSRuntime(_ =>
|
||||||
{
|
{
|
||||||
var obj = new object();
|
var obj = new object();
|
||||||
Assert.Same(obj, DotNetObjectRef.Create(obj).Value);
|
Assert.Same(obj, DotNetObjectReference.Create(obj).Value);
|
||||||
});
|
});
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public Task NotifiesAssociatedJsRuntimeOfDisposal() => WithJSRuntime(jsRuntime =>
|
public Task NotifiesAssociatedJsRuntimeOfDisposal() => WithJSRuntime(jsRuntime =>
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var objRef = DotNetObjectRef.Create(new object());
|
var objRef = DotNetObjectReference.Create(new object());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
Assert.Equal(1, objRef.ObjectId);
|
Assert.Equal(1, objRef.ObjectId);
|
||||||
|
|
@ -9,7 +9,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop
|
namespace Microsoft.JSInterop.Infrastructure
|
||||||
{
|
{
|
||||||
public class DotNetDispatcherTest
|
public class DotNetDispatcherTest
|
||||||
{
|
{
|
||||||
|
|
@ -114,7 +114,7 @@ namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
// Arrange: Track a .NET object to use as an arg
|
// Arrange: Track a .NET object to use as an arg
|
||||||
var arg3 = new TestDTO { IntVal = 999, StringVal = "My string" };
|
var arg3 = new TestDTO { IntVal = 999, StringVal = "My string" };
|
||||||
var objectRef = DotNetObjectRef.Create(arg3);
|
var objectRef = DotNetObjectReference.Create(arg3);
|
||||||
jsRuntime.Invoke<object>("unimportant", objectRef);
|
jsRuntime.Invoke<object>("unimportant", objectRef);
|
||||||
|
|
||||||
// Arrange: Remaining args
|
// Arrange: Remaining args
|
||||||
|
|
@ -142,7 +142,7 @@ namespace Microsoft.JSInterop
|
||||||
Assert.False(resultDto2Ref.TryGetProperty(nameof(TestDTO.IntVal), out _));
|
Assert.False(resultDto2Ref.TryGetProperty(nameof(TestDTO.IntVal), out _));
|
||||||
|
|
||||||
Assert.True(resultDto2Ref.TryGetProperty(DotNetDispatcher.DotNetObjectRefKey.EncodedUtf8Bytes, out var property));
|
Assert.True(resultDto2Ref.TryGetProperty(DotNetDispatcher.DotNetObjectRefKey.EncodedUtf8Bytes, out var property));
|
||||||
var resultDto2 = Assert.IsType<DotNetObjectRef<TestDTO>>(DotNetObjectRefManager.Current.FindDotNetObject(property.GetInt64())).Value;
|
var resultDto2 = Assert.IsType<DotNetObjectReference<TestDTO>>(DotNetObjectReferenceManager.Current.FindDotNetObject(property.GetInt64())).Value;
|
||||||
Assert.Equal("MY STRING", resultDto2.StringVal);
|
Assert.Equal("MY STRING", resultDto2.StringVal);
|
||||||
Assert.Equal(1299, resultDto2.IntVal);
|
Assert.Equal(1299, resultDto2.IntVal);
|
||||||
});
|
});
|
||||||
|
|
@ -153,7 +153,7 @@ namespace Microsoft.JSInterop
|
||||||
// Arrange
|
// Arrange
|
||||||
var method = nameof(SomePublicType.IncorrectDotNetObjectRefUsage);
|
var method = nameof(SomePublicType.IncorrectDotNetObjectRefUsage);
|
||||||
var arg3 = new TestDTO { IntVal = 999, StringVal = "My string" };
|
var arg3 = new TestDTO { IntVal = 999, StringVal = "My string" };
|
||||||
var objectRef = DotNetObjectRef.Create(arg3);
|
var objectRef = DotNetObjectReference.Create(arg3);
|
||||||
jsRuntime.Invoke<object>("unimportant", objectRef);
|
jsRuntime.Invoke<object>("unimportant", objectRef);
|
||||||
|
|
||||||
// Arrange: Remaining args
|
// Arrange: Remaining args
|
||||||
|
|
@ -175,7 +175,7 @@ namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
// Arrange: Track some instance
|
// Arrange: Track some instance
|
||||||
var targetInstance = new SomePublicType();
|
var targetInstance = new SomePublicType();
|
||||||
var objectRef = DotNetObjectRef.Create(targetInstance);
|
var objectRef = DotNetObjectReference.Create(targetInstance);
|
||||||
jsRuntime.Invoke<object>("unimportant", objectRef);
|
jsRuntime.Invoke<object>("unimportant", objectRef);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -191,7 +191,7 @@ namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
// Arrange: Track some instance
|
// Arrange: Track some instance
|
||||||
var targetInstance = new DerivedClass();
|
var targetInstance = new DerivedClass();
|
||||||
var objectRef = DotNetObjectRef.Create(targetInstance);
|
var objectRef = DotNetObjectReference.Create(targetInstance);
|
||||||
jsRuntime.Invoke<object>("unimportant", objectRef);
|
jsRuntime.Invoke<object>("unimportant", objectRef);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -207,10 +207,10 @@ namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var targetInstance = new SomePublicType();
|
var targetInstance = new SomePublicType();
|
||||||
var objectRef = DotNetObjectRef.Create(targetInstance);
|
var objectRef = DotNetObjectReference.Create(targetInstance);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
DotNetDispatcher.BeginInvoke(null, null, "__Dispose", objectRef.ObjectId, null);
|
DotNetDispatcher.BeginInvokeDotNet(null, null, "__Dispose", objectRef.ObjectId, null);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(objectRef.Disposed);
|
Assert.True(objectRef.Disposed);
|
||||||
|
|
@ -224,7 +224,7 @@ namespace Microsoft.JSInterop
|
||||||
|
|
||||||
// Arrange: Track some instance, then dispose it
|
// Arrange: Track some instance, then dispose it
|
||||||
var targetInstance = new SomePublicType();
|
var targetInstance = new SomePublicType();
|
||||||
var objectRef = DotNetObjectRef.Create(targetInstance);
|
var objectRef = DotNetObjectReference.Create(targetInstance);
|
||||||
jsRuntime.Invoke<object>("unimportant", objectRef);
|
jsRuntime.Invoke<object>("unimportant", objectRef);
|
||||||
objectRef.Dispose();
|
objectRef.Dispose();
|
||||||
|
|
||||||
|
|
@ -242,7 +242,7 @@ namespace Microsoft.JSInterop
|
||||||
|
|
||||||
// Arrange: Track some instance, then dispose it
|
// Arrange: Track some instance, then dispose it
|
||||||
var targetInstance = new SomePublicType();
|
var targetInstance = new SomePublicType();
|
||||||
var objectRef = DotNetObjectRef.Create(targetInstance);
|
var objectRef = DotNetObjectReference.Create(targetInstance);
|
||||||
jsRuntime.Invoke<object>("unimportant", objectRef);
|
jsRuntime.Invoke<object>("unimportant", objectRef);
|
||||||
objectRef.Dispose();
|
objectRef.Dispose();
|
||||||
|
|
||||||
|
|
@ -261,10 +261,10 @@ namespace Microsoft.JSInterop
|
||||||
var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, true, testDTO }, JsonSerializerOptionsProvider.Options);
|
var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, true, testDTO }, JsonSerializerOptionsProvider.Options);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
DotNetDispatcher.EndInvoke(argsJson);
|
DotNetDispatcher.EndInvokeJS(argsJson);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(task.IsCompleted && task.Status == TaskStatus.RanToCompletion);
|
Assert.True(task.IsCompletedSuccessfully);
|
||||||
var result = task.Result;
|
var result = task.Result;
|
||||||
Assert.Equal(testDTO.StringVal, result.StringVal);
|
Assert.Equal(testDTO.StringVal, result.StringVal);
|
||||||
Assert.Equal(testDTO.IntVal, result.IntVal);
|
Assert.Equal(testDTO.IntVal, result.IntVal);
|
||||||
|
|
@ -279,10 +279,10 @@ namespace Microsoft.JSInterop
|
||||||
var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, false, expected }, JsonSerializerOptionsProvider.Options);
|
var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, false, expected }, JsonSerializerOptionsProvider.Options);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
DotNetDispatcher.EndInvoke(argsJson);
|
DotNetDispatcher.EndInvokeJS(argsJson);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var ex = await Assert.ThrowsAsync<JSException>(() => task);
|
var ex = await Assert.ThrowsAsync<JSException>(async () => await task);
|
||||||
Assert.Equal(expected, ex.Message);
|
Assert.Equal(expected, ex.Message);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -297,7 +297,7 @@ namespace Microsoft.JSInterop
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
cts.Cancel();
|
cts.Cancel();
|
||||||
DotNetDispatcher.EndInvoke(argsJson);
|
DotNetDispatcher.EndInvokeJS(argsJson);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(task.IsCanceled);
|
Assert.True(task.IsCanceled);
|
||||||
|
|
@ -311,10 +311,10 @@ namespace Microsoft.JSInterop
|
||||||
var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, false, null }, JsonSerializerOptionsProvider.Options);
|
var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, false, null }, JsonSerializerOptionsProvider.Options);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
DotNetDispatcher.EndInvoke(argsJson);
|
DotNetDispatcher.EndInvokeJS(argsJson);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var ex = await Assert.ThrowsAsync<JSException>(() => task);
|
var ex = await Assert.ThrowsAsync<JSException>(async () => await task);
|
||||||
Assert.Empty(ex.Message);
|
Assert.Empty(ex.Message);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -325,8 +325,8 @@ namespace Microsoft.JSInterop
|
||||||
var targetInstance = new SomePublicType();
|
var targetInstance = new SomePublicType();
|
||||||
var arg2 = new TestDTO { IntVal = 1234, StringVal = "My string" };
|
var arg2 = new TestDTO { IntVal = 1234, StringVal = "My string" };
|
||||||
jsRuntime.Invoke<object>("unimportant",
|
jsRuntime.Invoke<object>("unimportant",
|
||||||
DotNetObjectRef.Create(targetInstance),
|
DotNetObjectReference.Create(targetInstance),
|
||||||
DotNetObjectRef.Create(arg2));
|
DotNetObjectReference.Create(arg2));
|
||||||
var argsJson = "[\"myvalue\",{\"__dotNetObject\":2}]";
|
var argsJson = "[\"myvalue\",{\"__dotNetObject\":2}]";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -334,7 +334,7 @@ namespace Microsoft.JSInterop
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("[\"You passed myvalue\",{\"__dotNetObject\":3}]", resultJson);
|
Assert.Equal("[\"You passed myvalue\",{\"__dotNetObject\":3}]", resultJson);
|
||||||
var resultDto = ((DotNetObjectRef<TestDTO>)jsRuntime.ObjectRefManager.FindDotNetObject(3)).Value;
|
var resultDto = ((DotNetObjectReference<TestDTO>)jsRuntime.ObjectRefManager.FindDotNetObject(3)).Value;
|
||||||
Assert.Equal(1235, resultDto.IntVal);
|
Assert.Equal(1235, resultDto.IntVal);
|
||||||
Assert.Equal("MY STRING", resultDto.StringVal);
|
Assert.Equal("MY STRING", resultDto.StringVal);
|
||||||
});
|
});
|
||||||
|
|
@ -362,7 +362,7 @@ namespace Microsoft.JSInterop
|
||||||
public Task CannotInvokeWithMoreParameters() => WithJSRuntime(jsRuntime =>
|
public Task CannotInvokeWithMoreParameters() => WithJSRuntime(jsRuntime =>
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var objectRef = DotNetObjectRef.Create(new TestDTO { IntVal = 4 });
|
var objectRef = DotNetObjectReference.Create(new TestDTO { IntVal = 4 });
|
||||||
var argsJson = JsonSerializer.Serialize(new object[]
|
var argsJson = JsonSerializer.Serialize(new object[]
|
||||||
{
|
{
|
||||||
new TestDTO { StringVal = "Another string", IntVal = 456 },
|
new TestDTO { StringVal = "Another string", IntVal = 456 },
|
||||||
|
|
@ -386,8 +386,8 @@ namespace Microsoft.JSInterop
|
||||||
// Arrange: Track some instance plus another object we'll pass as a param
|
// Arrange: Track some instance plus another object we'll pass as a param
|
||||||
var targetInstance = new SomePublicType();
|
var targetInstance = new SomePublicType();
|
||||||
var arg2 = new TestDTO { IntVal = 1234, StringVal = "My string" };
|
var arg2 = new TestDTO { IntVal = 1234, StringVal = "My string" };
|
||||||
var arg1Ref = DotNetObjectRef.Create(targetInstance);
|
var arg1Ref = DotNetObjectReference.Create(targetInstance);
|
||||||
var arg2Ref = DotNetObjectRef.Create(arg2);
|
var arg2Ref = DotNetObjectReference.Create(arg2);
|
||||||
jsRuntime.Invoke<object>("unimportant", arg1Ref, arg2Ref);
|
jsRuntime.Invoke<object>("unimportant", arg1Ref, arg2Ref);
|
||||||
|
|
||||||
// Arrange: all args
|
// Arrange: all args
|
||||||
|
|
@ -400,7 +400,7 @@ namespace Microsoft.JSInterop
|
||||||
// Act
|
// Act
|
||||||
var callId = "123";
|
var callId = "123";
|
||||||
var resultTask = jsRuntime.NextInvocationTask;
|
var resultTask = jsRuntime.NextInvocationTask;
|
||||||
DotNetDispatcher.BeginInvoke(callId, null, "InvokableAsyncMethod", 1, argsJson);
|
DotNetDispatcher.BeginInvokeDotNet(callId, null, "InvokableAsyncMethod", 1, argsJson);
|
||||||
await resultTask;
|
await resultTask;
|
||||||
|
|
||||||
// Assert: Correct completion information
|
// Assert: Correct completion information
|
||||||
|
|
@ -413,7 +413,7 @@ namespace Microsoft.JSInterop
|
||||||
Assert.Equal(2000, resultDto1.IntVal);
|
Assert.Equal(2000, resultDto1.IntVal);
|
||||||
|
|
||||||
// Assert: Second result value marshalled by ref
|
// Assert: Second result value marshalled by ref
|
||||||
var resultDto2Ref = Assert.IsType<DotNetObjectRef<TestDTO>>(result[1]);
|
var resultDto2Ref = Assert.IsType<DotNetObjectReference<TestDTO>>(result[1]);
|
||||||
var resultDto2 = resultDto2Ref.Value;
|
var resultDto2 = resultDto2Ref.Value;
|
||||||
Assert.Equal("MY STRING", resultDto2.StringVal);
|
Assert.Equal("MY STRING", resultDto2.StringVal);
|
||||||
Assert.Equal(2468, resultDto2.IntVal);
|
Assert.Equal(2468, resultDto2.IntVal);
|
||||||
|
|
@ -427,7 +427,7 @@ namespace Microsoft.JSInterop
|
||||||
// Act
|
// Act
|
||||||
var callId = "123";
|
var callId = "123";
|
||||||
var resultTask = jsRuntime.NextInvocationTask;
|
var resultTask = jsRuntime.NextInvocationTask;
|
||||||
DotNetDispatcher.BeginInvoke(callId, thisAssemblyName, nameof(ThrowingClass.ThrowingMethod), default, default);
|
DotNetDispatcher.BeginInvokeDotNet(callId, thisAssemblyName, nameof(ThrowingClass.ThrowingMethod), default, default);
|
||||||
|
|
||||||
await resultTask; // This won't throw, it sets properties on the jsRuntime.
|
await resultTask; // This won't throw, it sets properties on the jsRuntime.
|
||||||
|
|
||||||
|
|
@ -449,7 +449,7 @@ namespace Microsoft.JSInterop
|
||||||
// Act
|
// Act
|
||||||
var callId = "123";
|
var callId = "123";
|
||||||
var resultTask = jsRuntime.NextInvocationTask;
|
var resultTask = jsRuntime.NextInvocationTask;
|
||||||
DotNetDispatcher.BeginInvoke(callId, thisAssemblyName, nameof(ThrowingClass.AsyncThrowingMethod), default, default);
|
DotNetDispatcher.BeginInvokeDotNet(callId, thisAssemblyName, nameof(ThrowingClass.AsyncThrowingMethod), default, default);
|
||||||
|
|
||||||
await resultTask; // This won't throw, it sets properties on the jsRuntime.
|
await resultTask; // This won't throw, it sets properties on the jsRuntime.
|
||||||
|
|
||||||
|
|
@ -469,7 +469,7 @@ namespace Microsoft.JSInterop
|
||||||
// Arrange
|
// Arrange
|
||||||
var callId = "123";
|
var callId = "123";
|
||||||
var resultTask = jsRuntime.NextInvocationTask;
|
var resultTask = jsRuntime.NextInvocationTask;
|
||||||
DotNetDispatcher.BeginInvoke(callId, thisAssemblyName, "InvocableStaticWithParams", default, "<xml>not json</xml>");
|
DotNetDispatcher.BeginInvokeDotNet(callId, thisAssemblyName, "InvocableStaticWithParams", default, "<xml>not json</xml>");
|
||||||
|
|
||||||
await resultTask; // This won't throw, it sets properties on the jsRuntime.
|
await resultTask; // This won't throw, it sets properties on the jsRuntime.
|
||||||
|
|
||||||
|
|
@ -486,7 +486,7 @@ namespace Microsoft.JSInterop
|
||||||
// Arrange
|
// Arrange
|
||||||
var callId = "123";
|
var callId = "123";
|
||||||
var resultTask = jsRuntime.NextInvocationTask;
|
var resultTask = jsRuntime.NextInvocationTask;
|
||||||
DotNetDispatcher.BeginInvoke(callId, null, "InvokableInstanceVoid", 1, null);
|
DotNetDispatcher.BeginInvokeDotNet(callId, null, "InvokableInstanceVoid", 1, null);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(callId, jsRuntime.LastCompletionCallId);
|
Assert.Equal(callId, jsRuntime.LastCompletionCallId);
|
||||||
|
|
@ -611,7 +611,7 @@ namespace Microsoft.JSInterop
|
||||||
|
|
||||||
DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, {{\"intVal\": 7}}]");
|
DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, {{\"intVal\": 7}}]");
|
||||||
|
|
||||||
Assert.True(task.IsCompleted && task.Status == TaskStatus.RanToCompletion);
|
Assert.True(task.IsCompletedSuccessfully);
|
||||||
Assert.Equal(7, task.Result.IntVal);
|
Assert.Equal(7, task.Result.IntVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -623,7 +623,7 @@ namespace Microsoft.JSInterop
|
||||||
|
|
||||||
DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, [1, 2, 3]]");
|
DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, [1, 2, 3]]");
|
||||||
|
|
||||||
Assert.True(task.IsCompleted && task.Status == TaskStatus.RanToCompletion);
|
Assert.True(task.IsCompletedSuccessfully);
|
||||||
Assert.Equal(new[] { 1, 2, 3 }, task.Result);
|
Assert.Equal(new[] { 1, 2, 3 }, task.Result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -635,7 +635,7 @@ namespace Microsoft.JSInterop
|
||||||
|
|
||||||
DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, null]");
|
DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, null]");
|
||||||
|
|
||||||
Assert.True(task.IsCompleted && task.Status == TaskStatus.RanToCompletion);
|
Assert.True(task.IsCompletedSuccessfully);
|
||||||
Assert.Null(task.Result);
|
Assert.Null(task.Result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -685,7 +685,7 @@ namespace Microsoft.JSInterop
|
||||||
=> new TestDTO { StringVal = "Test", IntVal = 123 };
|
=> new TestDTO { StringVal = "Test", IntVal = 123 };
|
||||||
|
|
||||||
[JSInvokable("InvocableStaticWithParams")]
|
[JSInvokable("InvocableStaticWithParams")]
|
||||||
public static object[] MyInvocableWithParams(TestDTO dtoViaJson, int[] incrementAmounts, DotNetObjectRef<TestDTO> dtoByRef)
|
public static object[] MyInvocableWithParams(TestDTO dtoViaJson, int[] incrementAmounts, DotNetObjectReference<TestDTO> dtoByRef)
|
||||||
=> new object[]
|
=> new object[]
|
||||||
{
|
{
|
||||||
new TestDTO // Return via JSON marshalling
|
new TestDTO // Return via JSON marshalling
|
||||||
|
|
@ -693,7 +693,7 @@ namespace Microsoft.JSInterop
|
||||||
StringVal = dtoViaJson.StringVal.ToUpperInvariant(),
|
StringVal = dtoViaJson.StringVal.ToUpperInvariant(),
|
||||||
IntVal = dtoViaJson.IntVal + incrementAmounts.Sum()
|
IntVal = dtoViaJson.IntVal + incrementAmounts.Sum()
|
||||||
},
|
},
|
||||||
DotNetObjectRef.Create(new TestDTO // Return by ref
|
DotNetObjectReference.Create(new TestDTO // Return by ref
|
||||||
{
|
{
|
||||||
StringVal = dtoByRef.Value.StringVal.ToUpperInvariant(),
|
StringVal = dtoByRef.Value.StringVal.ToUpperInvariant(),
|
||||||
IntVal = dtoByRef.Value.IntVal + incrementAmounts.Sum()
|
IntVal = dtoByRef.Value.IntVal + incrementAmounts.Sum()
|
||||||
|
|
@ -715,7 +715,7 @@ namespace Microsoft.JSInterop
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable]
|
[JSInvokable]
|
||||||
public object[] InvokableInstanceMethod(string someString, DotNetObjectRef<TestDTO> someDTORef)
|
public object[] InvokableInstanceMethod(string someString, DotNetObjectReference<TestDTO> someDTORef)
|
||||||
{
|
{
|
||||||
var someDTO = someDTORef.Value;
|
var someDTO = someDTORef.Value;
|
||||||
// Returning an array to make the point that object references
|
// Returning an array to make the point that object references
|
||||||
|
|
@ -723,7 +723,7 @@ namespace Microsoft.JSInterop
|
||||||
return new object[]
|
return new object[]
|
||||||
{
|
{
|
||||||
$"You passed {someString}",
|
$"You passed {someString}",
|
||||||
DotNetObjectRef.Create(new TestDTO
|
DotNetObjectReference.Create(new TestDTO
|
||||||
{
|
{
|
||||||
IntVal = someDTO.IntVal + 1,
|
IntVal = someDTO.IntVal + 1,
|
||||||
StringVal = someDTO.StringVal.ToUpperInvariant()
|
StringVal = someDTO.StringVal.ToUpperInvariant()
|
||||||
|
|
@ -732,7 +732,7 @@ namespace Microsoft.JSInterop
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable]
|
[JSInvokable]
|
||||||
public async Task<object[]> InvokableAsyncMethod(TestDTO dtoViaJson, DotNetObjectRef<TestDTO> dtoByRefWrapper)
|
public async Task<object[]> InvokableAsyncMethod(TestDTO dtoViaJson, DotNetObjectReference<TestDTO> dtoByRefWrapper)
|
||||||
{
|
{
|
||||||
await Task.Delay(50);
|
await Task.Delay(50);
|
||||||
var dtoByRef = dtoByRefWrapper.Value;
|
var dtoByRef = dtoByRefWrapper.Value;
|
||||||
|
|
@ -743,7 +743,7 @@ namespace Microsoft.JSInterop
|
||||||
StringVal = dtoViaJson.StringVal.ToUpperInvariant(),
|
StringVal = dtoViaJson.StringVal.ToUpperInvariant(),
|
||||||
IntVal = dtoViaJson.IntVal * 2,
|
IntVal = dtoViaJson.IntVal * 2,
|
||||||
},
|
},
|
||||||
DotNetObjectRef.Create(new TestDTO // Return by ref
|
DotNetObjectReference.Create(new TestDTO // Return by ref
|
||||||
{
|
{
|
||||||
StringVal = dtoByRef.StringVal.ToUpperInvariant(),
|
StringVal = dtoByRef.StringVal.ToUpperInvariant(),
|
||||||
IntVal = dtoByRef.IntVal * 2,
|
IntVal = dtoByRef.IntVal * 2,
|
||||||
|
|
@ -789,7 +789,7 @@ namespace Microsoft.JSInterop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestJSRuntime : JSInProcessRuntimeBase
|
public class TestJSRuntime : JSInProcessRuntime
|
||||||
{
|
{
|
||||||
private TaskCompletionSource<object> _nextInvocationTcs = new TaskCompletionSource<object>();
|
private TaskCompletionSource<object> _nextInvocationTcs = new TaskCompletionSource<object>();
|
||||||
public Task NextInvocationTask => _nextInvocationTcs.Task;
|
public Task NextInvocationTask => _nextInvocationTcs.Task;
|
||||||
|
|
@ -6,7 +6,7 @@ using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using static Microsoft.JSInterop.TestJSRuntime;
|
using static Microsoft.JSInterop.TestJSRuntime;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop.Tests
|
namespace Microsoft.JSInterop.Infrastructure
|
||||||
{
|
{
|
||||||
public class DotNetObjectReferenceJsonConverterTest
|
public class DotNetObjectReferenceJsonConverterTest
|
||||||
{
|
{
|
||||||
|
|
@ -14,12 +14,12 @@ namespace Microsoft.JSInterop.Tests
|
||||||
public Task Read_Throws_IfJsonIsMissingDotNetObjectProperty() => WithJSRuntime(_ =>
|
public Task Read_Throws_IfJsonIsMissingDotNetObjectProperty() => WithJSRuntime(_ =>
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var dotNetObjectRef = DotNetObjectRef.Create(new TestModel());
|
var dotNetObjectRef = DotNetObjectReference.Create(new TestModel());
|
||||||
|
|
||||||
var json = "{}";
|
var json = "{}";
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<DotNetObjectRef<TestModel>>(json));
|
var ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<DotNetObjectReference<TestModel>>(json));
|
||||||
Assert.Equal("Required property __dotNetObject not found.", ex.Message);
|
Assert.Equal("Required property __dotNetObject not found.", ex.Message);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -27,12 +27,12 @@ namespace Microsoft.JSInterop.Tests
|
||||||
public Task Read_Throws_IfJsonContainsUnknownContent() => WithJSRuntime(_ =>
|
public Task Read_Throws_IfJsonContainsUnknownContent() => WithJSRuntime(_ =>
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var dotNetObjectRef = DotNetObjectRef.Create(new TestModel());
|
var dotNetObjectRef = DotNetObjectReference.Create(new TestModel());
|
||||||
|
|
||||||
var json = "{\"foo\":2}";
|
var json = "{\"foo\":2}";
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<DotNetObjectRef<TestModel>>(json));
|
var ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<DotNetObjectReference<TestModel>>(json));
|
||||||
Assert.Equal("Unexcepted JSON property foo.", ex.Message);
|
Assert.Equal("Unexcepted JSON property foo.", ex.Message);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -41,13 +41,13 @@ namespace Microsoft.JSInterop.Tests
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var input = new TestModel();
|
var input = new TestModel();
|
||||||
var dotNetObjectRef = DotNetObjectRef.Create(input);
|
var dotNetObjectRef = DotNetObjectReference.Create(input);
|
||||||
var objectId = dotNetObjectRef.ObjectId;
|
var objectId = dotNetObjectRef.ObjectId;
|
||||||
|
|
||||||
var json = $"{{\"__dotNetObject\":{objectId}";
|
var json = $"{{\"__dotNetObject\":{objectId}";
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var ex = Record.Exception(() => JsonSerializer.Deserialize<DotNetObjectRef<TestModel>>(json));
|
var ex = Record.Exception(() => JsonSerializer.Deserialize<DotNetObjectReference<TestModel>>(json));
|
||||||
Assert.IsAssignableFrom<JsonException>(ex);
|
Assert.IsAssignableFrom<JsonException>(ex);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -56,13 +56,13 @@ namespace Microsoft.JSInterop.Tests
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var input = new TestModel();
|
var input = new TestModel();
|
||||||
var dotNetObjectRef = DotNetObjectRef.Create(input);
|
var dotNetObjectRef = DotNetObjectReference.Create(input);
|
||||||
var objectId = dotNetObjectRef.ObjectId;
|
var objectId = dotNetObjectRef.ObjectId;
|
||||||
|
|
||||||
var json = $"{{\"__dotNetObject\":{objectId},\"__dotNetObject\":{objectId}}}";
|
var json = $"{{\"__dotNetObject\":{objectId},\"__dotNetObject\":{objectId}}}";
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var ex = Record.Exception(() => JsonSerializer.Deserialize<DotNetObjectRef<TestModel>>(json));
|
var ex = Record.Exception(() => JsonSerializer.Deserialize<DotNetObjectReference<TestModel>>(json));
|
||||||
Assert.IsAssignableFrom<JsonException>(ex);
|
Assert.IsAssignableFrom<JsonException>(ex);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -71,13 +71,13 @@ namespace Microsoft.JSInterop.Tests
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var input = new TestModel();
|
var input = new TestModel();
|
||||||
var dotNetObjectRef = DotNetObjectRef.Create(input);
|
var dotNetObjectRef = DotNetObjectReference.Create(input);
|
||||||
var objectId = dotNetObjectRef.ObjectId;
|
var objectId = dotNetObjectRef.ObjectId;
|
||||||
|
|
||||||
var json = $"{{\"__dotNetObject\":{objectId}}}";
|
var json = $"{{\"__dotNetObject\":{objectId}}}";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var deserialized = JsonSerializer.Deserialize<DotNetObjectRef<TestModel>>(json);
|
var deserialized = JsonSerializer.Deserialize<DotNetObjectReference<TestModel>>(json);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Same(input, deserialized.Value);
|
Assert.Same(input, deserialized.Value);
|
||||||
|
|
@ -92,13 +92,13 @@ namespace Microsoft.JSInterop.Tests
|
||||||
// Track a few instances and verify that the deserialized value returns the correct value.
|
// Track a few instances and verify that the deserialized value returns the correct value.
|
||||||
var instance1 = new TestModel();
|
var instance1 = new TestModel();
|
||||||
var instance2 = new TestModel();
|
var instance2 = new TestModel();
|
||||||
var ref1 = DotNetObjectRef.Create(instance1);
|
var ref1 = DotNetObjectReference.Create(instance1);
|
||||||
var ref2 = DotNetObjectRef.Create(instance2);
|
var ref2 = DotNetObjectReference.Create(instance2);
|
||||||
|
|
||||||
var json = $"[{{\"__dotNetObject\":{ref2.ObjectId}}},{{\"__dotNetObject\":{ref1.ObjectId}}}]";
|
var json = $"[{{\"__dotNetObject\":{ref2.ObjectId}}},{{\"__dotNetObject\":{ref1.ObjectId}}}]";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var deserialized = JsonSerializer.Deserialize<DotNetObjectRef<TestModel>[]>(json);
|
var deserialized = JsonSerializer.Deserialize<DotNetObjectReference<TestModel>[]>(json);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Same(instance2, deserialized[0].Value);
|
Assert.Same(instance2, deserialized[0].Value);
|
||||||
|
|
@ -110,7 +110,7 @@ namespace Microsoft.JSInterop.Tests
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var input = new TestModel();
|
var input = new TestModel();
|
||||||
var dotNetObjectRef = DotNetObjectRef.Create(input);
|
var dotNetObjectRef = DotNetObjectReference.Create(input);
|
||||||
var objectId = dotNetObjectRef.ObjectId;
|
var objectId = dotNetObjectRef.ObjectId;
|
||||||
|
|
||||||
var json =
|
var json =
|
||||||
|
|
@ -119,7 +119,7 @@ namespace Microsoft.JSInterop.Tests
|
||||||
}}";
|
}}";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var deserialized = JsonSerializer.Deserialize<DotNetObjectRef<TestModel>>(json);
|
var deserialized = JsonSerializer.Deserialize<DotNetObjectReference<TestModel>>(json);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Same(input, deserialized.Value);
|
Assert.Same(input, deserialized.Value);
|
||||||
|
|
@ -130,7 +130,7 @@ namespace Microsoft.JSInterop.Tests
|
||||||
public Task WriteJsonTwice_KeepsObjectId() => WithJSRuntime(_ =>
|
public Task WriteJsonTwice_KeepsObjectId() => WithJSRuntime(_ =>
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var dotNetObjectRef = DotNetObjectRef.Create(new TestModel());
|
var dotNetObjectRef = DotNetObjectReference.Create(new TestModel());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var json1 = JsonSerializer.Serialize(dotNetObjectRef);
|
var json1 = JsonSerializer.Serialize(dotNetObjectRef);
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.JSInterop
|
||||||
|
{
|
||||||
|
public class JSInProcessRuntimeExtensionsTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void InvokeVoid_Works()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var method = "someMethod";
|
||||||
|
var args = new[] { "a", "b" };
|
||||||
|
var jsRuntime = new Mock<IJSInProcessRuntime>(MockBehavior.Strict);
|
||||||
|
jsRuntime.Setup(s => s.Invoke<object>(method, args)).Returns(new ValueTask<object>(new object()));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
jsRuntime.Object.InvokeVoid(method, args);
|
||||||
|
|
||||||
|
jsRuntime.Verify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop.Tests
|
namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
public class JSInProcessRuntimeBaseTest
|
public class JSInProcessRuntimeBaseTest
|
||||||
{
|
{
|
||||||
|
|
@ -43,12 +43,12 @@ namespace Microsoft.JSInterop.Tests
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
// Showing we can pass the DotNetObject either as top-level args or nested
|
// Showing we can pass the DotNetObject either as top-level args or nested
|
||||||
var syncResult = runtime.Invoke<DotNetObjectRef<object>>("test identifier",
|
var syncResult = runtime.Invoke<DotNetObjectReference<object>>("test identifier",
|
||||||
DotNetObjectRef.Create(obj1),
|
DotNetObjectReference.Create(obj1),
|
||||||
new Dictionary<string, object>
|
new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
{ "obj2", DotNetObjectRef.Create(obj2) },
|
{ "obj2", DotNetObjectReference.Create(obj2) },
|
||||||
{ "obj3", DotNetObjectRef.Create(obj3) },
|
{ "obj3", DotNetObjectReference.Create(obj3) },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assert: Handles null result string
|
// Assert: Handles null result string
|
||||||
|
|
@ -78,11 +78,11 @@ namespace Microsoft.JSInterop.Tests
|
||||||
var obj2 = new object();
|
var obj2 = new object();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var syncResult = runtime.Invoke<DotNetObjectRef<object>[]>(
|
var syncResult = runtime.Invoke<DotNetObjectReference<object>[]>(
|
||||||
"test identifier",
|
"test identifier",
|
||||||
DotNetObjectRef.Create(obj1),
|
DotNetObjectReference.Create(obj1),
|
||||||
"some other arg",
|
"some other arg",
|
||||||
DotNetObjectRef.Create(obj2));
|
DotNetObjectReference.Create(obj2));
|
||||||
var call = runtime.InvokeCalls.Single();
|
var call = runtime.InvokeCalls.Single();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -95,7 +95,7 @@ namespace Microsoft.JSInterop.Tests
|
||||||
public string StringValue { get; set; }
|
public string StringValue { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestJSInProcessRuntime : JSInProcessRuntimeBase
|
class TestJSInProcessRuntime : JSInProcessRuntime
|
||||||
{
|
{
|
||||||
public List<InvokeArgs> InvokeCalls { get; set; } = new List<InvokeArgs>();
|
public List<InvokeArgs> InvokeCalls { get; set; } = new List<InvokeArgs>();
|
||||||
|
|
||||||
|
|
@ -1,386 +0,0 @@
|
||||||
// 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 System.Text;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.JSInterop
|
|
||||||
{
|
|
||||||
public class JSRuntimeBaseTest
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void DispatchesAsyncCallsWithDistinctAsyncHandles()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var runtime = new TestJSRuntime();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
runtime.InvokeAsync<object>("test identifier 1", "arg1", 123, true);
|
|
||||||
runtime.InvokeAsync<object>("test identifier 2", "some other arg");
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Collection(runtime.BeginInvokeCalls,
|
|
||||||
call =>
|
|
||||||
{
|
|
||||||
Assert.Equal("test identifier 1", call.Identifier);
|
|
||||||
Assert.Equal("[\"arg1\",123,true]", call.ArgsJson);
|
|
||||||
},
|
|
||||||
call =>
|
|
||||||
{
|
|
||||||
Assert.Equal("test identifier 2", call.Identifier);
|
|
||||||
Assert.Equal("[\"some other arg\"]", call.ArgsJson);
|
|
||||||
Assert.NotEqual(runtime.BeginInvokeCalls[0].AsyncHandle, call.AsyncHandle);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task InvokeAsync_CancelsAsyncTask_AfterDefaultTimeout()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var runtime = new TestJSRuntime();
|
|
||||||
runtime.DefaultTimeout = TimeSpan.FromSeconds(1);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var task = runtime.InvokeAsync<object>("test identifier 1", "arg1", 123, true);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
await Assert.ThrowsAsync<TaskCanceledException>(async () => await task);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void InvokeAsync_CompletesSuccessfullyBeforeTimeout()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var runtime = new TestJSRuntime();
|
|
||||||
runtime.DefaultTimeout = TimeSpan.FromSeconds(10);
|
|
||||||
var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes("null"));
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var task = runtime.InvokeAsync<object>("test identifier 1", "arg1", 123, true);
|
|
||||||
|
|
||||||
runtime.EndInvokeJS(2, succeeded: true, ref reader);
|
|
||||||
|
|
||||||
Assert.True(task.IsCompleted && task.Status == TaskStatus.RanToCompletion);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task InvokeAsync_CancelsAsyncTasksWhenCancellationTokenFires()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
using var cts = new CancellationTokenSource();
|
|
||||||
var runtime = new TestJSRuntime();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var task = runtime.InvokeAsync<object>("test identifier 1", new object[] { "arg1", 123, true }, cts.Token);
|
|
||||||
|
|
||||||
cts.Cancel();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
await Assert.ThrowsAsync<TaskCanceledException>(async () => await task);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task InvokeAsync_DoesNotStartWorkWhenCancellationHasBeenRequested()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
using var cts = new CancellationTokenSource();
|
|
||||||
cts.Cancel();
|
|
||||||
var runtime = new TestJSRuntime();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var task = runtime.InvokeAsync<object>("test identifier 1", new object[] { "arg1", 123, true }, cts.Token);
|
|
||||||
|
|
||||||
cts.Cancel();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
await Assert.ThrowsAsync<TaskCanceledException>(async () => await task);
|
|
||||||
Assert.Empty(runtime.BeginInvokeCalls);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CanCompleteAsyncCallsAsSuccess()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var runtime = new TestJSRuntime();
|
|
||||||
|
|
||||||
// Act/Assert: Tasks not initially completed
|
|
||||||
var unrelatedTask = runtime.InvokeAsync<string>("unrelated call", Array.Empty<object>());
|
|
||||||
var task = runtime.InvokeAsync<string>("test identifier", Array.Empty<object>());
|
|
||||||
Assert.False(unrelatedTask.IsCompleted);
|
|
||||||
Assert.False(task.IsCompleted);
|
|
||||||
var bytes = Encoding.UTF8.GetBytes("\"my result\"");
|
|
||||||
var reader = new Utf8JsonReader(bytes);
|
|
||||||
|
|
||||||
// Act/Assert: Task can be completed
|
|
||||||
runtime.EndInvokeJS(
|
|
||||||
runtime.BeginInvokeCalls[1].AsyncHandle,
|
|
||||||
/* succeeded: */ true,
|
|
||||||
ref reader);
|
|
||||||
Assert.False(unrelatedTask.IsCompleted);
|
|
||||||
Assert.True(task.IsCompleted);
|
|
||||||
Assert.Equal("my result", task.Result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CanCompleteAsyncCallsWithComplexType()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var runtime = new TestJSRuntime();
|
|
||||||
|
|
||||||
var task = runtime.InvokeAsync<TestPoco>("test identifier", Array.Empty<object>());
|
|
||||||
var bytes = Encoding.UTF8.GetBytes("{\"id\":10, \"name\": \"Test\"}");
|
|
||||||
var reader = new Utf8JsonReader(bytes);
|
|
||||||
|
|
||||||
// Act/Assert: Task can be completed
|
|
||||||
runtime.EndInvokeJS(
|
|
||||||
runtime.BeginInvokeCalls[0].AsyncHandle,
|
|
||||||
/* succeeded: */ true,
|
|
||||||
ref reader);
|
|
||||||
Assert.True(task.IsCompleted);
|
|
||||||
var poco = task.Result;
|
|
||||||
Assert.Equal(10, poco.Id);
|
|
||||||
Assert.Equal("Test", poco.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CanCompleteAsyncCallsWithComplexTypeUsingPropertyCasing()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var runtime = new TestJSRuntime();
|
|
||||||
|
|
||||||
var task = runtime.InvokeAsync<TestPoco>("test identifier", Array.Empty<object>());
|
|
||||||
var bytes = Encoding.UTF8.GetBytes("{\"Id\":10, \"Name\": \"Test\"}");
|
|
||||||
var reader = new Utf8JsonReader(bytes);
|
|
||||||
reader.Read();
|
|
||||||
|
|
||||||
// Act/Assert: Task can be completed
|
|
||||||
runtime.EndInvokeJS(
|
|
||||||
runtime.BeginInvokeCalls[0].AsyncHandle,
|
|
||||||
/* succeeded: */ true,
|
|
||||||
ref reader);
|
|
||||||
Assert.True(task.IsCompleted);
|
|
||||||
var poco = task.Result;
|
|
||||||
Assert.Equal(10, poco.Id);
|
|
||||||
Assert.Equal("Test", poco.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CanCompleteAsyncCallsAsFailure()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var runtime = new TestJSRuntime();
|
|
||||||
|
|
||||||
// Act/Assert: Tasks not initially completed
|
|
||||||
var unrelatedTask = runtime.InvokeAsync<string>("unrelated call", Array.Empty<object>());
|
|
||||||
var task = runtime.InvokeAsync<string>("test identifier", Array.Empty<object>());
|
|
||||||
Assert.False(unrelatedTask.IsCompleted);
|
|
||||||
Assert.False(task.IsCompleted);
|
|
||||||
var bytes = Encoding.UTF8.GetBytes("\"This is a test exception\"");
|
|
||||||
var reader = new Utf8JsonReader(bytes);
|
|
||||||
reader.Read();
|
|
||||||
|
|
||||||
// Act/Assert: Task can be failed
|
|
||||||
runtime.EndInvokeJS(
|
|
||||||
runtime.BeginInvokeCalls[1].AsyncHandle,
|
|
||||||
/* succeeded: */ false,
|
|
||||||
ref reader);
|
|
||||||
Assert.False(unrelatedTask.IsCompleted);
|
|
||||||
Assert.True(task.IsCompleted);
|
|
||||||
|
|
||||||
Assert.IsType<AggregateException>(task.Exception);
|
|
||||||
Assert.IsType<JSException>(task.Exception.InnerException);
|
|
||||||
Assert.Equal("This is a test exception", ((JSException)task.Exception.InnerException).Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public Task CanCompleteAsyncCallsWithErrorsDuringDeserialization()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var runtime = new TestJSRuntime();
|
|
||||||
|
|
||||||
// Act/Assert: Tasks not initially completed
|
|
||||||
var unrelatedTask = runtime.InvokeAsync<string>("unrelated call", Array.Empty<object>());
|
|
||||||
var task = runtime.InvokeAsync<int>("test identifier", Array.Empty<object>());
|
|
||||||
Assert.False(unrelatedTask.IsCompleted);
|
|
||||||
Assert.False(task.IsCompleted);
|
|
||||||
var bytes = Encoding.UTF8.GetBytes("Not a string");
|
|
||||||
var reader = new Utf8JsonReader(bytes);
|
|
||||||
|
|
||||||
// Act/Assert: Task can be failed
|
|
||||||
runtime.EndInvokeJS(
|
|
||||||
runtime.BeginInvokeCalls[1].AsyncHandle,
|
|
||||||
/* succeeded: */ true,
|
|
||||||
ref reader);
|
|
||||||
Assert.False(unrelatedTask.IsCompleted);
|
|
||||||
|
|
||||||
return AssertTask();
|
|
||||||
|
|
||||||
async Task AssertTask()
|
|
||||||
{
|
|
||||||
var jsException = await Assert.ThrowsAsync<JSException>(() => task);
|
|
||||||
Assert.IsAssignableFrom<JsonException>(jsException.InnerException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public Task CompletingSameAsyncCallMoreThanOnce_IgnoresSecondResultAsync()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var runtime = new TestJSRuntime();
|
|
||||||
|
|
||||||
// Act/Assert
|
|
||||||
var task = runtime.InvokeAsync<string>("test identifier", Array.Empty<object>());
|
|
||||||
var asyncHandle = runtime.BeginInvokeCalls[0].AsyncHandle;
|
|
||||||
var firstReader = new Utf8JsonReader(Encoding.UTF8.GetBytes("\"Some data\""));
|
|
||||||
var secondReader = new Utf8JsonReader(Encoding.UTF8.GetBytes("\"Exception\""));
|
|
||||||
|
|
||||||
runtime.EndInvokeJS(asyncHandle, true, ref firstReader);
|
|
||||||
runtime.EndInvokeJS(asyncHandle, false, ref secondReader);
|
|
||||||
|
|
||||||
return AssertTask();
|
|
||||||
|
|
||||||
async Task AssertTask()
|
|
||||||
{
|
|
||||||
var result = await task;
|
|
||||||
Assert.Equal("Some data", result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void SerializesDotNetObjectWrappersInKnownFormat()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var runtime = new TestJSRuntime();
|
|
||||||
JSRuntime.SetCurrentJSRuntime(runtime);
|
|
||||||
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 = DotNetObjectRef.Create(obj1);
|
|
||||||
var obj1DifferentRef = DotNetObjectRef.Create(obj1);
|
|
||||||
runtime.InvokeAsync<object>("test identifier",
|
|
||||||
obj1Ref,
|
|
||||||
new Dictionary<string, object>
|
|
||||||
{
|
|
||||||
{ "obj2", DotNetObjectRef.Create(obj2) },
|
|
||||||
{ "obj3", DotNetObjectRef.Create(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\":3},\"obj3\":{\"__dotNetObject\":4},\"obj1SameRef\":{\"__dotNetObject\":1},\"obj1DifferentRef\":{\"__dotNetObject\":2}}]", call.ArgsJson);
|
|
||||||
|
|
||||||
// Assert: Objects were tracked
|
|
||||||
Assert.Same(obj1, runtime.ObjectRefManager.FindDotNetObject(1).Value);
|
|
||||||
Assert.Same(obj1, runtime.ObjectRefManager.FindDotNetObject(2).Value);
|
|
||||||
Assert.Same(obj2, runtime.ObjectRefManager.FindDotNetObject(3).Value);
|
|
||||||
Assert.Same(obj3, runtime.ObjectRefManager.FindDotNetObject(4).Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CanSanitizeDotNetInteropExceptions()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
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 " +
|
|
||||||
"display more detailed information about the error that occurred.";
|
|
||||||
|
|
||||||
var runtime = new TestJSRuntime()
|
|
||||||
{
|
|
||||||
OnDotNetException = (e, a, m) => new JSError { Message = GetMessage(a, m) }
|
|
||||||
};
|
|
||||||
|
|
||||||
var exception = new Exception("Some really sensitive data in here");
|
|
||||||
|
|
||||||
// Act
|
|
||||||
runtime.EndInvokeDotNet("0", false, exception, "Assembly", "Method", 0);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var call = runtime.EndInvokeDotNetCalls.Single();
|
|
||||||
Assert.Equal("0", call.CallId);
|
|
||||||
Assert.False(call.Success);
|
|
||||||
var jsError = Assert.IsType<JSError>(call.ResultOrError);
|
|
||||||
Assert.Equal(expectedMessage, jsError.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class JSError
|
|
||||||
{
|
|
||||||
public string Message { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestPoco
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestJSRuntime : JSRuntimeBase
|
|
||||||
{
|
|
||||||
public List<BeginInvokeAsyncArgs> BeginInvokeCalls = new List<BeginInvokeAsyncArgs>();
|
|
||||||
public List<EndInvokeDotNetArgs> EndInvokeDotNetCalls = new List<EndInvokeDotNetArgs>();
|
|
||||||
|
|
||||||
public TimeSpan? DefaultTimeout
|
|
||||||
{
|
|
||||||
set
|
|
||||||
{
|
|
||||||
base.DefaultAsyncTimeout = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BeginInvokeAsyncArgs
|
|
||||||
{
|
|
||||||
public long AsyncHandle { get; set; }
|
|
||||||
public string Identifier { get; set; }
|
|
||||||
public string ArgsJson { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class EndInvokeDotNetArgs
|
|
||||||
{
|
|
||||||
public string CallId { get; set; }
|
|
||||||
public bool Success { get; set; }
|
|
||||||
public object ResultOrError { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public Func<Exception, string, string, object> OnDotNetException { get; set; }
|
|
||||||
|
|
||||||
protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId)
|
|
||||||
{
|
|
||||||
if (OnDotNetException != null && !success)
|
|
||||||
{
|
|
||||||
resultOrError = OnDotNetException(resultOrError as Exception, assemblyName, methodIdentifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
EndInvokeDotNetCalls.Add(new EndInvokeDotNetArgs
|
|
||||||
{
|
|
||||||
CallId = callId,
|
|
||||||
Success = success,
|
|
||||||
ResultOrError = resultOrError
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
|
||||||
{
|
|
||||||
BeginInvokeCalls.Add(new BeginInvokeAsyncArgs
|
|
||||||
{
|
|
||||||
AsyncHandle = asyncHandle,
|
|
||||||
Identifier = identifier,
|
|
||||||
ArgsJson = argsJson,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,181 @@
|
||||||
|
// 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;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.JSInterop
|
||||||
|
{
|
||||||
|
public class JSRuntimeExtensionsTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeAsync_WithParamsArgs()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var method = "someMethod";
|
||||||
|
var expected = new[] { "a", "b" };
|
||||||
|
var jsRuntime = new Mock<IJSRuntime>(MockBehavior.Strict);
|
||||||
|
jsRuntime.Setup(s => s.InvokeAsync<string>(method, It.IsAny<object[]>()))
|
||||||
|
.Callback<string, object[]>((method, args) =>
|
||||||
|
{
|
||||||
|
Assert.Equal(expected, args);
|
||||||
|
})
|
||||||
|
.Returns(new ValueTask<string>("Hello"))
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await jsRuntime.Object.InvokeAsync<string>(method, "a", "b");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("Hello", result);
|
||||||
|
jsRuntime.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeAsync_WithParamsArgsAndCancellationToken()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var method = "someMethod";
|
||||||
|
var expected = new[] { "a", "b" };
|
||||||
|
var cancellationToken = new CancellationToken();
|
||||||
|
var jsRuntime = new Mock<IJSRuntime>(MockBehavior.Strict);
|
||||||
|
jsRuntime.Setup(s => s.InvokeAsync<string>(method, cancellationToken, It.IsAny<object[]>()))
|
||||||
|
.Callback<string, CancellationToken, object[]>((method, cts, args) =>
|
||||||
|
{
|
||||||
|
Assert.Equal(expected, args);
|
||||||
|
})
|
||||||
|
.Returns(new ValueTask<string>("Hello"))
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await jsRuntime.Object.InvokeAsync<string>(method, cancellationToken, "a", "b");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("Hello", result);
|
||||||
|
jsRuntime.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeVoidAsync_WithoutCancellationToken()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var method = "someMethod";
|
||||||
|
var args = new[] { "a", "b" };
|
||||||
|
var jsRuntime = new Mock<IJSRuntime>(MockBehavior.Strict);
|
||||||
|
jsRuntime.Setup(s => s.InvokeAsync<object>(method, args)).Returns(new ValueTask<object>(new object()));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await jsRuntime.Object.InvokeVoidAsync(method, args);
|
||||||
|
|
||||||
|
jsRuntime.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeVoidAsync_WithCancellationToken()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var method = "someMethod";
|
||||||
|
var args = new[] { "a", "b" };
|
||||||
|
var jsRuntime = new Mock<IJSRuntime>(MockBehavior.Strict);
|
||||||
|
jsRuntime.Setup(s => s.InvokeAsync<object>(method, It.IsAny<CancellationToken>(), args)).Returns(new ValueTask<object>(new object()));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await jsRuntime.Object.InvokeVoidAsync(method, new CancellationToken(), args);
|
||||||
|
|
||||||
|
jsRuntime.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeAsync_WithTimeout()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Hello";
|
||||||
|
var method = "someMethod";
|
||||||
|
var args = new[] { "a", "b" };
|
||||||
|
var jsRuntime = new Mock<IJSRuntime>(MockBehavior.Strict);
|
||||||
|
jsRuntime.Setup(s => s.InvokeAsync<string>(method, It.IsAny<CancellationToken>(), args))
|
||||||
|
.Callback<string, CancellationToken, object[]>((method, cts, args) =>
|
||||||
|
{
|
||||||
|
// There isn't a very good way to test when the cts will cancel. We'll just verify that
|
||||||
|
// it'll get cancelled eventually.
|
||||||
|
Assert.True(cts.CanBeCanceled);
|
||||||
|
})
|
||||||
|
.Returns(new ValueTask<string>(expected));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await jsRuntime.Object.InvokeAsync<string>(method, TimeSpan.FromMinutes(5), args);
|
||||||
|
|
||||||
|
Assert.Equal(expected, result);
|
||||||
|
jsRuntime.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeAsync_WithInfiniteTimeout()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "Hello";
|
||||||
|
var method = "someMethod";
|
||||||
|
var args = new[] { "a", "b" };
|
||||||
|
var jsRuntime = new Mock<IJSRuntime>(MockBehavior.Strict);
|
||||||
|
jsRuntime.Setup(s => s.InvokeAsync<string>(method, It.IsAny<CancellationToken>(), args))
|
||||||
|
.Callback<string, CancellationToken, object[]>((method, cts, args) =>
|
||||||
|
{
|
||||||
|
Assert.False(cts.CanBeCanceled);
|
||||||
|
Assert.True(cts == CancellationToken.None);
|
||||||
|
})
|
||||||
|
.Returns(new ValueTask<string>(expected));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await jsRuntime.Object.InvokeAsync<string>(method, Timeout.InfiniteTimeSpan, args);
|
||||||
|
|
||||||
|
Assert.Equal(expected, result);
|
||||||
|
jsRuntime.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeVoidAsync_WithTimeout()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var method = "someMethod";
|
||||||
|
var args = new[] { "a", "b" };
|
||||||
|
var jsRuntime = new Mock<IJSRuntime>(MockBehavior.Strict);
|
||||||
|
jsRuntime.Setup(s => s.InvokeAsync<object>(method, It.IsAny<CancellationToken>(), args))
|
||||||
|
.Callback<string, CancellationToken, object[]>((method, cts, args) =>
|
||||||
|
{
|
||||||
|
// There isn't a very good way to test when the cts will cancel. We'll just verify that
|
||||||
|
// it'll get cancelled eventually.
|
||||||
|
Assert.True(cts.CanBeCanceled);
|
||||||
|
})
|
||||||
|
.Returns(new ValueTask<object>(new object()));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await jsRuntime.Object.InvokeVoidAsync(method, TimeSpan.FromMinutes(5), args);
|
||||||
|
|
||||||
|
jsRuntime.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeVoidAsync_WithInfiniteTimeout()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var method = "someMethod";
|
||||||
|
var args = new[] { "a", "b" };
|
||||||
|
var jsRuntime = new Mock<IJSRuntime>(MockBehavior.Strict);
|
||||||
|
jsRuntime.Setup(s => s.InvokeAsync<object>(method, It.IsAny<CancellationToken>(), args))
|
||||||
|
.Callback<string, CancellationToken, object[]>((method, cts, args) =>
|
||||||
|
{
|
||||||
|
Assert.False(cts.CanBeCanceled);
|
||||||
|
Assert.True(cts == CancellationToken.None);
|
||||||
|
})
|
||||||
|
.Returns(new ValueTask<object>(new object()));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await jsRuntime.Object.InvokeVoidAsync(method, Timeout.InfiniteTimeSpan, args);
|
||||||
|
|
||||||
|
jsRuntime.Verify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,20 +4,23 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop.Tests
|
namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
public class JSRuntimeTest
|
public class JSRuntimeTest
|
||||||
{
|
{
|
||||||
|
#region this will be removed eventually
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CanHaveDistinctJSRuntimeInstancesInEachAsyncContext()
|
public async Task CanHaveDistinctJSRuntimeInstancesInEachAsyncContext()
|
||||||
{
|
{
|
||||||
var tasks = Enumerable.Range(0, 20).Select(async _ =>
|
var tasks = Enumerable.Range(0, 20).Select(async _ =>
|
||||||
{
|
{
|
||||||
var jsRuntime = new FakeJSRuntime();
|
var jsRuntime = new TestJSRuntime();
|
||||||
JSRuntime.SetCurrentJSRuntime(jsRuntime);
|
JSRuntime.SetCurrentJSRuntime(jsRuntime);
|
||||||
await Task.Delay(50).ConfigureAwait(false);
|
await Task.Delay(50).ConfigureAwait(false);
|
||||||
Assert.Same(jsRuntime, JSRuntime.Current);
|
Assert.Same(jsRuntime, JSRuntime.Current);
|
||||||
|
|
@ -26,14 +29,377 @@ namespace Microsoft.JSInterop.Tests
|
||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
Assert.Null(JSRuntime.Current);
|
Assert.Null(JSRuntime.Current);
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
private class FakeJSRuntime : IJSRuntime
|
[Fact]
|
||||||
|
public void DispatchesAsyncCallsWithDistinctAsyncHandles()
|
||||||
{
|
{
|
||||||
public Task<T> InvokeAsync<T>(string identifier, params object[] args)
|
// Arrange
|
||||||
=> throw new NotImplementedException();
|
var runtime = new TestJSRuntime();
|
||||||
|
|
||||||
public Task<TValue> InvokeAsync<TValue>(string identifier, IEnumerable<object> args, CancellationToken cancellationToken = default) =>
|
// Act
|
||||||
throw new NotImplementedException();
|
runtime.InvokeAsync<object>("test identifier 1", "arg1", 123, true);
|
||||||
|
runtime.InvokeAsync<object>("test identifier 2", "some other arg");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(runtime.BeginInvokeCalls,
|
||||||
|
call =>
|
||||||
|
{
|
||||||
|
Assert.Equal("test identifier 1", call.Identifier);
|
||||||
|
Assert.Equal("[\"arg1\",123,true]", call.ArgsJson);
|
||||||
|
},
|
||||||
|
call =>
|
||||||
|
{
|
||||||
|
Assert.Equal("test identifier 2", call.Identifier);
|
||||||
|
Assert.Equal("[\"some other arg\"]", call.ArgsJson);
|
||||||
|
Assert.NotEqual(runtime.BeginInvokeCalls[0].AsyncHandle, call.AsyncHandle);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeAsync_CancelsAsyncTask_AfterDefaultTimeout()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var runtime = new TestJSRuntime();
|
||||||
|
runtime.DefaultTimeout = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var task = runtime.InvokeAsync<object>("test identifier 1", "arg1", 123, true);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await Assert.ThrowsAsync<TaskCanceledException>(async () => await task);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvokeAsync_CompletesSuccessfullyBeforeTimeout()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var runtime = new TestJSRuntime();
|
||||||
|
runtime.DefaultTimeout = TimeSpan.FromSeconds(10);
|
||||||
|
var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes("null"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var task = runtime.InvokeAsync<object>("test identifier 1", "arg1", 123, true);
|
||||||
|
|
||||||
|
runtime.EndInvokeJS(2, succeeded: true, ref reader);
|
||||||
|
|
||||||
|
Assert.True(task.IsCompletedSuccessfully);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeAsync_CancelsAsyncTasksWhenCancellationTokenFires()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using var cts = new CancellationTokenSource();
|
||||||
|
var runtime = new TestJSRuntime();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var task = runtime.InvokeAsync<object>("test identifier 1", cts.Token, new object[] { "arg1", 123, true });
|
||||||
|
|
||||||
|
cts.Cancel();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await Assert.ThrowsAsync<TaskCanceledException>(async () => await task);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeAsync_DoesNotStartWorkWhenCancellationHasBeenRequested()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using var cts = new CancellationTokenSource();
|
||||||
|
cts.Cancel();
|
||||||
|
var runtime = new TestJSRuntime();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var task = runtime.InvokeAsync<object>("test identifier 1", cts.Token, new object[] { "arg1", 123, true });
|
||||||
|
|
||||||
|
cts.Cancel();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await Assert.ThrowsAsync<TaskCanceledException>(async () => await task);
|
||||||
|
Assert.Empty(runtime.BeginInvokeCalls);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanCompleteAsyncCallsAsSuccess()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var runtime = new TestJSRuntime();
|
||||||
|
|
||||||
|
// Act/Assert: Tasks not initially completed
|
||||||
|
var unrelatedTask = runtime.InvokeAsync<string>("unrelated call", Array.Empty<object>());
|
||||||
|
var task = runtime.InvokeAsync<string>("test identifier", Array.Empty<object>());
|
||||||
|
Assert.False(unrelatedTask.IsCompleted);
|
||||||
|
Assert.False(task.IsCompleted);
|
||||||
|
var bytes = Encoding.UTF8.GetBytes("\"my result\"");
|
||||||
|
var reader = new Utf8JsonReader(bytes);
|
||||||
|
|
||||||
|
// Act/Assert: Task can be completed
|
||||||
|
runtime.EndInvokeJS(
|
||||||
|
runtime.BeginInvokeCalls[1].AsyncHandle,
|
||||||
|
/* succeeded: */ true,
|
||||||
|
ref reader);
|
||||||
|
Assert.False(unrelatedTask.IsCompleted);
|
||||||
|
Assert.True(task.IsCompleted);
|
||||||
|
Assert.Equal("my result", task.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanCompleteAsyncCallsWithComplexType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var runtime = new TestJSRuntime();
|
||||||
|
|
||||||
|
var task = runtime.InvokeAsync<TestPoco>("test identifier", Array.Empty<object>());
|
||||||
|
var bytes = Encoding.UTF8.GetBytes("{\"id\":10, \"name\": \"Test\"}");
|
||||||
|
var reader = new Utf8JsonReader(bytes);
|
||||||
|
|
||||||
|
// Act/Assert: Task can be completed
|
||||||
|
runtime.EndInvokeJS(
|
||||||
|
runtime.BeginInvokeCalls[0].AsyncHandle,
|
||||||
|
/* succeeded: */ true,
|
||||||
|
ref reader);
|
||||||
|
Assert.True(task.IsCompleted);
|
||||||
|
var poco = task.Result;
|
||||||
|
Assert.Equal(10, poco.Id);
|
||||||
|
Assert.Equal("Test", poco.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanCompleteAsyncCallsWithComplexTypeUsingPropertyCasing()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var runtime = new TestJSRuntime();
|
||||||
|
|
||||||
|
var task = runtime.InvokeAsync<TestPoco>("test identifier", Array.Empty<object>());
|
||||||
|
var bytes = Encoding.UTF8.GetBytes("{\"Id\":10, \"Name\": \"Test\"}");
|
||||||
|
var reader = new Utf8JsonReader(bytes);
|
||||||
|
reader.Read();
|
||||||
|
|
||||||
|
// Act/Assert: Task can be completed
|
||||||
|
runtime.EndInvokeJS(
|
||||||
|
runtime.BeginInvokeCalls[0].AsyncHandle,
|
||||||
|
/* succeeded: */ true,
|
||||||
|
ref reader);
|
||||||
|
Assert.True(task.IsCompleted);
|
||||||
|
var poco = task.Result;
|
||||||
|
Assert.Equal(10, poco.Id);
|
||||||
|
Assert.Equal("Test", poco.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanCompleteAsyncCallsAsFailure()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var runtime = new TestJSRuntime();
|
||||||
|
|
||||||
|
// Act/Assert: Tasks not initially completed
|
||||||
|
var unrelatedTask = runtime.InvokeAsync<string>("unrelated call", Array.Empty<object>());
|
||||||
|
var task = runtime.InvokeAsync<string>("test identifier", Array.Empty<object>());
|
||||||
|
Assert.False(unrelatedTask.IsCompleted);
|
||||||
|
Assert.False(task.IsCompleted);
|
||||||
|
var bytes = Encoding.UTF8.GetBytes("\"This is a test exception\"");
|
||||||
|
var reader = new Utf8JsonReader(bytes);
|
||||||
|
reader.Read();
|
||||||
|
|
||||||
|
// Act/Assert: Task can be failed
|
||||||
|
runtime.EndInvokeJS(
|
||||||
|
runtime.BeginInvokeCalls[1].AsyncHandle,
|
||||||
|
/* succeeded: */ false,
|
||||||
|
ref reader);
|
||||||
|
Assert.False(unrelatedTask.IsCompleted);
|
||||||
|
Assert.True(task.IsCompleted);
|
||||||
|
|
||||||
|
var exception = Assert.IsType<AggregateException>(task.AsTask().Exception);
|
||||||
|
var jsException = Assert.IsType<JSException>(exception.InnerException);
|
||||||
|
Assert.Equal("This is a test exception", jsException.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public Task CanCompleteAsyncCallsWithErrorsDuringDeserialization()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var runtime = new TestJSRuntime();
|
||||||
|
|
||||||
|
// Act/Assert: Tasks not initially completed
|
||||||
|
var unrelatedTask = runtime.InvokeAsync<string>("unrelated call", Array.Empty<object>());
|
||||||
|
var task = runtime.InvokeAsync<int>("test identifier", Array.Empty<object>());
|
||||||
|
Assert.False(unrelatedTask.IsCompleted);
|
||||||
|
Assert.False(task.IsCompleted);
|
||||||
|
var bytes = Encoding.UTF8.GetBytes("Not a string");
|
||||||
|
var reader = new Utf8JsonReader(bytes);
|
||||||
|
|
||||||
|
// Act/Assert: Task can be failed
|
||||||
|
runtime.EndInvokeJS(
|
||||||
|
runtime.BeginInvokeCalls[1].AsyncHandle,
|
||||||
|
/* succeeded: */ true,
|
||||||
|
ref reader);
|
||||||
|
Assert.False(unrelatedTask.IsCompleted);
|
||||||
|
|
||||||
|
return AssertTask();
|
||||||
|
|
||||||
|
async Task AssertTask()
|
||||||
|
{
|
||||||
|
var jsException = await Assert.ThrowsAsync<JSException>(async () => await task);
|
||||||
|
Assert.IsAssignableFrom<JsonException>(jsException.InnerException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public Task CompletingSameAsyncCallMoreThanOnce_IgnoresSecondResultAsync()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var runtime = new TestJSRuntime();
|
||||||
|
|
||||||
|
// Act/Assert
|
||||||
|
var task = runtime.InvokeAsync<string>("test identifier", Array.Empty<object>());
|
||||||
|
var asyncHandle = runtime.BeginInvokeCalls[0].AsyncHandle;
|
||||||
|
var firstReader = new Utf8JsonReader(Encoding.UTF8.GetBytes("\"Some data\""));
|
||||||
|
var secondReader = new Utf8JsonReader(Encoding.UTF8.GetBytes("\"Exception\""));
|
||||||
|
|
||||||
|
runtime.EndInvokeJS(asyncHandle, true, ref firstReader);
|
||||||
|
runtime.EndInvokeJS(asyncHandle, false, ref secondReader);
|
||||||
|
|
||||||
|
return AssertTask();
|
||||||
|
|
||||||
|
async Task AssertTask()
|
||||||
|
{
|
||||||
|
var result = await task;
|
||||||
|
Assert.Equal("Some data", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SerializesDotNetObjectWrappersInKnownFormat()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var runtime = new TestJSRuntime();
|
||||||
|
JSRuntime.SetCurrentJSRuntime(runtime);
|
||||||
|
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 = DotNetObjectReference.Create(obj1);
|
||||||
|
var obj1DifferentRef = DotNetObjectReference.Create(obj1);
|
||||||
|
runtime.InvokeAsync<object>("test identifier",
|
||||||
|
obj1Ref,
|
||||||
|
new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "obj2", DotNetObjectReference.Create(obj2) },
|
||||||
|
{ "obj3", DotNetObjectReference.Create(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\":3},\"obj3\":{\"__dotNetObject\":4},\"obj1SameRef\":{\"__dotNetObject\":1},\"obj1DifferentRef\":{\"__dotNetObject\":2}}]", call.ArgsJson);
|
||||||
|
|
||||||
|
// Assert: Objects were tracked
|
||||||
|
Assert.Same(obj1Ref, runtime.ObjectRefManager.FindDotNetObject(1));
|
||||||
|
Assert.Same(obj1, obj1Ref.Value);
|
||||||
|
Assert.NotSame(obj1Ref, runtime.ObjectRefManager.FindDotNetObject(2));
|
||||||
|
Assert.Same(obj1, runtime.ObjectRefManager.FindDotNetObject(2).Value);
|
||||||
|
Assert.Same(obj2, runtime.ObjectRefManager.FindDotNetObject(3).Value);
|
||||||
|
Assert.Same(obj3, runtime.ObjectRefManager.FindDotNetObject(4).Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanSanitizeDotNetInteropExceptions()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
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 " +
|
||||||
|
"display more detailed information about the error that occurred.";
|
||||||
|
|
||||||
|
var runtime = new TestJSRuntime()
|
||||||
|
{
|
||||||
|
OnDotNetException = (e, a, m) => new JSError { Message = GetMessage(a, m) }
|
||||||
|
};
|
||||||
|
|
||||||
|
var exception = new Exception("Some really sensitive data in here");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
runtime.EndInvokeDotNet("0", false, exception, "Assembly", "Method", 0);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var call = runtime.EndInvokeDotNetCalls.Single();
|
||||||
|
Assert.Equal("0", call.CallId);
|
||||||
|
Assert.False(call.Success);
|
||||||
|
var jsError = Assert.IsType<JSError>(call.ResultOrError);
|
||||||
|
Assert.Equal(expectedMessage, jsError.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class JSError
|
||||||
|
{
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestPoco
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestJSRuntime : JSRuntime
|
||||||
|
{
|
||||||
|
public List<BeginInvokeAsyncArgs> BeginInvokeCalls = new List<BeginInvokeAsyncArgs>();
|
||||||
|
public List<EndInvokeDotNetArgs> EndInvokeDotNetCalls = new List<EndInvokeDotNetArgs>();
|
||||||
|
|
||||||
|
public TimeSpan? DefaultTimeout
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
base.DefaultAsyncTimeout = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BeginInvokeAsyncArgs
|
||||||
|
{
|
||||||
|
public long AsyncHandle { get; set; }
|
||||||
|
public string Identifier { get; set; }
|
||||||
|
public string ArgsJson { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EndInvokeDotNetArgs
|
||||||
|
{
|
||||||
|
public string CallId { get; set; }
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public object ResultOrError { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Func<Exception, string, string, object> OnDotNetException { get; set; }
|
||||||
|
|
||||||
|
protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId)
|
||||||
|
{
|
||||||
|
if (OnDotNetException != null && !success)
|
||||||
|
{
|
||||||
|
resultOrError = OnDotNetException(resultOrError as Exception, assemblyName, methodIdentifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
EndInvokeDotNetCalls.Add(new EndInvokeDotNetArgs
|
||||||
|
{
|
||||||
|
CallId = callId,
|
||||||
|
Success = success,
|
||||||
|
ResultOrError = resultOrError
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
||||||
|
{
|
||||||
|
BeginInvokeCalls.Add(new BeginInvokeAsyncArgs
|
||||||
|
{
|
||||||
|
AsyncHandle = asyncHandle,
|
||||||
|
Identifier = identifier,
|
||||||
|
ArgsJson = argsJson,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop
|
namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
internal class TestJSRuntime : JSRuntimeBase
|
internal class TestJSRuntime : JSRuntime
|
||||||
{
|
{
|
||||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
||||||
{
|
{
|
||||||
|
|
@ -18,7 +18,7 @@ namespace Microsoft.JSInterop
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task WithJSRuntime(Action<JSRuntimeBase> testCode)
|
public static async Task WithJSRuntime(Action<JSRuntime> testCode)
|
||||||
{
|
{
|
||||||
// Since the tests rely on the asynclocal JSRuntime.Current, ensure we
|
// Since the tests rely on the asynclocal JSRuntime.Current, ensure we
|
||||||
// are on a distinct async context with a non-null JSRuntime.Current
|
// are on a distinct async context with a non-null JSRuntime.Current
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
namespace Mono.WebAssembly.Interop
|
namespace Mono.WebAssembly.Interop
|
||||||
{
|
{
|
||||||
public partial class MonoWebAssemblyJSRuntime : Microsoft.JSInterop.JSInProcessRuntimeBase
|
public partial class MonoWebAssemblyJSRuntime : Microsoft.JSInterop.JSInProcessRuntime
|
||||||
{
|
{
|
||||||
public MonoWebAssemblyJSRuntime() { }
|
public MonoWebAssemblyJSRuntime() { }
|
||||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) { }
|
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) { }
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Runtime.ExceptionServices;
|
using System.Runtime.ExceptionServices;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
using Microsoft.JSInterop.Infrastructure;
|
||||||
using WebAssembly.JSInterop;
|
using WebAssembly.JSInterop;
|
||||||
|
|
||||||
namespace Mono.WebAssembly.Interop
|
namespace Mono.WebAssembly.Interop
|
||||||
|
|
@ -13,7 +14,7 @@ namespace Mono.WebAssembly.Interop
|
||||||
/// Provides methods for invoking JavaScript functions for applications running
|
/// Provides methods for invoking JavaScript functions for applications running
|
||||||
/// on the Mono WebAssembly runtime.
|
/// on the Mono WebAssembly runtime.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class MonoWebAssemblyJSRuntime : JSInProcessRuntimeBase
|
public class MonoWebAssemblyJSRuntime : JSInProcessRuntime
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override string InvokeJS(string identifier, string argsJson)
|
protected override string InvokeJS(string identifier, string argsJson)
|
||||||
|
|
@ -37,7 +38,7 @@ namespace Mono.WebAssembly.Interop
|
||||||
|
|
||||||
// Invoked via Mono's JS interop mechanism (invoke_method)
|
// Invoked via Mono's JS interop mechanism (invoke_method)
|
||||||
private static void EndInvokeJS(string argsJson)
|
private static void EndInvokeJS(string argsJson)
|
||||||
=> DotNetDispatcher.EndInvoke(argsJson);
|
=> DotNetDispatcher.EndInvokeJS(argsJson);
|
||||||
|
|
||||||
// Invoked via Mono's JS interop mechanism (invoke_method)
|
// Invoked via Mono's JS interop mechanism (invoke_method)
|
||||||
private static void BeginInvokeDotNet(string callId, string assemblyNameOrDotNetObjectId, string methodIdentifier, string argsJson)
|
private static void BeginInvokeDotNet(string callId, string assemblyNameOrDotNetObjectId, string methodIdentifier, string argsJson)
|
||||||
|
|
@ -58,7 +59,7 @@ namespace Mono.WebAssembly.Interop
|
||||||
assemblyName = assemblyNameOrDotNetObjectId;
|
assemblyName = assemblyNameOrDotNetObjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson);
|
DotNetDispatcher.BeginInvokeDotNet(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void EndInvokeDotNet(
|
protected override void EndInvokeDotNet(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue