Merge branch 'release/3.0' => 'master' (dotnet/extensions#2192)
\n\nCommit migrated from 2dd5bf89b4
This commit is contained in:
commit
f2d6b04301
|
|
@ -55,7 +55,7 @@ module DotNet {
|
|||
return invokePossibleInstanceMethodAsync(assemblyName, methodIdentifier, null, args);
|
||||
}
|
||||
|
||||
function invokePossibleInstanceMethod<T>(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[]): T {
|
||||
function invokePossibleInstanceMethod<T>(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[] | null): T {
|
||||
const dispatcher = getRequiredDispatcher();
|
||||
if (dispatcher.invokeDotNetFromJS) {
|
||||
const argsJson = JSON.stringify(args, argReplacer);
|
||||
|
|
@ -66,7 +66,11 @@ module DotNet {
|
|||
}
|
||||
}
|
||||
|
||||
function invokePossibleInstanceMethodAsync<T>(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[]): Promise<T> {
|
||||
function invokePossibleInstanceMethodAsync<T>(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[] | null): Promise<T> {
|
||||
if (assemblyName && dotNetObjectId) {
|
||||
throw new Error(`For instance method calls, assemblyName should be null. Received '${assemblyName}'.`) ;
|
||||
}
|
||||
|
||||
const asyncCallId = nextAsyncCallId++;
|
||||
const resultPromise = new Promise<T>((resolve, reject) => {
|
||||
pendingAsyncCalls[asyncCallId] = { resolve, reject };
|
||||
|
|
@ -269,10 +273,7 @@ module DotNet {
|
|||
}
|
||||
|
||||
public dispose() {
|
||||
const promise = invokeMethodAsync<any>(
|
||||
'Microsoft.JSInterop',
|
||||
'DotNetDispatcher.ReleaseDotNetObject',
|
||||
this._id);
|
||||
const promise = invokePossibleInstanceMethodAsync<any>(null, '__Dispose', this._id, null);
|
||||
promise.catch(error => console.error(error));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,22 +3,14 @@
|
|||
|
||||
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 void EndInvoke(string arguments) { }
|
||||
public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; }
|
||||
[Microsoft.JSInterop.JSInvokableAttribute("DotNetDispatcher.ReleaseDotNetObject")]
|
||||
public static void ReleaseDotNetObject(long dotNetObjectId) { }
|
||||
public static Microsoft.JSInterop.DotNetObjectReference<TValue> Create<TValue>(TValue value) where TValue : class { 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; }
|
||||
}
|
||||
public sealed partial class DotNetObjectRef<TValue> : System.IDisposable where TValue : class
|
||||
{
|
||||
internal DotNetObjectRef() { }
|
||||
public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
internal DotNetObjectReference() { }
|
||||
public TValue Value { get { throw null; } }
|
||||
public void Dispose() { }
|
||||
}
|
||||
public partial interface IJSInProcessRuntime : Microsoft.JSInterop.IJSRuntime
|
||||
|
|
@ -27,38 +19,84 @@ namespace Microsoft.JSInterop
|
|||
}
|
||||
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.Task<TValue> InvokeAsync<TValue>(string identifier, params object[] args);
|
||||
System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args);
|
||||
System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, System.Threading.CancellationToken cancellationToken, object[] args);
|
||||
}
|
||||
public partial class JSException : System.Exception
|
||||
{
|
||||
public JSException(string message) { }
|
||||
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);
|
||||
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)]
|
||||
public partial class JSInvokableAttribute : System.Attribute
|
||||
public sealed partial class JSInvokableAttribute : System.Attribute
|
||||
{
|
||||
public JSInvokableAttribute() { }
|
||||
public JSInvokableAttribute(string identifier) { }
|
||||
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) { }
|
||||
}
|
||||
public abstract partial class JSRuntimeBase : Microsoft.JSInterop.IJSRuntime
|
||||
{
|
||||
protected JSRuntimeBase() { }
|
||||
protected JSRuntime() { }
|
||||
protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
protected internal System.Text.Json.JsonSerializerOptions JsonSerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson);
|
||||
protected internal abstract void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId);
|
||||
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.Task<T> InvokeAsync<T>(string identifier, params object[] args) { throw null; }
|
||||
protected internal abstract void EndInvokeDotNet(Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, in Microsoft.JSInterop.Infrastructure.DotNetInvocationResult invocationResult);
|
||||
public System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args) { throw null; }
|
||||
public System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, System.Threading.CancellationToken cancellationToken, object[] args) { throw null; }
|
||||
}
|
||||
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(Microsoft.JSInterop.JSRuntime jsRuntime, Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, string argsJson) { }
|
||||
public static void EndInvokeJS(Microsoft.JSInterop.JSRuntime jsRuntime, string arguments) { }
|
||||
public static string Invoke(Microsoft.JSInterop.JSRuntime jsRuntime, in Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, string argsJson) { throw null; }
|
||||
}
|
||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||
public readonly partial struct DotNetInvocationInfo
|
||||
{
|
||||
private readonly object _dummy;
|
||||
private readonly int _dummyPrimitive;
|
||||
public DotNetInvocationInfo(string assemblyName, string methodIdentifier, long dotNetObjectId, string callId) { throw null; }
|
||||
public string AssemblyName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public string CallId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public long DotNetObjectId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public string MethodIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
}
|
||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||
public readonly partial struct DotNetInvocationResult
|
||||
{
|
||||
private readonly object _dummy;
|
||||
private readonly int _dummyPrimitive;
|
||||
public DotNetInvocationResult(System.Exception exception, string errorKind) { throw null; }
|
||||
public DotNetInvocationResult(object result) { throw null; }
|
||||
public string ErrorKind { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public System.Exception Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public object Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public bool Success { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,22 +3,14 @@
|
|||
|
||||
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 void EndInvoke(string arguments) { }
|
||||
public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; }
|
||||
[Microsoft.JSInterop.JSInvokableAttribute("DotNetDispatcher.ReleaseDotNetObject")]
|
||||
public static void ReleaseDotNetObject(long dotNetObjectId) { }
|
||||
public static Microsoft.JSInterop.DotNetObjectReference<TValue> Create<TValue>(TValue value) where TValue : class { 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; }
|
||||
}
|
||||
public sealed partial class DotNetObjectRef<TValue> : System.IDisposable where TValue : class
|
||||
{
|
||||
internal DotNetObjectRef() { }
|
||||
public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
internal DotNetObjectReference() { }
|
||||
public TValue Value { get { throw null; } }
|
||||
public void Dispose() { }
|
||||
}
|
||||
public partial interface IJSInProcessRuntime : Microsoft.JSInterop.IJSRuntime
|
||||
|
|
@ -27,38 +19,84 @@ namespace Microsoft.JSInterop
|
|||
}
|
||||
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.Task<TValue> InvokeAsync<TValue>(string identifier, params object[] args);
|
||||
System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args);
|
||||
System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, System.Threading.CancellationToken cancellationToken, object[] args);
|
||||
}
|
||||
public partial class JSException : System.Exception
|
||||
{
|
||||
public JSException(string message) { }
|
||||
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);
|
||||
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)]
|
||||
public partial class JSInvokableAttribute : System.Attribute
|
||||
public sealed partial class JSInvokableAttribute : System.Attribute
|
||||
{
|
||||
public JSInvokableAttribute() { }
|
||||
public JSInvokableAttribute(string identifier) { }
|
||||
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) { }
|
||||
}
|
||||
public abstract partial class JSRuntimeBase : Microsoft.JSInterop.IJSRuntime
|
||||
{
|
||||
protected JSRuntimeBase() { }
|
||||
protected JSRuntime() { }
|
||||
protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
protected internal System.Text.Json.JsonSerializerOptions JsonSerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson);
|
||||
protected internal abstract void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId);
|
||||
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.Task<T> InvokeAsync<T>(string identifier, params object[] args) { throw null; }
|
||||
protected internal abstract void EndInvokeDotNet(Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, in Microsoft.JSInterop.Infrastructure.DotNetInvocationResult invocationResult);
|
||||
public System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args) { throw null; }
|
||||
public System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, System.Threading.CancellationToken cancellationToken, object[] args) { throw null; }
|
||||
}
|
||||
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(Microsoft.JSInterop.JSRuntime jsRuntime, Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, string argsJson) { }
|
||||
public static void EndInvokeJS(Microsoft.JSInterop.JSRuntime jsRuntime, string arguments) { }
|
||||
public static string Invoke(Microsoft.JSInterop.JSRuntime jsRuntime, in Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, string argsJson) { throw null; }
|
||||
}
|
||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||
public readonly partial struct DotNetInvocationInfo
|
||||
{
|
||||
private readonly object _dummy;
|
||||
private readonly int _dummyPrimitive;
|
||||
public DotNetInvocationInfo(string assemblyName, string methodIdentifier, long dotNetObjectId, string callId) { throw null; }
|
||||
public string AssemblyName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public string CallId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public long DotNetObjectId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public string MethodIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
}
|
||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||
public readonly partial struct DotNetInvocationResult
|
||||
{
|
||||
private readonly object _dummy;
|
||||
private readonly int _dummyPrimitive;
|
||||
public DotNetInvocationResult(System.Exception exception, string errorKind) { throw null; }
|
||||
public DotNetInvocationResult(object result) { throw null; }
|
||||
public string ErrorKind { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public System.Exception Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public object Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public bool Success { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,22 +3,14 @@
|
|||
|
||||
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 void EndInvoke(string arguments) { }
|
||||
public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; }
|
||||
[Microsoft.JSInterop.JSInvokableAttribute("DotNetDispatcher.ReleaseDotNetObject")]
|
||||
public static void ReleaseDotNetObject(long dotNetObjectId) { }
|
||||
public static Microsoft.JSInterop.DotNetObjectReference<TValue> Create<TValue>(TValue value) where TValue : class { 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; }
|
||||
}
|
||||
public sealed partial class DotNetObjectRef<TValue> : System.IDisposable where TValue : class
|
||||
{
|
||||
internal DotNetObjectRef() { }
|
||||
public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
internal DotNetObjectReference() { }
|
||||
public TValue Value { get { throw null; } }
|
||||
public void Dispose() { }
|
||||
}
|
||||
public partial interface IJSInProcessRuntime : Microsoft.JSInterop.IJSRuntime
|
||||
|
|
@ -27,38 +19,84 @@ namespace Microsoft.JSInterop
|
|||
}
|
||||
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.Task<TValue> InvokeAsync<TValue>(string identifier, params object[] args);
|
||||
System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args);
|
||||
System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, System.Threading.CancellationToken cancellationToken, object[] args);
|
||||
}
|
||||
public partial class JSException : System.Exception
|
||||
{
|
||||
public JSException(string message) { }
|
||||
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);
|
||||
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)]
|
||||
public partial class JSInvokableAttribute : System.Attribute
|
||||
public sealed partial class JSInvokableAttribute : System.Attribute
|
||||
{
|
||||
public JSInvokableAttribute() { }
|
||||
public JSInvokableAttribute(string identifier) { }
|
||||
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) { }
|
||||
}
|
||||
public abstract partial class JSRuntimeBase : Microsoft.JSInterop.IJSRuntime
|
||||
{
|
||||
protected JSRuntimeBase() { }
|
||||
protected JSRuntime() { }
|
||||
protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
protected internal System.Text.Json.JsonSerializerOptions JsonSerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson);
|
||||
protected internal abstract void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId);
|
||||
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.Task<T> InvokeAsync<T>(string identifier, params object[] args) { throw null; }
|
||||
protected internal abstract void EndInvokeDotNet(Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, in Microsoft.JSInterop.Infrastructure.DotNetInvocationResult invocationResult);
|
||||
public System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args) { throw null; }
|
||||
public System.Threading.Tasks.ValueTask<TValue> InvokeAsync<TValue>(string identifier, System.Threading.CancellationToken cancellationToken, object[] args) { throw null; }
|
||||
}
|
||||
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(Microsoft.JSInterop.JSRuntime jsRuntime, Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, string argsJson) { }
|
||||
public static void EndInvokeJS(Microsoft.JSInterop.JSRuntime jsRuntime, string arguments) { }
|
||||
public static string Invoke(Microsoft.JSInterop.JSRuntime jsRuntime, in Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, string argsJson) { throw null; }
|
||||
}
|
||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||
public readonly partial struct DotNetInvocationInfo
|
||||
{
|
||||
private readonly object _dummy;
|
||||
private readonly int _dummyPrimitive;
|
||||
public DotNetInvocationInfo(string assemblyName, string methodIdentifier, long dotNetObjectId, string callId) { throw null; }
|
||||
public string AssemblyName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public string CallId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public long DotNetObjectId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public string MethodIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
}
|
||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||
public readonly partial struct DotNetInvocationResult
|
||||
{
|
||||
private readonly object _dummy;
|
||||
private readonly int _dummyPrimitive;
|
||||
public DotNetInvocationResult(System.Exception exception, string errorKind) { throw null; }
|
||||
public DotNetInvocationResult(object result) { throw null; }
|
||||
public string ErrorKind { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public System.Exception Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public object Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public bool Success { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,51 +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.Threading;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
internal class DotNetObjectRefManager
|
||||
{
|
||||
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, object> _trackedRefsById = new ConcurrentDictionary<long, object>();
|
||||
|
||||
public static DotNetObjectRefManager Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!(JSRuntime.Current is JSRuntimeBase jsRuntimeBase))
|
||||
{
|
||||
throw new InvalidOperationException("JSRuntime must be set up correctly and must be an instance of JSRuntimeBase to use DotNetObjectRef.");
|
||||
}
|
||||
|
||||
return jsRuntimeBase.ObjectRefManager;
|
||||
}
|
||||
}
|
||||
|
||||
public long TrackObject(object dotNetObjectRef)
|
||||
{
|
||||
var dotNetObjectId = Interlocked.Increment(ref _nextId);
|
||||
_trackedRefsById[dotNetObjectId] = dotNetObjectRef;
|
||||
|
||||
return dotNetObjectId;
|
||||
}
|
||||
|
||||
public object FindDotNetObject(long dotNetObjectId)
|
||||
{
|
||||
return _trackedRefsById.TryGetValue(dotNetObjectId, out var dotNetObjectRef)
|
||||
? dotNetObjectRef
|
||||
: throw new ArgumentException($"There is no tracked object with id '{dotNetObjectId}'. Perhaps the DotNetObjectRef instance was already disposed.", nameof(dotNetObjectId));
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
/// <param name="dotNetObjectId">The ID of the <see cref="DotNetObjectRef{TValue}"/>.</param>
|
||||
public void ReleaseDotNetObject(long dotNetObjectId) => _trackedRefsById.TryRemove(dotNetObjectId, out _);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +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.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps a JS interop argument, indicating that the value should not be serialized as JSON
|
||||
/// but instead should be passed as a reference.
|
||||
///
|
||||
/// To avoid leaking memory, the reference must later be disposed by JS code or by .NET code.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The type of the value to wrap.</typeparam>
|
||||
[JsonConverter(typeof(DotNetObjectReferenceJsonConverterFactory))]
|
||||
public sealed class DotNetObjectRef<TValue> : IDotNetObjectRef, IDisposable where TValue : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="DotNetObjectRef{TValue}" />.
|
||||
/// </summary>
|
||||
/// <param name="objectId">The object Id.</param>
|
||||
/// <param name="value">The value to pass by reference.</param>
|
||||
internal DotNetObjectRef(long objectId, TValue value)
|
||||
{
|
||||
ObjectId = objectId;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object instance represented by this wrapper.
|
||||
/// </summary>
|
||||
public TValue Value { get; }
|
||||
|
||||
internal long ObjectId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Stops tracking this object reference, allowing it to be garbage collected
|
||||
/// (if there are no other references to it). Once the instance is disposed, it
|
||||
/// can no longer be used in interop calls from JavaScript code.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
DotNetObjectRefManager.Current.ReleaseDotNetObject(ObjectId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,19 +4,18 @@
|
|||
namespace Microsoft.JSInterop
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides convenience methods to produce a <see cref="DotNetObjectRef{TValue}" />.
|
||||
/// Provides convenience methods to produce a <see cref="DotNetObjectReference{TValue}" />.
|
||||
/// </summary>
|
||||
public static class DotNetObjectRef
|
||||
public static class DotNetObjectReference
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="DotNetObjectRef{TValue}" />.
|
||||
/// Creates a new instance of <see cref="DotNetObjectReference{TValue}" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The reference type to track.</param>
|
||||
/// <returns>An instance of <see cref="DotNetObjectRef{TValue}" />.</returns>
|
||||
public static DotNetObjectRef<TValue> Create<TValue>(TValue value) where TValue : class
|
||||
/// <returns>An instance of <see cref="DotNetObjectReference{TValue}" />.</returns>
|
||||
public static DotNetObjectReference<TValue> Create<TValue>(TValue value) where TValue : class
|
||||
{
|
||||
var objectId = DotNetObjectRefManager.Current.TrackObject(value);
|
||||
return new DotNetObjectRef<TValue>(objectId, value);
|
||||
return new DotNetObjectReference<TValue>(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
// 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.Diagnostics;
|
||||
using Microsoft.JSInterop.Infrastructure;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps a JS interop argument, indicating that the value should not be serialized as JSON
|
||||
/// but instead should be passed as a reference.
|
||||
///
|
||||
/// To avoid leaking memory, the reference must later be disposed by JS code or by .NET code.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The type of the value to wrap.</typeparam>
|
||||
public sealed class DotNetObjectReference<TValue> : IDotNetObjectReference, IDisposable where TValue : class
|
||||
{
|
||||
private readonly TValue _value;
|
||||
private long _objectId;
|
||||
private JSRuntime _jsRuntime;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="DotNetObjectReference{TValue}" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to pass by reference.</param>
|
||||
internal DotNetObjectReference(TValue value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object instance represented by this wrapper.
|
||||
/// </summary>
|
||||
public TValue Value
|
||||
{
|
||||
get
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return _value;
|
||||
}
|
||||
}
|
||||
|
||||
internal long ObjectId
|
||||
{
|
||||
get
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Debug.Assert(_objectId != 0, "Accessing ObjectId without tracking is always incorrect.");
|
||||
|
||||
return _objectId;
|
||||
}
|
||||
set
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_objectId = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal JSRuntime JSRuntime
|
||||
{
|
||||
get
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return _jsRuntime;
|
||||
}
|
||||
set
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_jsRuntime = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object IDotNetObjectReference.Value => Value;
|
||||
|
||||
internal bool Disposed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stops tracking this object reference, allowing it to be garbage collected
|
||||
/// (if there are no other references to it). Once the instance is disposed, it
|
||||
/// can no longer be used in interop calls from JavaScript code.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!Disposed)
|
||||
{
|
||||
Disposed = true;
|
||||
|
||||
if (_jsRuntime != null)
|
||||
{
|
||||
_jsRuntime.ReleaseObjectReference(_objectId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void ThrowIfDisposed()
|
||||
{
|
||||
if (Disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
|
@ -14,21 +13,28 @@ namespace Microsoft.JSInterop
|
|||
{
|
||||
/// <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="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>
|
||||
Task<TValue> InvokeAsync<TValue>(string identifier, params object[] args);
|
||||
ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] 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>
|
||||
/// <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>
|
||||
Task<TValue> InvokeAsync<TValue>(string identifier, IEnumerable<object> args, CancellationToken cancellationToken = default);
|
||||
ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken, object[] args);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,13 +11,14 @@ using System.Text;
|
|||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
namespace Microsoft.JSInterop.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods that receive incoming calls from JS to .NET.
|
||||
/// </summary>
|
||||
public static class DotNetDispatcher
|
||||
{
|
||||
private const string DisposeDotNetObjectReferenceMethodName = "__Dispose";
|
||||
internal static readonly JsonEncodedText DotNetObjectRefKey = JsonEncodedText.Encode("__dotNetObject");
|
||||
|
||||
private static readonly ConcurrentDictionary<AssemblyKey, IReadOnlyDictionary<string, (MethodInfo, Type[])>> _cachedMethodsByAssembly
|
||||
|
|
@ -26,68 +27,62 @@ namespace Microsoft.JSInterop
|
|||
/// <summary>
|
||||
/// Receives a call from JS to .NET, locating and invoking the specified method.
|
||||
/// </summary>
|
||||
/// <param name="assemblyName">The assembly containing the method to be invoked.</param>
|
||||
/// <param name="methodIdentifier">The identifier of the method to be invoked. The method must be annotated with a <see cref="JSInvokableAttribute"/> matching this identifier string.</param>
|
||||
/// <param name="dotNetObjectId">For instance method calls, identifies the target object.</param>
|
||||
/// <param name="jsRuntime">The <see cref="JSRuntime"/>.</param>
|
||||
/// <param name="invocationInfo">The <see cref="DotNetInvocationInfo"/>.</param>
|
||||
/// <param name="argsJson">A JSON representation of the parameters.</param>
|
||||
/// <returns>A JSON representation of the return value, or null.</returns>
|
||||
public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson)
|
||||
public static string Invoke(JSRuntime jsRuntime, in DotNetInvocationInfo invocationInfo, string argsJson)
|
||||
{
|
||||
// This method doesn't need [JSInvokable] because the platform is responsible for having
|
||||
// some way to dispatch calls here. The logic inside here is the thing that checks whether
|
||||
// the targeted method has [JSInvokable]. It is not itself subject to that restriction,
|
||||
// because there would be nobody to police that. This method *is* the police.
|
||||
|
||||
var targetInstance = (object)null;
|
||||
if (dotNetObjectId != default)
|
||||
IDotNetObjectReference targetInstance = default;
|
||||
if (invocationInfo.DotNetObjectId != default)
|
||||
{
|
||||
targetInstance = DotNetObjectRefManager.Current.FindDotNetObject(dotNetObjectId);
|
||||
targetInstance = jsRuntime.GetObjectReference(invocationInfo.DotNetObjectId);
|
||||
}
|
||||
|
||||
var syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson);
|
||||
var syncResult = InvokeSynchronously(jsRuntime, invocationInfo, targetInstance, argsJson);
|
||||
if (syncResult == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return JsonSerializer.Serialize(syncResult, JsonSerializerOptionsProvider.Options);
|
||||
return JsonSerializer.Serialize(syncResult, jsRuntime.JsonSerializerOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Receives a call from JS to .NET, locating and invoking the specified method asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="callId">A value identifying the asynchronous call that should be passed back with the result, or null if no result notification is required.</param>
|
||||
/// <param name="assemblyName">The assembly containing the method to be invoked.</param>
|
||||
/// <param name="methodIdentifier">The identifier of the method to be invoked. The method must be annotated with a <see cref="JSInvokableAttribute"/> matching this identifier string.</param>
|
||||
/// <param name="dotNetObjectId">For instance method calls, identifies the target object.</param>
|
||||
/// <param name="jsRuntime">The <see cref="JSRuntime"/>.</param>
|
||||
/// <param name="invocationInfo">The <see cref="DotNetInvocationInfo"/>.</param>
|
||||
/// <param name="argsJson">A JSON representation of the parameters.</param>
|
||||
/// <returns>A JSON representation of the return value, or null.</returns>
|
||||
public static void BeginInvoke(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson)
|
||||
public static void BeginInvokeDotNet(JSRuntime jsRuntime, DotNetInvocationInfo invocationInfo, string argsJson)
|
||||
{
|
||||
// This method doesn't need [JSInvokable] because the platform is responsible for having
|
||||
// some way to dispatch calls here. The logic inside here is the thing that checks whether
|
||||
// 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.
|
||||
|
||||
// DotNetDispatcher only works with JSRuntimeBase instances.
|
||||
// If the developer wants to use a totally custom IJSRuntime, then their JS-side
|
||||
// code has to implement its own way of returning async results.
|
||||
var jsRuntimeBaseInstance = (JSRuntimeBase)JSRuntime.Current;
|
||||
|
||||
// Using ExceptionDispatchInfo here throughout because we want to always preserve
|
||||
// original stack traces.
|
||||
|
||||
var callId = invocationInfo.CallId;
|
||||
|
||||
object syncResult = null;
|
||||
ExceptionDispatchInfo syncException = null;
|
||||
object targetInstance = null;
|
||||
|
||||
IDotNetObjectReference targetInstance = null;
|
||||
try
|
||||
{
|
||||
if (dotNetObjectId != default)
|
||||
if (invocationInfo.DotNetObjectId != default)
|
||||
{
|
||||
targetInstance = DotNetObjectRefManager.Current.FindDotNetObject(dotNetObjectId);
|
||||
targetInstance = jsRuntime.GetObjectReference(invocationInfo.DotNetObjectId);
|
||||
}
|
||||
|
||||
syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson);
|
||||
syncResult = InvokeSynchronously(jsRuntime, invocationInfo, targetInstance, argsJson);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -102,7 +97,7 @@ namespace Microsoft.JSInterop
|
|||
else if (syncException != null)
|
||||
{
|
||||
// Threw synchronously, let's respond.
|
||||
jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, syncException, assemblyName, methodIdentifier, dotNetObjectId);
|
||||
jsRuntime.EndInvokeDotNet(invocationInfo, new DotNetInvocationResult(syncException.SourceException, "InvocationFailure"));
|
||||
}
|
||||
else if (syncResult is Task task)
|
||||
{
|
||||
|
|
@ -112,45 +107,57 @@ namespace Microsoft.JSInterop
|
|||
{
|
||||
if (t.Exception != null)
|
||||
{
|
||||
var exception = t.Exception.GetBaseException();
|
||||
|
||||
jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, ExceptionDispatchInfo.Capture(exception), assemblyName, methodIdentifier, dotNetObjectId);
|
||||
var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(t.Exception.GetBaseException());
|
||||
var dispatchResult = new DotNetInvocationResult(exceptionDispatchInfo.SourceException, "InvocationFailure");
|
||||
jsRuntime.EndInvokeDotNet(invocationInfo, dispatchResult);
|
||||
}
|
||||
|
||||
var result = TaskGenericsUtil.GetTaskResult(task);
|
||||
jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, result, assemblyName, methodIdentifier, dotNetObjectId);
|
||||
jsRuntime.EndInvokeDotNet(invocationInfo, new DotNetInvocationResult(result));
|
||||
}, TaskScheduler.Current);
|
||||
}
|
||||
else
|
||||
{
|
||||
jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, syncResult, assemblyName, methodIdentifier, dotNetObjectId);
|
||||
var dispatchResult = new DotNetInvocationResult(syncResult);
|
||||
jsRuntime.EndInvokeDotNet(invocationInfo, dispatchResult);
|
||||
}
|
||||
}
|
||||
|
||||
private static object InvokeSynchronously(string assemblyName, string methodIdentifier, object targetInstance, string argsJson)
|
||||
private static object InvokeSynchronously(JSRuntime jsRuntime, in DotNetInvocationInfo callInfo, IDotNetObjectReference objectReference, string argsJson)
|
||||
{
|
||||
var assemblyName = callInfo.AssemblyName;
|
||||
var methodIdentifier = callInfo.MethodIdentifier;
|
||||
|
||||
AssemblyKey assemblyKey;
|
||||
if (targetInstance != null)
|
||||
if (objectReference is null)
|
||||
{
|
||||
assemblyKey = new AssemblyKey(assemblyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (assemblyName != null)
|
||||
{
|
||||
throw new ArgumentException($"For instance method calls, '{nameof(assemblyName)}' should be null. Value received: '{assemblyName}'.");
|
||||
}
|
||||
|
||||
assemblyKey = new AssemblyKey(targetInstance.GetType().Assembly);
|
||||
}
|
||||
else
|
||||
{
|
||||
assemblyKey = new AssemblyKey(assemblyName);
|
||||
if (string.Equals(DisposeDotNetObjectReferenceMethodName, methodIdentifier, StringComparison.Ordinal))
|
||||
{
|
||||
// The client executed dotNetObjectReference.dispose(). Dispose the reference and exit.
|
||||
objectReference.Dispose();
|
||||
return default;
|
||||
}
|
||||
|
||||
assemblyKey = new AssemblyKey(objectReference.Value.GetType().Assembly);
|
||||
}
|
||||
|
||||
var (methodInfo, parameterTypes) = GetCachedMethodInfo(assemblyKey, methodIdentifier);
|
||||
|
||||
var suppliedArgs = ParseArguments(methodIdentifier, argsJson, parameterTypes);
|
||||
var suppliedArgs = ParseArguments(jsRuntime, methodIdentifier, argsJson, parameterTypes);
|
||||
|
||||
try
|
||||
{
|
||||
return methodInfo.Invoke(targetInstance, suppliedArgs);
|
||||
// objectReference will be null if this call invokes a static JSInvokable method.
|
||||
return methodInfo.Invoke(objectReference?.Value, suppliedArgs);
|
||||
}
|
||||
catch (TargetInvocationException tie) // Avoid using exception filters for AOT runtime support
|
||||
{
|
||||
|
|
@ -164,7 +171,7 @@ namespace Microsoft.JSInterop
|
|||
}
|
||||
}
|
||||
|
||||
internal static object[] ParseArguments(string methodIdentifier, string arguments, Type[] parameterTypes)
|
||||
internal static object[] ParseArguments(JSRuntime jsRuntime, string methodIdentifier, string arguments, Type[] parameterTypes)
|
||||
{
|
||||
if (parameterTypes.Length == 0)
|
||||
{
|
||||
|
|
@ -189,7 +196,7 @@ namespace Microsoft.JSInterop
|
|||
throw new InvalidOperationException($"In call to '{methodIdentifier}', parameter of type '{parameterType.Name}' at index {(index + 1)} must be declared as type 'DotNetObjectRef<{parameterType.Name}>' to receive the incoming value.");
|
||||
}
|
||||
|
||||
suppliedArgs[index] = JsonSerializer.Deserialize(ref reader, parameterType, JsonSerializerOptionsProvider.Options);
|
||||
suppliedArgs[index] = JsonSerializer.Deserialize(ref reader, parameterType, jsRuntime.JsonSerializerOptions);
|
||||
index++;
|
||||
}
|
||||
|
||||
|
|
@ -218,7 +225,7 @@ namespace Microsoft.JSInterop
|
|||
jsonReader.ValueTextEquals(DotNetObjectRefKey.EncodedUtf8Bytes))
|
||||
{
|
||||
// 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;
|
||||
|
|
@ -230,26 +237,21 @@ namespace Microsoft.JSInterop
|
|||
/// associated <see cref="Task"/> as completed.
|
||||
/// </summary>
|
||||
/// <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
|
||||
/// 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
|
||||
/// 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
|
||||
/// passed in as parameters.
|
||||
/// </remarks>
|
||||
/// <param name="jsRuntime">The <see cref="JSRuntime"/>.</param>
|
||||
/// <param name="arguments">The serialized arguments for the callback completion.</param>
|
||||
/// <exception cref="Exception">
|
||||
/// This method can throw any exception either from the argument received or as a result
|
||||
/// of executing any callback synchronously upon completion.
|
||||
/// </exception>
|
||||
public static void EndInvoke(string arguments)
|
||||
{
|
||||
var jsRuntimeBase = (JSRuntimeBase)JSRuntime.Current;
|
||||
ParseEndInvokeArguments(jsRuntimeBase, arguments);
|
||||
}
|
||||
|
||||
internal static void ParseEndInvokeArguments(JSRuntimeBase jsRuntimeBase, string arguments)
|
||||
public static void EndInvokeJS(JSRuntime jsRuntime, string arguments)
|
||||
{
|
||||
var utf8JsonBytes = Encoding.UTF8.GetBytes(arguments);
|
||||
|
||||
|
|
@ -272,7 +274,7 @@ namespace Microsoft.JSInterop
|
|||
var success = reader.GetBoolean();
|
||||
|
||||
reader.Read();
|
||||
jsRuntimeBase.EndInvokeJS(taskId, success, ref reader);
|
||||
jsRuntime.EndInvokeJS(taskId, success, ref reader);
|
||||
|
||||
if (!reader.Read() || reader.TokenType != JsonTokenType.EndArray)
|
||||
{
|
||||
|
|
@ -280,22 +282,6 @@ namespace Microsoft.JSInterop
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the reference to the specified .NET object. This allows the .NET runtime
|
||||
/// to garbage collect that object if there are no other references to it.
|
||||
///
|
||||
/// To avoid leaking memory, the JavaScript side code must call this for every .NET
|
||||
/// object it obtains a reference to. The exception is if that object is used for
|
||||
/// the entire lifetime of a given user's session, in which case it is released
|
||||
/// automatically when the JavaScript runtime is disposed.
|
||||
/// </summary>
|
||||
/// <param name="dotNetObjectId">The identifier previously passed to JavaScript code.</param>
|
||||
[JSInvokable(nameof(DotNetDispatcher) + "." + nameof(ReleaseDotNetObject))]
|
||||
public static void ReleaseDotNetObject(long dotNetObjectId)
|
||||
{
|
||||
DotNetObjectRefManager.Current.ReleaseDotNetObject(dotNetObjectId);
|
||||
}
|
||||
|
||||
private static (MethodInfo, Type[]) GetCachedMethodInfo(AssemblyKey assemblyKey, string methodIdentifier)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(assemblyKey.AssemblyName))
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.JSInterop.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about a JSInterop call from JavaScript to .NET.
|
||||
/// </summary>
|
||||
public readonly struct DotNetInvocationInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="DotNetInvocationInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="assemblyName">The name of the assembly containing the method.</param>
|
||||
/// <param name="methodIdentifier">The identifier of the method to be invoked.</param>
|
||||
/// <param name="dotNetObjectId">The object identifier for instance method calls.</param>
|
||||
/// <param name="callId">The call identifier.</param>
|
||||
public DotNetInvocationInfo(string assemblyName, string methodIdentifier, long dotNetObjectId, string callId)
|
||||
{
|
||||
CallId = callId;
|
||||
AssemblyName = assemblyName;
|
||||
MethodIdentifier = methodIdentifier;
|
||||
DotNetObjectId = dotNetObjectId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the assembly containing the method.
|
||||
/// Only one of <see cref="DotNetObjectId"/> or <see cref="AssemblyName"/> may be specified.
|
||||
/// </summary>
|
||||
public string AssemblyName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier of the method to be invoked. This is the value specified in the <see cref="JSInvokableAttribute"/>.
|
||||
/// </summary>
|
||||
public string MethodIdentifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object identifier for instance method calls.
|
||||
/// Only one of <see cref="DotNetObjectId"/> or <see cref="AssemblyName"/> may be specified.
|
||||
/// </summary>
|
||||
public long DotNetObjectId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the call identifier. This value is <see langword="null"/> when the client does not expect a value to be returned.
|
||||
/// </summary>
|
||||
public string CallId { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
using System;
|
||||
|
||||
namespace Microsoft.JSInterop.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Result of a .NET invocation that is returned to JavaScript.
|
||||
/// </summary>
|
||||
public readonly struct DotNetInvocationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor for a failed invocation.
|
||||
/// </summary>
|
||||
/// <param name="exception">The <see cref="System.Exception"/> that caused the failure.</param>
|
||||
/// <param name="errorKind">The error kind.</param>
|
||||
public DotNetInvocationResult(Exception exception, string errorKind)
|
||||
{
|
||||
Result = default;
|
||||
Exception = exception ?? throw new ArgumentNullException(nameof(exception));
|
||||
ErrorKind = errorKind;
|
||||
Success = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for a successful invocation.
|
||||
/// </summary>
|
||||
/// <param name="result">The result.</param>
|
||||
public DotNetInvocationResult(object result)
|
||||
{
|
||||
Result = result;
|
||||
Exception = default;
|
||||
ErrorKind = default;
|
||||
Success = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="System.Exception"/> that caused the failure.
|
||||
/// </summary>
|
||||
public Exception Exception { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error kind.
|
||||
/// </summary>
|
||||
public string ErrorKind { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the result of a successful invocation.
|
||||
/// </summary>
|
||||
public object Result { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <see langword="true"/> if the invocation succeeded, otherwise <see langword="false"/>.
|
||||
/// </summary>
|
||||
public bool Success { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -5,13 +5,20 @@ using System;
|
|||
using System.Text.Json;
|
||||
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
|
||||
{
|
||||
public DotNetObjectReferenceJsonConverter(JSRuntime jsRuntime)
|
||||
{
|
||||
JSRuntime = jsRuntime;
|
||||
}
|
||||
|
||||
private static JsonEncodedText DotNetObjectRefKey => DotNetDispatcher.DotNetObjectRefKey;
|
||||
|
||||
public override DotNetObjectRef<TValue> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
public JSRuntime JSRuntime { get; }
|
||||
|
||||
public override DotNetObjectReference<TValue> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
long dotNetObjectId = 0;
|
||||
|
||||
|
|
@ -40,14 +47,16 @@ namespace Microsoft.JSInterop
|
|||
throw new JsonException($"Required property {DotNetObjectRefKey} not found.");
|
||||
}
|
||||
|
||||
var value = (TValue)DotNetObjectRefManager.Current.FindDotNetObject(dotNetObjectId);
|
||||
return new DotNetObjectRef<TValue>(dotNetObjectId, value);
|
||||
var value = (DotNetObjectReference<TValue>)JSRuntime.GetObjectReference(dotNetObjectId);
|
||||
return value;
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, DotNetObjectRef<TValue> value, JsonSerializerOptions options)
|
||||
public override void Write(Utf8JsonWriter writer, DotNetObjectReference<TValue> value, JsonSerializerOptions options)
|
||||
{
|
||||
var objectId = JSRuntime.TrackObjectReference<TValue>(value);
|
||||
|
||||
writer.WriteStartObject();
|
||||
writer.WriteNumber(DotNetObjectRefKey, value.ObjectId);
|
||||
writer.WriteNumber(DotNetObjectRefKey, objectId);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
|
|
@ -5,13 +5,20 @@ using System;
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
namespace Microsoft.JSInterop.Infrastructure
|
||||
{
|
||||
internal sealed class DotNetObjectReferenceJsonConverterFactory : JsonConverterFactory
|
||||
{
|
||||
public DotNetObjectReferenceJsonConverterFactory(JSRuntime jsRuntime)
|
||||
{
|
||||
JSRuntime = jsRuntime;
|
||||
}
|
||||
|
||||
public JSRuntime JSRuntime { get; }
|
||||
|
||||
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)
|
||||
|
|
@ -20,7 +27,7 @@ namespace Microsoft.JSInterop
|
|||
var instanceType = typeToConvert.GetGenericArguments()[0];
|
||||
var converterType = typeof(DotNetObjectReferenceJsonConverter<>).MakeGenericType(instanceType);
|
||||
|
||||
return (JsonConverter)Activator.CreateInstance(converterType);
|
||||
return (JsonConverter)Activator.CreateInstance(converterType, JSRuntime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,10 @@
|
|||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
namespace Microsoft.JSInterop.Infrastructure
|
||||
{
|
||||
internal interface IDotNetObjectRef : IDisposable
|
||||
internal interface IDotNetObjectReference : IDisposable
|
||||
{
|
||||
object Value { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ using System.Collections.Concurrent;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
namespace Microsoft.JSInterop.Infrastructure
|
||||
{
|
||||
internal static class TaskGenericsUtil
|
||||
{
|
||||
|
|
@ -8,7 +8,7 @@ namespace Microsoft.JSInterop
|
|||
/// <summary>
|
||||
/// Abstract base class for an in-process JavaScript runtime.
|
||||
/// </summary>
|
||||
public abstract class JSInProcessRuntimeBase : JSRuntimeBase, IJSInProcessRuntime
|
||||
public abstract class JSInProcessRuntime : JSRuntime, IJSInProcessRuntime
|
||||
{
|
||||
/// <summary>
|
||||
/// Invokes the specified JavaScript function synchronously.
|
||||
|
|
@ -19,13 +19,13 @@ namespace Microsoft.JSInterop
|
|||
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
|
||||
public TValue Invoke<TValue>(string identifier, params object[] args)
|
||||
{
|
||||
var resultJson = InvokeJS(identifier, JsonSerializer.Serialize(args, JsonSerializerOptionsProvider.Options));
|
||||
var resultJson = InvokeJS(identifier, JsonSerializer.Serialize(args, JsonSerializerOptions));
|
||||
if (resultJson is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<TValue>(resultJson, JsonSerializerOptionsProvider.Options);
|
||||
return JsonSerializer.Deserialize<TValue>(resultJson, JsonSerializerOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -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.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
|
||||
public class JSInvokableAttribute : Attribute
|
||||
public sealed class JSInvokableAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the identifier for the method. The identifier must be unique within the scope
|
||||
|
|
|
|||
|
|
@ -2,29 +2,234 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.JSInterop.Infrastructure;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides mechanisms for accessing the current <see cref="IJSRuntime"/>.
|
||||
/// Abstract base class for a JavaScript runtime.
|
||||
/// </summary>
|
||||
public static class JSRuntime
|
||||
public abstract partial class JSRuntime : IJSRuntime
|
||||
{
|
||||
private static readonly AsyncLocal<IJSRuntime> _currentJSRuntime = new AsyncLocal<IJSRuntime>();
|
||||
|
||||
internal static IJSRuntime Current => _currentJSRuntime.Value;
|
||||
private long _nextObjectReferenceId = 0; // 0 signals no object, but we increment prior to assignment. The first tracked object should have id 1
|
||||
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, IDotNetObjectReference> _trackedRefsById = new ConcurrentDictionary<long, IDotNetObjectReference>();
|
||||
private readonly ConcurrentDictionary<long, CancellationTokenRegistration> _cancellationRegistrations =
|
||||
new ConcurrentDictionary<long, CancellationTokenRegistration>();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current JS runtime to the supplied instance.
|
||||
///
|
||||
/// This is intended for framework use. Developers should not normally need to call this method.
|
||||
/// Initializes a new instance of <see cref="JSRuntime"/>.
|
||||
/// </summary>
|
||||
/// <param name="instance">The new current <see cref="IJSRuntime"/>.</param>
|
||||
public static void SetCurrentJSRuntime(IJSRuntime instance)
|
||||
protected JSRuntime()
|
||||
{
|
||||
_currentJSRuntime.Value = instance
|
||||
?? throw new ArgumentNullException(nameof(instance));
|
||||
JsonSerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
MaxDepth = 32,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
Converters =
|
||||
{
|
||||
new DotNetObjectReferenceJsonConverterFactory(this),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="System.Text.Json.JsonSerializerOptions"/> used to serialize and deserialize interop payloads.
|
||||
/// </summary>
|
||||
protected internal JsonSerializerOptions JsonSerializerOptions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default timeout for asynchronous JavaScript calls.
|
||||
/// </summary>
|
||||
protected TimeSpan? DefaultAsyncTimeout { get; set; }
|
||||
|
||||
/// <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, JsonSerializerOptions) :
|
||||
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="invocationInfo">The <see cref="DotNetInvocationInfo"/>.</param>
|
||||
/// <param name="invocationResult">The <see cref="DotNetInvocationResult"/>.</param>
|
||||
protected internal abstract void EndInvokeDotNet(
|
||||
DotNetInvocationInfo invocationInfo,
|
||||
in DotNetInvocationResult invocationResult);
|
||||
|
||||
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, JsonSerializerOptions);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
internal long TrackObjectReference<TValue>(DotNetObjectReference<TValue> dotNetObjectReference) where TValue : class
|
||||
{
|
||||
if (dotNetObjectReference == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dotNetObjectReference));
|
||||
}
|
||||
|
||||
dotNetObjectReference.ThrowIfDisposed();
|
||||
|
||||
var jsRuntime = dotNetObjectReference.JSRuntime;
|
||||
if (jsRuntime is null)
|
||||
{
|
||||
var dotNetObjectId = Interlocked.Increment(ref _nextObjectReferenceId);
|
||||
|
||||
dotNetObjectReference.JSRuntime = this;
|
||||
dotNetObjectReference.ObjectId = dotNetObjectId;
|
||||
|
||||
_trackedRefsById[dotNetObjectId] = dotNetObjectReference;
|
||||
}
|
||||
else if (!ReferenceEquals(this, jsRuntime))
|
||||
{
|
||||
throw new InvalidOperationException($"{dotNetObjectReference.GetType().Name} is already being tracked by a different instance of {nameof(JSRuntime)}." +
|
||||
$" A common cause is caching an instance of {nameof(DotNetObjectReference<TValue>)} globally. Consider creating instances of {nameof(DotNetObjectReference<TValue>)} at the JSInterop callsite.");
|
||||
}
|
||||
|
||||
Debug.Assert(dotNetObjectReference.ObjectId != 0);
|
||||
return dotNetObjectReference.ObjectId;
|
||||
}
|
||||
|
||||
internal IDotNetObjectReference GetObjectReference(long dotNetObjectId)
|
||||
{
|
||||
return _trackedRefsById.TryGetValue(dotNetObjectId, out var dotNetObjectRef)
|
||||
? dotNetObjectRef
|
||||
: throw new ArgumentException($"There is no tracked object with id '{dotNetObjectId}'. Perhaps the DotNetObjectReference instance was already disposed.", nameof(dotNetObjectId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
/// <param name="dotNetObjectId">The ID of the <see cref="DotNetObjectReference{TValue}"/>.</param>
|
||||
internal void ReleaseObjectReference(long dotNetObjectId) => _trackedRefsById.TryRemove(dotNetObjectId, out _);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +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.Text.Json;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
internal static class JsonSerializerOptionsProvider
|
||||
{
|
||||
public static readonly JsonSerializerOptions Options = new JsonSerializerOptions
|
||||
{
|
||||
MaxDepth = 32,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +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.Threading.Tasks;
|
||||
using Xunit;
|
||||
using static Microsoft.JSInterop.TestJSRuntime;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
public class DotNetObjectRefTest
|
||||
{
|
||||
[Fact]
|
||||
public Task CanAccessValue() => WithJSRuntime(_ =>
|
||||
{
|
||||
var obj = new object();
|
||||
Assert.Same(obj, DotNetObjectRef.Create(obj).Value);
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public Task NotifiesAssociatedJsRuntimeOfDisposal() => WithJSRuntime(jsRuntime =>
|
||||
{
|
||||
// Arrange
|
||||
var objRef = DotNetObjectRef.Create(new object());
|
||||
|
||||
// Act
|
||||
objRef.Dispose();
|
||||
|
||||
// Assert
|
||||
var ex = Assert.Throws<ArgumentException>(() => jsRuntime.ObjectRefManager.FindDotNetObject(objRef.ObjectId));
|
||||
Assert.StartsWith("There is no tracked object with id '1'.", ex.Message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
// 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 Xunit;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
public class DotNetObjectReferenceTest
|
||||
{
|
||||
[Fact]
|
||||
public void CanAccessValue()
|
||||
{
|
||||
var obj = new object();
|
||||
Assert.Same(obj, DotNetObjectReference.Create(obj).Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TrackObjectReference_AssignsObjectId()
|
||||
{
|
||||
// Arrange
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var objRef = DotNetObjectReference.Create(new object());
|
||||
|
||||
// Act
|
||||
var objectId = jsRuntime.TrackObjectReference(objRef);
|
||||
|
||||
// Act
|
||||
Assert.Equal(objectId, objRef.ObjectId);
|
||||
Assert.Equal(1, objRef.ObjectId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TrackObjectReference_AllowsMultipleCallsUsingTheSameJSRuntime()
|
||||
{
|
||||
// Arrange
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var objRef = DotNetObjectReference.Create(new object());
|
||||
|
||||
// Act
|
||||
var objectId1 = jsRuntime.TrackObjectReference(objRef);
|
||||
var objectId2 = jsRuntime.TrackObjectReference(objRef);
|
||||
|
||||
// Act
|
||||
Assert.Equal(objectId1, objectId2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TrackObjectReference_ThrowsIfDifferentJSRuntimeInstancesAreUsed()
|
||||
{
|
||||
// Arrange
|
||||
var objRef = DotNetObjectReference.Create("Hello world");
|
||||
var expected = $"{objRef.GetType().Name} is already being tracked by a different instance of {nameof(JSRuntime)}. A common cause is caching an instance of {nameof(DotNetObjectReference<string>)}" +
|
||||
$" globally. Consider creating instances of {nameof(DotNetObjectReference<string>)} at the JSInterop callsite.";
|
||||
var jsRuntime1 = new TestJSRuntime();
|
||||
var jsRuntime2 = new TestJSRuntime();
|
||||
jsRuntime1.TrackObjectReference(objRef);
|
||||
|
||||
// Act
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => jsRuntime2.TrackObjectReference(objRef));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_StopsTrackingObject()
|
||||
{
|
||||
// Arrange
|
||||
var objRef = DotNetObjectReference.Create("Hello world");
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
jsRuntime.TrackObjectReference(objRef);
|
||||
var objectId = objRef.ObjectId;
|
||||
var expected = $"There is no tracked object with id '{objectId}'. Perhaps the DotNetObjectReference instance was already disposed.";
|
||||
|
||||
// Act
|
||||
Assert.Same(objRef, jsRuntime.GetObjectReference(objectId));
|
||||
objRef.Dispose();
|
||||
|
||||
// Assert
|
||||
Assert.True(objRef.Disposed);
|
||||
Assert.Throws<ArgumentException>(() => jsRuntime.GetObjectReference(objectId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoubleDispose_Works()
|
||||
{
|
||||
// Arrange
|
||||
var objRef = DotNetObjectReference.Create("Hello world");
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
jsRuntime.TrackObjectReference(objRef);
|
||||
var objectId = objRef.ObjectId;
|
||||
|
||||
// Act
|
||||
Assert.Same(objRef, jsRuntime.GetObjectReference(objectId));
|
||||
objRef.Dispose();
|
||||
|
||||
// Assert
|
||||
objRef.Dispose();
|
||||
// If we got this far, this did not throw.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
namespace Microsoft.JSInterop.Infrastructure
|
||||
{
|
||||
public class DotNetDispatcherTest
|
||||
{
|
||||
|
|
@ -20,7 +20,7 @@ namespace Microsoft.JSInterop
|
|||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
DotNetDispatcher.Invoke(" ", "SomeMethod", default, "[]");
|
||||
DotNetDispatcher.Invoke(new TestJSRuntime(), new DotNetInvocationInfo(" ", "SomeMethod", default, default), "[]");
|
||||
});
|
||||
|
||||
Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message);
|
||||
|
|
@ -32,7 +32,7 @@ namespace Microsoft.JSInterop
|
|||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
DotNetDispatcher.Invoke("SomeAssembly", " ", default, "[]");
|
||||
DotNetDispatcher.Invoke(new TestJSRuntime(), new DotNetInvocationInfo("SomeAssembly", " ", default, default), "[]");
|
||||
});
|
||||
|
||||
Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message);
|
||||
|
|
@ -45,7 +45,7 @@ namespace Microsoft.JSInterop
|
|||
var assemblyName = "Some.Fake.Assembly";
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
DotNetDispatcher.Invoke(assemblyName, "SomeMethod", default, null);
|
||||
DotNetDispatcher.Invoke(new TestJSRuntime(), new DotNetInvocationInfo(assemblyName, "SomeMethod", default, default), null);
|
||||
});
|
||||
|
||||
Assert.Equal($"There is no loaded assembly with the name '{assemblyName}'.", ex.Message);
|
||||
|
|
@ -67,54 +67,58 @@ namespace Microsoft.JSInterop
|
|||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
DotNetDispatcher.Invoke(thisAssemblyName, methodIdentifier, default, null);
|
||||
DotNetDispatcher.Invoke(new TestJSRuntime(), new DotNetInvocationInfo(thisAssemblyName, methodIdentifier, default, default), null);
|
||||
});
|
||||
|
||||
Assert.Equal($"The assembly '{thisAssemblyName}' does not contain a public method with [JSInvokableAttribute(\"{methodIdentifier}\")].", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task CanInvokeStaticVoidMethod() => WithJSRuntime(jsRuntime =>
|
||||
public void CanInvokeStaticVoidMethod()
|
||||
{
|
||||
// Arrange/Act
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
SomePublicType.DidInvokeMyInvocableStaticVoid = false;
|
||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticVoid", default, null);
|
||||
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticVoid", default, default), null);
|
||||
|
||||
// Assert
|
||||
Assert.Null(resultJson);
|
||||
Assert.True(SomePublicType.DidInvokeMyInvocableStaticVoid);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task CanInvokeStaticNonVoidMethod() => WithJSRuntime(jsRuntime =>
|
||||
public void CanInvokeStaticNonVoidMethod()
|
||||
{
|
||||
// Arrange/Act
|
||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticNonVoid", default, null);
|
||||
var result = JsonSerializer.Deserialize<TestDTO>(resultJson, JsonSerializerOptionsProvider.Options);
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticNonVoid", default, default), null);
|
||||
var result = JsonSerializer.Deserialize<TestDTO>(resultJson, jsRuntime.JsonSerializerOptions);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Test", result.StringVal);
|
||||
Assert.Equal(123, result.IntVal);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task CanInvokeStaticNonVoidMethodWithoutCustomIdentifier() => WithJSRuntime(jsRuntime =>
|
||||
public void CanInvokeStaticNonVoidMethodWithoutCustomIdentifier()
|
||||
{
|
||||
// Arrange/Act
|
||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, nameof(SomePublicType.InvokableMethodWithoutCustomIdentifier), default, null);
|
||||
var result = JsonSerializer.Deserialize<TestDTO>(resultJson, JsonSerializerOptionsProvider.Options);
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, nameof(SomePublicType.InvokableMethodWithoutCustomIdentifier), default, default), null);
|
||||
var result = JsonSerializer.Deserialize<TestDTO>(resultJson, jsRuntime.JsonSerializerOptions);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("InvokableMethodWithoutCustomIdentifier", result.StringVal);
|
||||
Assert.Equal(456, result.IntVal);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task CanInvokeStaticWithParams() => WithJSRuntime(jsRuntime =>
|
||||
public void CanInvokeStaticWithParams()
|
||||
{
|
||||
// Arrange: Track a .NET object to use as an arg
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var arg3 = new TestDTO { IntVal = 999, StringVal = "My string" };
|
||||
var objectRef = DotNetObjectRef.Create(arg3);
|
||||
var objectRef = DotNetObjectReference.Create(arg3);
|
||||
jsRuntime.Invoke<object>("unimportant", objectRef);
|
||||
|
||||
// Arrange: Remaining args
|
||||
|
|
@ -123,15 +127,15 @@ namespace Microsoft.JSInterop
|
|||
new TestDTO { StringVal = "Another string", IntVal = 456 },
|
||||
new[] { 100, 200 },
|
||||
objectRef
|
||||
}, JsonSerializerOptionsProvider.Options);
|
||||
}, jsRuntime.JsonSerializerOptions);
|
||||
|
||||
// Act
|
||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", default, argsJson);
|
||||
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticWithParams", default, default), argsJson);
|
||||
var result = JsonDocument.Parse(resultJson);
|
||||
var root = result.RootElement;
|
||||
|
||||
// Assert: First result value marshalled via JSON
|
||||
var resultDto1 = JsonSerializer.Deserialize<TestDTO>(root[0].GetRawText(), JsonSerializerOptionsProvider.Options);
|
||||
var resultDto1 = JsonSerializer.Deserialize<TestDTO>(root[0].GetRawText(), jsRuntime.JsonSerializerOptions);
|
||||
|
||||
Assert.Equal("ANOTHER STRING", resultDto1.StringVal);
|
||||
Assert.Equal(756, resultDto1.IntVal);
|
||||
|
|
@ -142,18 +146,19 @@ namespace Microsoft.JSInterop
|
|||
Assert.False(resultDto2Ref.TryGetProperty(nameof(TestDTO.IntVal), out _));
|
||||
|
||||
Assert.True(resultDto2Ref.TryGetProperty(DotNetDispatcher.DotNetObjectRefKey.EncodedUtf8Bytes, out var property));
|
||||
var resultDto2 = Assert.IsType<TestDTO>(DotNetObjectRefManager.Current.FindDotNetObject(property.GetInt64()));
|
||||
var resultDto2 = Assert.IsType<DotNetObjectReference<TestDTO>>(jsRuntime.GetObjectReference(property.GetInt64())).Value;
|
||||
Assert.Equal("MY STRING", resultDto2.StringVal);
|
||||
Assert.Equal(1299, resultDto2.IntVal);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task InvokingWithIncorrectUseOfDotNetObjectRefThrows() => WithJSRuntime(jsRuntime =>
|
||||
public void InvokingWithIncorrectUseOfDotNetObjectRefThrows()
|
||||
{
|
||||
// Arrange
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var method = nameof(SomePublicType.IncorrectDotNetObjectRefUsage);
|
||||
var arg3 = new TestDTO { IntVal = 999, StringVal = "My string" };
|
||||
var objectRef = DotNetObjectRef.Create(arg3);
|
||||
var objectRef = DotNetObjectReference.Create(arg3);
|
||||
jsRuntime.Invoke<object>("unimportant", objectRef);
|
||||
|
||||
// Arrange: Remaining args
|
||||
|
|
@ -162,218 +167,246 @@ namespace Microsoft.JSInterop
|
|||
new TestDTO { StringVal = "Another string", IntVal = 456 },
|
||||
new[] { 100, 200 },
|
||||
objectRef
|
||||
}, JsonSerializerOptionsProvider.Options);
|
||||
}, jsRuntime.JsonSerializerOptions);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
DotNetDispatcher.Invoke(thisAssemblyName, method, default, argsJson));
|
||||
DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, method, default, default), argsJson));
|
||||
Assert.Equal($"In call to '{method}', parameter of type '{nameof(TestDTO)}' at index 3 must be declared as type 'DotNetObjectRef<TestDTO>' to receive the incoming value.", ex.Message);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task CanInvokeInstanceVoidMethod() => WithJSRuntime(jsRuntime =>
|
||||
public void CanInvokeInstanceVoidMethod()
|
||||
{
|
||||
// Arrange: Track some instance
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var targetInstance = new SomePublicType();
|
||||
var objectRef = DotNetObjectRef.Create(targetInstance);
|
||||
var objectRef = DotNetObjectReference.Create(targetInstance);
|
||||
jsRuntime.Invoke<object>("unimportant", objectRef);
|
||||
|
||||
// Act
|
||||
var resultJson = DotNetDispatcher.Invoke(null, "InvokableInstanceVoid", 1, null);
|
||||
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, "InvokableInstanceVoid", 1, default), null);
|
||||
|
||||
// Assert
|
||||
Assert.Null(resultJson);
|
||||
Assert.True(targetInstance.DidInvokeMyInvocableInstanceVoid);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task CanInvokeBaseInstanceVoidMethod() => WithJSRuntime(jsRuntime =>
|
||||
public void CanInvokeBaseInstanceVoidMethod()
|
||||
{
|
||||
// Arrange: Track some instance
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var targetInstance = new DerivedClass();
|
||||
var objectRef = DotNetObjectRef.Create(targetInstance);
|
||||
var objectRef = DotNetObjectReference.Create(targetInstance);
|
||||
jsRuntime.Invoke<object>("unimportant", objectRef);
|
||||
|
||||
// Act
|
||||
var resultJson = DotNetDispatcher.Invoke(null, "BaseClassInvokableInstanceVoid", 1, null);
|
||||
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, "BaseClassInvokableInstanceVoid", 1, default), null);
|
||||
|
||||
// Assert
|
||||
Assert.Null(resultJson);
|
||||
Assert.True(targetInstance.DidInvokeMyBaseClassInvocableInstanceVoid);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task CannotUseDotNetObjectRefAfterDisposal() => WithJSRuntime(jsRuntime =>
|
||||
public void DotNetObjectReferencesCanBeDisposed()
|
||||
{
|
||||
// Arrange
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var targetInstance = new SomePublicType();
|
||||
var objectRef = DotNetObjectReference.Create(targetInstance);
|
||||
jsRuntime.Invoke<object>("unimportant", objectRef);
|
||||
|
||||
// Act
|
||||
DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(null, "__Dispose", objectRef.ObjectId, default), null);
|
||||
|
||||
// Assert
|
||||
Assert.True(objectRef.Disposed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotUseDotNetObjectRefAfterDisposal()
|
||||
{
|
||||
// This test addresses the case where the developer calls objectRef.Dispose()
|
||||
// from .NET code, as opposed to .dispose() from JS code
|
||||
|
||||
// Arrange: Track some instance, then dispose it
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var targetInstance = new SomePublicType();
|
||||
var objectRef = DotNetObjectRef.Create(targetInstance);
|
||||
var objectRef = DotNetObjectReference.Create(targetInstance);
|
||||
jsRuntime.Invoke<object>("unimportant", objectRef);
|
||||
objectRef.Dispose();
|
||||
|
||||
// Act/Assert
|
||||
var ex = Assert.Throws<ArgumentException>(
|
||||
() => DotNetDispatcher.Invoke(null, "InvokableInstanceVoid", 1, null));
|
||||
() => DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, "InvokableInstanceVoid", 1, default), null));
|
||||
Assert.StartsWith("There is no tracked object with id '1'.", ex.Message);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task CannotUseDotNetObjectRefAfterReleaseDotNetObject() => WithJSRuntime(jsRuntime =>
|
||||
public void CannotUseDotNetObjectRefAfterReleaseDotNetObject()
|
||||
{
|
||||
// This test addresses the case where the developer calls .dispose()
|
||||
// from JS code, as opposed to objectRef.Dispose() from .NET code
|
||||
|
||||
// Arrange: Track some instance, then dispose it
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var targetInstance = new SomePublicType();
|
||||
var objectRef = DotNetObjectRef.Create(targetInstance);
|
||||
var objectRef = DotNetObjectReference.Create(targetInstance);
|
||||
jsRuntime.Invoke<object>("unimportant", objectRef);
|
||||
DotNetDispatcher.ReleaseDotNetObject(1);
|
||||
objectRef.Dispose();
|
||||
|
||||
// Act/Assert
|
||||
var ex = Assert.Throws<ArgumentException>(
|
||||
() => DotNetDispatcher.Invoke(null, "InvokableInstanceVoid", 1, null));
|
||||
() => DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, "InvokableInstanceVoid", 1, default), null));
|
||||
Assert.StartsWith("There is no tracked object with id '1'.", ex.Message);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task EndInvoke_WithSuccessValue() => WithJSRuntime(jsRuntime =>
|
||||
public void EndInvoke_WithSuccessValue()
|
||||
{
|
||||
// Arrange
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var testDTO = new TestDTO { StringVal = "Hello", IntVal = 4 };
|
||||
var task = jsRuntime.InvokeAsync<TestDTO>("unimportant");
|
||||
var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, true, testDTO }, JsonSerializerOptionsProvider.Options);
|
||||
var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, true, testDTO }, jsRuntime.JsonSerializerOptions);
|
||||
|
||||
// Act
|
||||
DotNetDispatcher.EndInvoke(argsJson);
|
||||
DotNetDispatcher.EndInvokeJS(jsRuntime, argsJson);
|
||||
|
||||
// Assert
|
||||
Assert.True(task.IsCompleted && task.Status == TaskStatus.RanToCompletion);
|
||||
Assert.True(task.IsCompletedSuccessfully);
|
||||
var result = task.Result;
|
||||
Assert.Equal(testDTO.StringVal, result.StringVal);
|
||||
Assert.Equal(testDTO.IntVal, result.IntVal);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task EndInvoke_WithErrorString() => WithJSRuntime(async jsRuntime =>
|
||||
public async Task EndInvoke_WithErrorString()
|
||||
{
|
||||
// Arrange
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var expected = "Some error";
|
||||
var task = jsRuntime.InvokeAsync<TestDTO>("unimportant");
|
||||
var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, false, expected }, JsonSerializerOptionsProvider.Options);
|
||||
var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, false, expected }, jsRuntime.JsonSerializerOptions);
|
||||
|
||||
// Act
|
||||
DotNetDispatcher.EndInvoke(argsJson);
|
||||
DotNetDispatcher.EndInvokeJS(jsRuntime, argsJson);
|
||||
|
||||
// Assert
|
||||
var ex = await Assert.ThrowsAsync<JSException>(() => task);
|
||||
var ex = await Assert.ThrowsAsync<JSException>(async () => await task);
|
||||
Assert.Equal(expected, ex.Message);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/12357")]
|
||||
public Task EndInvoke_AfterCancel() => WithJSRuntime(jsRuntime =>
|
||||
public void EndInvoke_AfterCancel()
|
||||
{
|
||||
// Arrange
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var testDTO = new TestDTO { StringVal = "Hello", IntVal = 4 };
|
||||
var cts = new CancellationTokenSource();
|
||||
var task = jsRuntime.InvokeAsync<TestDTO>("unimportant", cts.Token);
|
||||
var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, true, testDTO }, JsonSerializerOptionsProvider.Options);
|
||||
var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, true, testDTO }, jsRuntime.JsonSerializerOptions);
|
||||
|
||||
// Act
|
||||
cts.Cancel();
|
||||
DotNetDispatcher.EndInvoke(argsJson);
|
||||
DotNetDispatcher.EndInvokeJS(jsRuntime, argsJson);
|
||||
|
||||
// Assert
|
||||
Assert.True(task.IsCanceled);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task EndInvoke_WithNullError() => WithJSRuntime(async jsRuntime =>
|
||||
public async Task EndInvoke_WithNullError()
|
||||
{
|
||||
// Arrange
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var task = jsRuntime.InvokeAsync<TestDTO>("unimportant");
|
||||
var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, false, null }, JsonSerializerOptionsProvider.Options);
|
||||
var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, false, null }, jsRuntime.JsonSerializerOptions);
|
||||
|
||||
// Act
|
||||
DotNetDispatcher.EndInvoke(argsJson);
|
||||
DotNetDispatcher.EndInvokeJS(jsRuntime, argsJson);
|
||||
|
||||
// Assert
|
||||
var ex = await Assert.ThrowsAsync<JSException>(() => task);
|
||||
var ex = await Assert.ThrowsAsync<JSException>(async () => await task);
|
||||
Assert.Empty(ex.Message);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task CanInvokeInstanceMethodWithParams() => WithJSRuntime(jsRuntime =>
|
||||
public void CanInvokeInstanceMethodWithParams()
|
||||
{
|
||||
// Arrange: Track some instance plus another object we'll pass as a param
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var targetInstance = new SomePublicType();
|
||||
var arg2 = new TestDTO { IntVal = 1234, StringVal = "My string" };
|
||||
jsRuntime.Invoke<object>("unimportant",
|
||||
DotNetObjectRef.Create(targetInstance),
|
||||
DotNetObjectRef.Create(arg2));
|
||||
DotNetObjectReference.Create(targetInstance),
|
||||
DotNetObjectReference.Create(arg2));
|
||||
var argsJson = "[\"myvalue\",{\"__dotNetObject\":2}]";
|
||||
|
||||
// Act
|
||||
var resultJson = DotNetDispatcher.Invoke(null, "InvokableInstanceMethod", 1, argsJson);
|
||||
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, "InvokableInstanceMethod", 1, default), argsJson);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("[\"You passed myvalue\",{\"__dotNetObject\":3}]", resultJson);
|
||||
var resultDto = (TestDTO)jsRuntime.ObjectRefManager.FindDotNetObject(3);
|
||||
var resultDto = ((DotNetObjectReference<TestDTO>)jsRuntime.GetObjectReference(3)).Value;
|
||||
Assert.Equal(1235, resultDto.IntVal);
|
||||
Assert.Equal("MY STRING", resultDto.StringVal);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task CannotInvokeWithFewerNumberOfParameters() => WithJSRuntime(jsRuntime =>
|
||||
public void CannotInvokeWithFewerNumberOfParameters()
|
||||
{
|
||||
// Arrange
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var argsJson = JsonSerializer.Serialize(new object[]
|
||||
{
|
||||
new TestDTO { StringVal = "Another string", IntVal = 456 },
|
||||
new[] { 100, 200 },
|
||||
}, JsonSerializerOptionsProvider.Options);
|
||||
}, jsRuntime.JsonSerializerOptions);
|
||||
|
||||
// Act/Assert
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", default, argsJson);
|
||||
DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticWithParams", default, default), argsJson);
|
||||
});
|
||||
|
||||
Assert.Equal("The call to 'InvocableStaticWithParams' expects '3' parameters, but received '2'.", ex.Message);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task CannotInvokeWithMoreParameters() => WithJSRuntime(jsRuntime =>
|
||||
public void CannotInvokeWithMoreParameters()
|
||||
{
|
||||
// Arrange
|
||||
var objectRef = DotNetObjectRef.Create(new TestDTO { IntVal = 4 });
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var objectRef = DotNetObjectReference.Create(new TestDTO { IntVal = 4 });
|
||||
var argsJson = JsonSerializer.Serialize(new object[]
|
||||
{
|
||||
new TestDTO { StringVal = "Another string", IntVal = 456 },
|
||||
new[] { 100, 200 },
|
||||
objectRef,
|
||||
7,
|
||||
}, JsonSerializerOptionsProvider.Options);
|
||||
}, jsRuntime.JsonSerializerOptions);
|
||||
|
||||
// Act/Assert
|
||||
var ex = Assert.Throws<JsonException>(() =>
|
||||
{
|
||||
DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", default, argsJson);
|
||||
DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticWithParams", default, default), argsJson);
|
||||
});
|
||||
|
||||
Assert.Equal("Unexpected JSON token Number. Ensure that the call to `InvocableStaticWithParams' is supplied with exactly '3' parameters.", ex.Message);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task CanInvokeAsyncMethod() => WithJSRuntime(async jsRuntime =>
|
||||
public async Task CanInvokeAsyncMethod()
|
||||
{
|
||||
// Arrange: Track some instance plus another object we'll pass as a param
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var targetInstance = new SomePublicType();
|
||||
var arg2 = new TestDTO { IntVal = 1234, StringVal = "My string" };
|
||||
var arg1Ref = DotNetObjectRef.Create(targetInstance);
|
||||
var arg2Ref = DotNetObjectRef.Create(arg2);
|
||||
var arg1Ref = DotNetObjectReference.Create(targetInstance);
|
||||
var arg2Ref = DotNetObjectReference.Create(arg2);
|
||||
jsRuntime.Invoke<object>("unimportant", arg1Ref, arg2Ref);
|
||||
|
||||
// Arrange: all args
|
||||
|
|
@ -381,112 +414,114 @@ namespace Microsoft.JSInterop
|
|||
{
|
||||
new TestDTO { IntVal = 1000, StringVal = "String via JSON" },
|
||||
arg2Ref,
|
||||
}, JsonSerializerOptionsProvider.Options);
|
||||
}, jsRuntime.JsonSerializerOptions);
|
||||
|
||||
// Act
|
||||
var callId = "123";
|
||||
var resultTask = jsRuntime.NextInvocationTask;
|
||||
DotNetDispatcher.BeginInvoke(callId, null, "InvokableAsyncMethod", 1, argsJson);
|
||||
DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(null, "InvokableAsyncMethod", 1, callId), argsJson);
|
||||
await resultTask;
|
||||
|
||||
// Assert: Correct completion information
|
||||
Assert.Equal(callId, jsRuntime.LastCompletionCallId);
|
||||
Assert.True(jsRuntime.LastCompletionStatus);
|
||||
var result = Assert.IsType<object[]>(jsRuntime.LastCompletionResult);
|
||||
Assert.True(jsRuntime.LastCompletionResult.Success);
|
||||
var result = Assert.IsType<object[]>(jsRuntime.LastCompletionResult.Result);
|
||||
var resultDto1 = Assert.IsType<TestDTO>(result[0]);
|
||||
|
||||
Assert.Equal("STRING VIA JSON", resultDto1.StringVal);
|
||||
Assert.Equal(2000, resultDto1.IntVal);
|
||||
|
||||
// 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;
|
||||
Assert.Equal("MY STRING", resultDto2.StringVal);
|
||||
Assert.Equal(2468, resultDto2.IntVal);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task CanInvokeSyncThrowingMethod() => WithJSRuntime(async jsRuntime =>
|
||||
public async Task CanInvokeSyncThrowingMethod()
|
||||
{
|
||||
// Arrange
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
|
||||
// Act
|
||||
var callId = "123";
|
||||
var resultTask = jsRuntime.NextInvocationTask;
|
||||
DotNetDispatcher.BeginInvoke(callId, thisAssemblyName, nameof(ThrowingClass.ThrowingMethod), default, default);
|
||||
DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, nameof(ThrowingClass.ThrowingMethod), default, callId), default);
|
||||
|
||||
await resultTask; // This won't throw, it sets properties on the jsRuntime.
|
||||
|
||||
// Assert
|
||||
Assert.Equal(callId, jsRuntime.LastCompletionCallId);
|
||||
Assert.False(jsRuntime.LastCompletionStatus); // Fails
|
||||
Assert.False(jsRuntime.LastCompletionResult.Success); // Fails
|
||||
|
||||
// Make sure the method that threw the exception shows up in the call stack
|
||||
// https://github.com/aspnet/AspNetCore/issues/8612
|
||||
var exception = jsRuntime.LastCompletionResult is ExceptionDispatchInfo edi ? edi.SourceException.ToString() : null;
|
||||
Assert.Contains(nameof(ThrowingClass.ThrowingMethod), exception);
|
||||
});
|
||||
Assert.Contains(nameof(ThrowingClass.ThrowingMethod), jsRuntime.LastCompletionResult.Exception.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task CanInvokeAsyncThrowingMethod() => WithJSRuntime(async jsRuntime =>
|
||||
public async Task CanInvokeAsyncThrowingMethod()
|
||||
{
|
||||
// Arrange
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
|
||||
// Act
|
||||
var callId = "123";
|
||||
var resultTask = jsRuntime.NextInvocationTask;
|
||||
DotNetDispatcher.BeginInvoke(callId, thisAssemblyName, nameof(ThrowingClass.AsyncThrowingMethod), default, default);
|
||||
DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, nameof(ThrowingClass.AsyncThrowingMethod), default, callId), default);
|
||||
|
||||
await resultTask; // This won't throw, it sets properties on the jsRuntime.
|
||||
|
||||
// Assert
|
||||
Assert.Equal(callId, jsRuntime.LastCompletionCallId);
|
||||
Assert.False(jsRuntime.LastCompletionStatus); // Fails
|
||||
Assert.False(jsRuntime.LastCompletionResult.Success); // Fails
|
||||
|
||||
// Make sure the method that threw the exception shows up in the call stack
|
||||
// https://github.com/aspnet/AspNetCore/issues/8612
|
||||
var exception = jsRuntime.LastCompletionResult is ExceptionDispatchInfo edi ? edi.SourceException.ToString() : null;
|
||||
Assert.Contains(nameof(ThrowingClass.AsyncThrowingMethod), exception);
|
||||
});
|
||||
Assert.Contains(nameof(ThrowingClass.AsyncThrowingMethod), jsRuntime.LastCompletionResult.Exception.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task BeginInvoke_ThrowsWithInvalidArgsJson_WithCallId() => WithJSRuntime(async jsRuntime =>
|
||||
public async Task BeginInvoke_ThrowsWithInvalidArgsJson_WithCallId()
|
||||
{
|
||||
// Arrange
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var callId = "123";
|
||||
var resultTask = jsRuntime.NextInvocationTask;
|
||||
DotNetDispatcher.BeginInvoke(callId, thisAssemblyName, "InvocableStaticWithParams", default, "<xml>not json</xml>");
|
||||
DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticWithParams", default, callId), "<xml>not json</xml>");
|
||||
|
||||
await resultTask; // This won't throw, it sets properties on the jsRuntime.
|
||||
|
||||
// Assert
|
||||
Assert.Equal(callId, jsRuntime.LastCompletionCallId);
|
||||
Assert.False(jsRuntime.LastCompletionStatus); // Fails
|
||||
var result = Assert.IsType<ExceptionDispatchInfo>(jsRuntime.LastCompletionResult);
|
||||
Assert.Contains("JsonReaderException: '<' is an invalid start of a value.", result.SourceException.ToString());
|
||||
});
|
||||
Assert.False(jsRuntime.LastCompletionResult.Success); // Fails
|
||||
var exception = jsRuntime.LastCompletionResult.Exception;
|
||||
Assert.Contains("JsonReaderException: '<' is an invalid start of a value.", exception.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task BeginInvoke_ThrowsWithInvalid_DotNetObjectRef() => WithJSRuntime(jsRuntime =>
|
||||
public void BeginInvoke_ThrowsWithInvalid_DotNetObjectRef()
|
||||
{
|
||||
// Arrange
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var callId = "123";
|
||||
var resultTask = jsRuntime.NextInvocationTask;
|
||||
DotNetDispatcher.BeginInvoke(callId, null, "InvokableInstanceVoid", 1, null);
|
||||
DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(null, "InvokableInstanceVoid", 1, callId), null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(callId, jsRuntime.LastCompletionCallId);
|
||||
Assert.False(jsRuntime.LastCompletionStatus); // Fails
|
||||
var result = Assert.IsType<ExceptionDispatchInfo>(jsRuntime.LastCompletionResult);
|
||||
Assert.StartsWith("System.ArgumentException: There is no tracked object with id '1'. Perhaps the DotNetObjectRef instance was already disposed.", result.SourceException.ToString());
|
||||
});
|
||||
Assert.False(jsRuntime.LastCompletionResult.Success); // Fails
|
||||
var exception = jsRuntime.LastCompletionResult.Exception;
|
||||
Assert.StartsWith("System.ArgumentException: There is no tracked object with id '1'. Perhaps the DotNetObjectReference instance was already disposed.", exception.ToString());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("<xml>")]
|
||||
public void ParseArguments_ThrowsIfJsonIsInvalid(string arguments)
|
||||
{
|
||||
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.ParseArguments("SomeMethod", arguments, new[] { typeof(string) }));
|
||||
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.ParseArguments(new TestJSRuntime(), "SomeMethod", arguments, new[] { typeof(string) }));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -495,7 +530,7 @@ namespace Microsoft.JSInterop
|
|||
public void ParseArguments_ThrowsIfTheArgsJsonIsNotArray(string arguments)
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.ParseArguments("SomeMethod", arguments, new[] { typeof(string) }));
|
||||
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.ParseArguments(new TestJSRuntime(), "SomeMethod", arguments, new[] { typeof(string) }));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -504,7 +539,7 @@ namespace Microsoft.JSInterop
|
|||
public void ParseArguments_ThrowsIfTheArgsJsonIsInvalidArray(string arguments)
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.ParseArguments("SomeMethod", arguments, new[] { typeof(string) }));
|
||||
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.ParseArguments(new TestJSRuntime(), "SomeMethod", arguments, new[] { typeof(string) }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -514,7 +549,7 @@ namespace Microsoft.JSInterop
|
|||
var arguments = "[\"Hello\", 2]";
|
||||
|
||||
// Act
|
||||
var result = DotNetDispatcher.ParseArguments("SomeMethod", arguments, new[] { typeof(string), typeof(int), });
|
||||
var result = DotNetDispatcher.ParseArguments(new TestJSRuntime(), "SomeMethod", arguments, new[] { typeof(string), typeof(int), });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new object[] { "Hello", 2 }, result);
|
||||
|
|
@ -527,7 +562,7 @@ namespace Microsoft.JSInterop
|
|||
var arguments = "[{\"IntVal\": 7}]";
|
||||
|
||||
// Act
|
||||
var result = DotNetDispatcher.ParseArguments("SomeMethod", arguments, new[] { typeof(TestDTO), });
|
||||
var result = DotNetDispatcher.ParseArguments(new TestJSRuntime(), "SomeMethod", arguments, new[] { typeof(TestDTO), });
|
||||
|
||||
// Assert
|
||||
var value = Assert.IsType<TestDTO>(Assert.Single(result));
|
||||
|
|
@ -542,7 +577,7 @@ namespace Microsoft.JSInterop
|
|||
var arguments = "[4, null]";
|
||||
|
||||
// Act
|
||||
var result = DotNetDispatcher.ParseArguments("SomeMethod", arguments, new[] { typeof(int), typeof(TestDTO), });
|
||||
var result = DotNetDispatcher.ParseArguments(new TestJSRuntime(), "SomeMethod", arguments, new[] { typeof(int), typeof(TestDTO), });
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
|
|
@ -559,92 +594,72 @@ namespace Microsoft.JSInterop
|
|||
var arguments = "[4, {\"__dotNetObject\": 7}]";
|
||||
|
||||
// Act
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => DotNetDispatcher.ParseArguments(method, arguments, new[] { typeof(int), typeof(TestDTO), }));
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => DotNetDispatcher.ParseArguments(new TestJSRuntime(), method, arguments, new[] { typeof(int), typeof(TestDTO), }));
|
||||
|
||||
// Assert
|
||||
Assert.Equal($"In call to '{method}', parameter of type '{nameof(TestDTO)}' at index 2 must be declared as type 'DotNetObjectRef<TestDTO>' to receive the incoming value.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseEndInvokeArguments_ThrowsIfJsonIsEmptyString()
|
||||
public void EndInvokeJS_ThrowsIfJsonIsEmptyString()
|
||||
{
|
||||
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.ParseEndInvokeArguments(new TestJSRuntime(), ""));
|
||||
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.EndInvokeJS(new TestJSRuntime(), ""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseEndInvokeArguments_ThrowsIfJsonIsNotArray()
|
||||
public void EndInvokeJS_ThrowsIfJsonIsNotArray()
|
||||
{
|
||||
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.ParseEndInvokeArguments(new TestJSRuntime(), "{\"key\": \"value\"}"));
|
||||
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.EndInvokeJS(new TestJSRuntime(), "{\"key\": \"value\"}"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseEndInvokeArguments_ThrowsIfJsonArrayIsInComplete()
|
||||
public void EndInvokeJS_ThrowsIfJsonArrayIsInComplete()
|
||||
{
|
||||
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.ParseEndInvokeArguments(new TestJSRuntime(), "[7, false"));
|
||||
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.EndInvokeJS(new TestJSRuntime(), "[7, false"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseEndInvokeArguments_ThrowsIfJsonArrayHasMoreThan3Arguments()
|
||||
public void EndInvokeJS_ThrowsIfJsonArrayHasMoreThan3Arguments()
|
||||
{
|
||||
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.ParseEndInvokeArguments(new TestJSRuntime(), "[7, false, \"Hello\", 5]"));
|
||||
Assert.ThrowsAny<JsonException>(() => DotNetDispatcher.EndInvokeJS(new TestJSRuntime(), "[7, false, \"Hello\", 5]"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseEndInvokeArguments_Works()
|
||||
public void EndInvokeJS_Works()
|
||||
{
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var task = jsRuntime.InvokeAsync<TestDTO>("somemethod");
|
||||
|
||||
DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, {{\"intVal\": 7}}]");
|
||||
DotNetDispatcher.EndInvokeJS(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, {{\"intVal\": 7}}]");
|
||||
|
||||
Assert.True(task.IsCompleted && task.Status == TaskStatus.RanToCompletion);
|
||||
Assert.True(task.IsCompletedSuccessfully);
|
||||
Assert.Equal(7, task.Result.IntVal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseEndInvokeArguments_WithArrayValue()
|
||||
public void EndInvokeJS_WithArrayValue()
|
||||
{
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var task = jsRuntime.InvokeAsync<int[]>("somemethod");
|
||||
|
||||
DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, [1, 2, 3]]");
|
||||
DotNetDispatcher.EndInvokeJS(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);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseEndInvokeArguments_WithNullValue()
|
||||
public void EndInvokeJS_WithNullValue()
|
||||
{
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var task = jsRuntime.InvokeAsync<TestDTO>("somemethod");
|
||||
|
||||
DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, null]");
|
||||
DotNetDispatcher.EndInvokeJS(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, null]");
|
||||
|
||||
Assert.True(task.IsCompleted && task.Status == TaskStatus.RanToCompletion);
|
||||
Assert.True(task.IsCompletedSuccessfully);
|
||||
Assert.Null(task.Result);
|
||||
}
|
||||
|
||||
Task WithJSRuntime(Action<TestJSRuntime> testCode)
|
||||
{
|
||||
return WithJSRuntime(jsRuntime =>
|
||||
{
|
||||
testCode(jsRuntime);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
async Task WithJSRuntime(Func<TestJSRuntime, Task> testCode)
|
||||
{
|
||||
// Since the tests rely on the asynclocal JSRuntime.Current, ensure we
|
||||
// are on a distinct async context with a non-null JSRuntime.Current
|
||||
await Task.Yield();
|
||||
|
||||
var runtime = new TestJSRuntime();
|
||||
JSRuntime.SetCurrentJSRuntime(runtime);
|
||||
await testCode(runtime);
|
||||
}
|
||||
|
||||
internal class SomeInteralType
|
||||
{
|
||||
[JSInvokable("MethodOnInternalType")] public void MyMethod() { }
|
||||
|
|
@ -671,7 +686,7 @@ namespace Microsoft.JSInterop
|
|||
=> new TestDTO { StringVal = "Test", IntVal = 123 };
|
||||
|
||||
[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 TestDTO // Return via JSON marshalling
|
||||
|
|
@ -679,7 +694,7 @@ namespace Microsoft.JSInterop
|
|||
StringVal = dtoViaJson.StringVal.ToUpperInvariant(),
|
||||
IntVal = dtoViaJson.IntVal + incrementAmounts.Sum()
|
||||
},
|
||||
DotNetObjectRef.Create(new TestDTO // Return by ref
|
||||
DotNetObjectReference.Create(new TestDTO // Return by ref
|
||||
{
|
||||
StringVal = dtoByRef.Value.StringVal.ToUpperInvariant(),
|
||||
IntVal = dtoByRef.Value.IntVal + incrementAmounts.Sum()
|
||||
|
|
@ -701,7 +716,7 @@ namespace Microsoft.JSInterop
|
|||
}
|
||||
|
||||
[JSInvokable]
|
||||
public object[] InvokableInstanceMethod(string someString, DotNetObjectRef<TestDTO> someDTORef)
|
||||
public object[] InvokableInstanceMethod(string someString, DotNetObjectReference<TestDTO> someDTORef)
|
||||
{
|
||||
var someDTO = someDTORef.Value;
|
||||
// Returning an array to make the point that object references
|
||||
|
|
@ -709,7 +724,7 @@ namespace Microsoft.JSInterop
|
|||
return new object[]
|
||||
{
|
||||
$"You passed {someString}",
|
||||
DotNetObjectRef.Create(new TestDTO
|
||||
DotNetObjectReference.Create(new TestDTO
|
||||
{
|
||||
IntVal = someDTO.IntVal + 1,
|
||||
StringVal = someDTO.StringVal.ToUpperInvariant()
|
||||
|
|
@ -718,7 +733,7 @@ namespace Microsoft.JSInterop
|
|||
}
|
||||
|
||||
[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);
|
||||
var dtoByRef = dtoByRefWrapper.Value;
|
||||
|
|
@ -729,7 +744,7 @@ namespace Microsoft.JSInterop
|
|||
StringVal = dtoViaJson.StringVal.ToUpperInvariant(),
|
||||
IntVal = dtoViaJson.IntVal * 2,
|
||||
},
|
||||
DotNetObjectRef.Create(new TestDTO // Return by ref
|
||||
DotNetObjectReference.Create(new TestDTO // Return by ref
|
||||
{
|
||||
StringVal = dtoByRef.StringVal.ToUpperInvariant(),
|
||||
IntVal = dtoByRef.IntVal * 2,
|
||||
|
|
@ -775,7 +790,7 @@ namespace Microsoft.JSInterop
|
|||
}
|
||||
}
|
||||
|
||||
public class TestJSRuntime : JSInProcessRuntimeBase
|
||||
public class TestJSRuntime : JSInProcessRuntime
|
||||
{
|
||||
private TaskCompletionSource<object> _nextInvocationTcs = new TaskCompletionSource<object>();
|
||||
public Task NextInvocationTask => _nextInvocationTcs.Task;
|
||||
|
|
@ -784,8 +799,7 @@ namespace Microsoft.JSInterop
|
|||
public string LastInvocationArgsJson { get; private set; }
|
||||
|
||||
public string LastCompletionCallId { get; private set; }
|
||||
public bool LastCompletionStatus { get; private set; }
|
||||
public object LastCompletionResult { get; private set; }
|
||||
public DotNetInvocationResult LastCompletionResult { get; private set; }
|
||||
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
||||
{
|
||||
|
|
@ -806,17 +820,10 @@ namespace Microsoft.JSInterop
|
|||
return null;
|
||||
}
|
||||
|
||||
protected internal override void EndInvokeDotNet(
|
||||
string callId,
|
||||
bool success,
|
||||
object resultOrError,
|
||||
string assemblyName,
|
||||
string methodIdentifier,
|
||||
long dotNetObjectId)
|
||||
protected internal override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult)
|
||||
{
|
||||
LastCompletionCallId = callId;
|
||||
LastCompletionStatus = success;
|
||||
LastCompletionResult = resultOrError;
|
||||
LastCompletionCallId = invocationInfo.CallId;
|
||||
LastCompletionResult = invocationResult;
|
||||
_nextInvocationTcs.SetResult(null);
|
||||
_nextInvocationTcs = new TaskCompletionSource<object>();
|
||||
}
|
||||
|
|
@ -2,116 +2,118 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using static Microsoft.JSInterop.TestJSRuntime;
|
||||
|
||||
namespace Microsoft.JSInterop.Tests
|
||||
namespace Microsoft.JSInterop.Infrastructure
|
||||
{
|
||||
public class DotNetObjectReferenceJsonConverterTest
|
||||
{
|
||||
private readonly JSRuntime JSRuntime = new TestJSRuntime();
|
||||
private JsonSerializerOptions JsonSerializerOptions => JSRuntime.JsonSerializerOptions;
|
||||
|
||||
[Fact]
|
||||
public Task Read_Throws_IfJsonIsMissingDotNetObjectProperty() => WithJSRuntime(_ =>
|
||||
public void Read_Throws_IfJsonIsMissingDotNetObjectProperty()
|
||||
{
|
||||
// Arrange
|
||||
var dotNetObjectRef = DotNetObjectRef.Create(new TestModel());
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var dotNetObjectRef = DotNetObjectReference.Create(new TestModel());
|
||||
|
||||
var json = "{}";
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<DotNetObjectRef<TestModel>>(json));
|
||||
var ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<DotNetObjectReference<TestModel>>(json, JsonSerializerOptions));
|
||||
Assert.Equal("Required property __dotNetObject not found.", ex.Message);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task Read_Throws_IfJsonContainsUnknownContent() => WithJSRuntime(_ =>
|
||||
public void Read_Throws_IfJsonContainsUnknownContent()
|
||||
{
|
||||
// Arrange
|
||||
var dotNetObjectRef = DotNetObjectRef.Create(new TestModel());
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var dotNetObjectRef = DotNetObjectReference.Create(new TestModel());
|
||||
|
||||
var json = "{\"foo\":2}";
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<DotNetObjectRef<TestModel>>(json));
|
||||
var ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<DotNetObjectReference<TestModel>>(json, JsonSerializerOptions));
|
||||
Assert.Equal("Unexcepted JSON property foo.", ex.Message);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task Read_Throws_IfJsonIsIncomplete() => WithJSRuntime(_ =>
|
||||
public void Read_Throws_IfJsonIsIncomplete()
|
||||
{
|
||||
// Arrange
|
||||
var input = new TestModel();
|
||||
var dotNetObjectRef = DotNetObjectRef.Create(input);
|
||||
var objectId = dotNetObjectRef.ObjectId;
|
||||
var dotNetObjectRef = DotNetObjectReference.Create(input);
|
||||
var objectId = JSRuntime.TrackObjectReference(dotNetObjectRef);
|
||||
|
||||
var json = $"{{\"__dotNetObject\":{objectId}";
|
||||
|
||||
// Act & Assert
|
||||
var ex = Record.Exception(() => JsonSerializer.Deserialize<DotNetObjectRef<TestModel>>(json));
|
||||
var ex = Record.Exception(() => JsonSerializer.Deserialize<DotNetObjectReference<TestModel>>(json, JsonSerializerOptions));
|
||||
Assert.IsAssignableFrom<JsonException>(ex);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task Read_Throws_IfDotNetObjectIdAppearsMultipleTimes() => WithJSRuntime(_ =>
|
||||
public void Read_Throws_IfDotNetObjectIdAppearsMultipleTimes()
|
||||
{
|
||||
// Arrange
|
||||
var input = new TestModel();
|
||||
var dotNetObjectRef = DotNetObjectRef.Create(input);
|
||||
var objectId = dotNetObjectRef.ObjectId;
|
||||
var dotNetObjectRef = DotNetObjectReference.Create(input);
|
||||
var objectId = JSRuntime.TrackObjectReference(dotNetObjectRef);
|
||||
|
||||
var json = $"{{\"__dotNetObject\":{objectId},\"__dotNetObject\":{objectId}}}";
|
||||
|
||||
// Act & Assert
|
||||
var ex = Record.Exception(() => JsonSerializer.Deserialize<DotNetObjectRef<TestModel>>(json));
|
||||
var ex = Record.Exception(() => JsonSerializer.Deserialize<DotNetObjectReference<TestModel>>(json, JsonSerializerOptions));
|
||||
Assert.IsAssignableFrom<JsonException>(ex);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task Read_ReadsJson() => WithJSRuntime(_ =>
|
||||
public void Read_ReadsJson()
|
||||
{
|
||||
// Arrange
|
||||
var input = new TestModel();
|
||||
var dotNetObjectRef = DotNetObjectRef.Create(input);
|
||||
var objectId = dotNetObjectRef.ObjectId;
|
||||
var dotNetObjectRef = DotNetObjectReference.Create(input);
|
||||
var objectId = JSRuntime.TrackObjectReference(dotNetObjectRef);
|
||||
|
||||
var json = $"{{\"__dotNetObject\":{objectId}}}";
|
||||
|
||||
// Act
|
||||
var deserialized = JsonSerializer.Deserialize<DotNetObjectRef<TestModel>>(json);
|
||||
var deserialized = JsonSerializer.Deserialize<DotNetObjectReference<TestModel>>(json, JsonSerializerOptions);
|
||||
|
||||
// Assert
|
||||
Assert.Same(input, deserialized.Value);
|
||||
Assert.Equal(objectId, deserialized.ObjectId);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task Read_ReturnsTheCorrectInstance() => WithJSRuntime(_ =>
|
||||
public void Read_ReturnsTheCorrectInstance()
|
||||
{
|
||||
// Arrange
|
||||
// Track a few instances and verify that the deserialized value returns the correct value.
|
||||
var instance1 = new TestModel();
|
||||
var instance2 = new TestModel();
|
||||
var ref1 = DotNetObjectRef.Create(instance1);
|
||||
var ref2 = DotNetObjectRef.Create(instance2);
|
||||
var ref1 = DotNetObjectReference.Create(instance1);
|
||||
var ref2 = DotNetObjectReference.Create(instance2);
|
||||
|
||||
var json = $"[{{\"__dotNetObject\":{ref2.ObjectId}}},{{\"__dotNetObject\":{ref1.ObjectId}}}]";
|
||||
var json = $"[{{\"__dotNetObject\":{JSRuntime.TrackObjectReference(ref1)}}},{{\"__dotNetObject\":{JSRuntime.TrackObjectReference(ref2)}}}]";
|
||||
|
||||
// Act
|
||||
var deserialized = JsonSerializer.Deserialize<DotNetObjectRef<TestModel>[]>(json);
|
||||
var deserialized = JsonSerializer.Deserialize<DotNetObjectReference<TestModel>[]>(json, JsonSerializerOptions);
|
||||
|
||||
// Assert
|
||||
Assert.Same(instance2, deserialized[0].Value);
|
||||
Assert.Same(instance1, deserialized[1].Value);
|
||||
});
|
||||
Assert.Same(instance1, deserialized[0].Value);
|
||||
Assert.Same(instance2, deserialized[1].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task Read_ReadsJson_WithFormatting() => WithJSRuntime(_ =>
|
||||
public void Read_ReadsJson_WithFormatting()
|
||||
{
|
||||
// Arrange
|
||||
var input = new TestModel();
|
||||
var dotNetObjectRef = DotNetObjectRef.Create(input);
|
||||
var objectId = dotNetObjectRef.ObjectId;
|
||||
var dotNetObjectRef = DotNetObjectReference.Create(input);
|
||||
var objectId = JSRuntime.TrackObjectReference(dotNetObjectRef);
|
||||
|
||||
var json =
|
||||
@$"{{
|
||||
|
|
@ -119,27 +121,27 @@ namespace Microsoft.JSInterop.Tests
|
|||
}}";
|
||||
|
||||
// Act
|
||||
var deserialized = JsonSerializer.Deserialize<DotNetObjectRef<TestModel>>(json);
|
||||
var deserialized = JsonSerializer.Deserialize<DotNetObjectReference<TestModel>>(json, JsonSerializerOptions);
|
||||
|
||||
// Assert
|
||||
Assert.Same(input, deserialized.Value);
|
||||
Assert.Equal(objectId, deserialized.ObjectId);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task WriteJsonTwice_KeepsObjectId() => WithJSRuntime(_ =>
|
||||
public void WriteJsonTwice_KeepsObjectId()
|
||||
{
|
||||
// Arrange
|
||||
var dotNetObjectRef = DotNetObjectRef.Create(new TestModel());
|
||||
var dotNetObjectRef = DotNetObjectReference.Create(new TestModel());
|
||||
|
||||
// Act
|
||||
var json1 = JsonSerializer.Serialize(dotNetObjectRef);
|
||||
var json2 = JsonSerializer.Serialize(dotNetObjectRef);
|
||||
var json1 = JsonSerializer.Serialize(dotNetObjectRef, JsonSerializerOptions);
|
||||
var json2 = JsonSerializer.Serialize(dotNetObjectRef, JsonSerializerOptions);
|
||||
|
||||
// Assert
|
||||
Assert.Equal($"{{\"__dotNetObject\":{dotNetObjectRef.ObjectId}}}", json1);
|
||||
Assert.Equal(json1, json2);
|
||||
});
|
||||
}
|
||||
|
||||
private class TestModel
|
||||
{
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,9 +4,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.JSInterop.Infrastructure;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.JSInterop.Tests
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
public class JSInProcessRuntimeBaseTest
|
||||
{
|
||||
|
|
@ -18,7 +19,6 @@ namespace Microsoft.JSInterop.Tests
|
|||
{
|
||||
NextResultJson = "{\"intValue\":123,\"stringValue\":\"Hello\"}"
|
||||
};
|
||||
JSRuntime.SetCurrentJSRuntime(runtime);
|
||||
|
||||
// Act
|
||||
var syncResult = runtime.Invoke<TestDTO>("test identifier 1", "arg1", 123, true);
|
||||
|
|
@ -36,19 +36,18 @@ namespace Microsoft.JSInterop.Tests
|
|||
{
|
||||
// Arrange
|
||||
var runtime = new TestJSInProcessRuntime { NextResultJson = null };
|
||||
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 syncResult = runtime.Invoke<DotNetObjectRef<object>>("test identifier",
|
||||
DotNetObjectRef.Create(obj1),
|
||||
var syncResult = runtime.Invoke<DotNetObjectReference<object>>("test identifier",
|
||||
DotNetObjectReference.Create(obj1),
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{ "obj2", DotNetObjectRef.Create(obj2) },
|
||||
{ "obj3", DotNetObjectRef.Create(obj3) },
|
||||
{ "obj2", DotNetObjectReference.Create(obj2) },
|
||||
{ "obj3", DotNetObjectReference.Create(obj3) },
|
||||
});
|
||||
|
||||
// Assert: Handles null result string
|
||||
|
|
@ -60,9 +59,9 @@ namespace Microsoft.JSInterop.Tests
|
|||
Assert.Equal("[{\"__dotNetObject\":1},{\"obj2\":{\"__dotNetObject\":2},\"obj3\":{\"__dotNetObject\":3}}]", call.ArgsJson);
|
||||
|
||||
// Assert: Objects were tracked
|
||||
Assert.Same(obj1, runtime.ObjectRefManager.FindDotNetObject(1));
|
||||
Assert.Same(obj2, runtime.ObjectRefManager.FindDotNetObject(2));
|
||||
Assert.Same(obj3, runtime.ObjectRefManager.FindDotNetObject(3));
|
||||
Assert.Same(obj1, runtime.GetObjectReference(1).Value);
|
||||
Assert.Same(obj2, runtime.GetObjectReference(2).Value);
|
||||
Assert.Same(obj3, runtime.GetObjectReference(3).Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -73,16 +72,15 @@ namespace Microsoft.JSInterop.Tests
|
|||
{
|
||||
NextResultJson = "[{\"__dotNetObject\":2},{\"__dotNetObject\":1}]"
|
||||
};
|
||||
JSRuntime.SetCurrentJSRuntime(runtime);
|
||||
var obj1 = new object();
|
||||
var obj2 = new object();
|
||||
|
||||
// Act
|
||||
var syncResult = runtime.Invoke<DotNetObjectRef<object>[]>(
|
||||
var syncResult = runtime.Invoke<DotNetObjectReference<object>[]>(
|
||||
"test identifier",
|
||||
DotNetObjectRef.Create(obj1),
|
||||
DotNetObjectReference.Create(obj1),
|
||||
"some other arg",
|
||||
DotNetObjectRef.Create(obj2));
|
||||
DotNetObjectReference.Create(obj2));
|
||||
var call = runtime.InvokeCalls.Single();
|
||||
|
||||
// Assert
|
||||
|
|
@ -95,7 +93,7 @@ namespace Microsoft.JSInterop.Tests
|
|||
public string StringValue { get; set; }
|
||||
}
|
||||
|
||||
class TestJSInProcessRuntime : JSInProcessRuntimeBase
|
||||
class TestJSInProcessRuntime : JSInProcessRuntime
|
||||
{
|
||||
public List<InvokeArgs> InvokeCalls { get; set; } = new List<InvokeArgs>();
|
||||
|
||||
|
|
@ -116,8 +114,8 @@ namespace Microsoft.JSInterop.Tests
|
|||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
||||
=> throw new NotImplementedException("This test only covers sync calls");
|
||||
|
||||
protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId) =>
|
||||
throw new NotImplementedException("This test only covers sync calls");
|
||||
protected internal override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult)
|
||||
=> throw new NotImplementedException("This test only covers sync calls");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
Assert.Same(obj1, runtime.ObjectRefManager.FindDotNetObject(2));
|
||||
Assert.Same(obj2, runtime.ObjectRefManager.FindDotNetObject(3));
|
||||
Assert.Same(obj3, runtime.ObjectRefManager.FindDotNetObject(4));
|
||||
}
|
||||
|
||||
[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,36 +4,388 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.JSInterop.Infrastructure;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.JSInterop.Tests
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
public class JSRuntimeTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task CanHaveDistinctJSRuntimeInstancesInEachAsyncContext()
|
||||
public void DispatchesAsyncCallsWithDistinctAsyncHandles()
|
||||
{
|
||||
var tasks = Enumerable.Range(0, 20).Select(async _ =>
|
||||
{
|
||||
var jsRuntime = new FakeJSRuntime();
|
||||
JSRuntime.SetCurrentJSRuntime(jsRuntime);
|
||||
await Task.Delay(50).ConfigureAwait(false);
|
||||
Assert.Same(jsRuntime, JSRuntime.Current);
|
||||
});
|
||||
// Arrange
|
||||
var runtime = new TestJSRuntime();
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
Assert.Null(JSRuntime.Current);
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
|
||||
private class FakeJSRuntime : IJSRuntime
|
||||
[Fact]
|
||||
public async Task InvokeAsync_CancelsAsyncTask_AfterDefaultTimeout()
|
||||
{
|
||||
public Task<T> InvokeAsync<T>(string identifier, params object[] args)
|
||||
=> throw new NotImplementedException();
|
||||
// Arrange
|
||||
var runtime = new TestJSRuntime();
|
||||
runtime.DefaultTimeout = TimeSpan.FromSeconds(1);
|
||||
|
||||
public Task<TValue> InvokeAsync<TValue>(string identifier, IEnumerable<object> args, CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
// 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();
|
||||
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\":2},\"obj3\":{\"__dotNetObject\":3},\"obj1SameRef\":{\"__dotNetObject\":1},\"obj1DifferentRef\":{\"__dotNetObject\":4}}]", call.ArgsJson);
|
||||
|
||||
// Assert: Objects were tracked
|
||||
Assert.Same(obj1Ref, runtime.GetObjectReference(1));
|
||||
Assert.Same(obj1, obj1Ref.Value);
|
||||
Assert.NotSame(obj1Ref, runtime.GetObjectReference(2));
|
||||
Assert.Same(obj2, runtime.GetObjectReference(2).Value);
|
||||
Assert.Same(obj3, runtime.GetObjectReference(3).Value);
|
||||
Assert.Same(obj1, runtime.GetObjectReference(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(DotNetInvocationInfo info) => $"An error ocurred while invoking '[{info.AssemblyName}]::{info.MethodIdentifier}'. Swapping to 'Development' environment will " +
|
||||
"display more detailed information about the error that occurred.";
|
||||
|
||||
var runtime = new TestJSRuntime()
|
||||
{
|
||||
OnDotNetException = (invocationInfo) => new JSError { Message = GetMessage(invocationInfo) }
|
||||
};
|
||||
|
||||
var exception = new Exception("Some really sensitive data in here");
|
||||
var invocation = new DotNetInvocationInfo("Assembly", "Method", 0, "0");
|
||||
var result = new DotNetInvocationResult(exception, default);
|
||||
|
||||
// Act
|
||||
runtime.EndInvokeDotNet(invocation, result);
|
||||
|
||||
// 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<DotNetInvocationInfo, object> OnDotNetException { get; set; }
|
||||
|
||||
protected internal override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult)
|
||||
{
|
||||
var resultOrError = invocationResult.Success ? invocationResult.Result : invocationResult.Exception;
|
||||
if (OnDotNetException != null && !invocationResult.Success)
|
||||
{
|
||||
resultOrError = OnDotNetException(invocationInfo);
|
||||
}
|
||||
|
||||
EndInvokeDotNetCalls.Add(new EndInvokeDotNetArgs
|
||||
{
|
||||
CallId = invocationInfo.CallId,
|
||||
Success = invocationResult.Success,
|
||||
ResultOrError = resultOrError,
|
||||
});
|
||||
}
|
||||
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
||||
{
|
||||
BeginInvokeCalls.Add(new BeginInvokeAsyncArgs
|
||||
{
|
||||
AsyncHandle = asyncHandle,
|
||||
Identifier = identifier,
|
||||
ArgsJson = argsJson,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,31 +2,20 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.JSInterop.Infrastructure;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
internal class TestJSRuntime : JSRuntimeBase
|
||||
internal class TestJSRuntime : JSRuntime
|
||||
{
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId)
|
||||
protected internal override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static async Task WithJSRuntime(Action<JSRuntimeBase> testCode)
|
||||
{
|
||||
// Since the tests rely on the asynclocal JSRuntime.Current, ensure we
|
||||
// are on a distinct async context with a non-null JSRuntime.Current
|
||||
await Task.Yield();
|
||||
|
||||
var runtime = new TestJSRuntime();
|
||||
JSRuntime.SetCurrentJSRuntime(runtime);
|
||||
testCode(runtime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@
|
|||
|
||||
namespace Mono.WebAssembly.Interop
|
||||
{
|
||||
public partial class MonoWebAssemblyJSRuntime : Microsoft.JSInterop.JSInProcessRuntimeBase
|
||||
public partial class MonoWebAssemblyJSRuntime : Microsoft.JSInterop.JSInProcessRuntime
|
||||
{
|
||||
public MonoWebAssemblyJSRuntime() { }
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) { }
|
||||
protected override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId) { }
|
||||
protected override void EndInvokeDotNet(Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo callInfo, in Microsoft.JSInterop.Infrastructure.DotNetInvocationResult dispatchResult) { }
|
||||
protected static void Initialize(Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime jsRuntime) { }
|
||||
protected override string InvokeJS(string identifier, string argsJson) { throw null; }
|
||||
public TRes InvokeUnmarshalled<TRes>(string identifier) { throw null; }
|
||||
public TRes InvokeUnmarshalled<T0, TRes>(string identifier, T0 arg0) { throw null; }
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Text.Json;
|
||||
using Microsoft.JSInterop;
|
||||
using Microsoft.JSInterop.Infrastructure;
|
||||
using WebAssembly.JSInterop;
|
||||
|
||||
namespace Mono.WebAssembly.Interop
|
||||
|
|
@ -13,8 +13,27 @@ namespace Mono.WebAssembly.Interop
|
|||
/// Provides methods for invoking JavaScript functions for applications running
|
||||
/// on the Mono WebAssembly runtime.
|
||||
/// </summary>
|
||||
public class MonoWebAssemblyJSRuntime : JSInProcessRuntimeBase
|
||||
public class MonoWebAssemblyJSRuntime : JSInProcessRuntime
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="MonoWebAssemblyJSRuntime"/> used to perform operations using <see cref="DotNetDispatcher"/>.
|
||||
/// </summary>
|
||||
private static MonoWebAssemblyJSRuntime Instance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="MonoWebAssemblyJSRuntime"/> to be used to perform operations using <see cref="DotNetDispatcher"/>.
|
||||
/// </summary>
|
||||
/// <param name="jsRuntime">The <see cref="MonoWebAssemblyJSRuntime"/> instance.</param>
|
||||
protected static void Initialize(MonoWebAssemblyJSRuntime jsRuntime)
|
||||
{
|
||||
if (Instance != null)
|
||||
{
|
||||
throw new InvalidOperationException("MonoWebAssemblyJSRuntime has already been initialized.");
|
||||
}
|
||||
|
||||
Instance = jsRuntime ?? throw new ArgumentNullException(nameof(jsRuntime));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string InvokeJS(string identifier, string argsJson)
|
||||
{
|
||||
|
|
@ -33,11 +52,14 @@ namespace Mono.WebAssembly.Interop
|
|||
|
||||
// Invoked via Mono's JS interop mechanism (invoke_method)
|
||||
private static string InvokeDotNet(string assemblyName, string methodIdentifier, string dotNetObjectId, string argsJson)
|
||||
=> DotNetDispatcher.Invoke(assemblyName, methodIdentifier, dotNetObjectId == null ? default : long.Parse(dotNetObjectId), argsJson);
|
||||
{
|
||||
var callInfo = new DotNetInvocationInfo(assemblyName, methodIdentifier, dotNetObjectId == null ? default : long.Parse(dotNetObjectId), callId: null);
|
||||
return DotNetDispatcher.Invoke(Instance, callInfo, argsJson);
|
||||
}
|
||||
|
||||
// Invoked via Mono's JS interop mechanism (invoke_method)
|
||||
private static void EndInvokeJS(string argsJson)
|
||||
=> DotNetDispatcher.EndInvoke(argsJson);
|
||||
=> DotNetDispatcher.EndInvokeJS(Instance, argsJson);
|
||||
|
||||
// Invoked via Mono's JS interop mechanism (invoke_method)
|
||||
private static void BeginInvokeDotNet(string callId, string assemblyNameOrDotNetObjectId, string methodIdentifier, string argsJson)
|
||||
|
|
@ -58,32 +80,20 @@ namespace Mono.WebAssembly.Interop
|
|||
assemblyName = assemblyNameOrDotNetObjectId;
|
||||
}
|
||||
|
||||
DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson);
|
||||
var callInfo = new DotNetInvocationInfo(assemblyName, methodIdentifier, dotNetObjectId, callId);
|
||||
DotNetDispatcher.BeginInvokeDotNet(Instance, callInfo, argsJson);
|
||||
}
|
||||
|
||||
protected override void EndInvokeDotNet(
|
||||
string callId,
|
||||
bool success,
|
||||
object resultOrError,
|
||||
string assemblyName,
|
||||
string methodIdentifier,
|
||||
long dotNetObjectId)
|
||||
protected override void EndInvokeDotNet(DotNetInvocationInfo callInfo, in DotNetInvocationResult dispatchResult)
|
||||
{
|
||||
// For failures, the common case is to call EndInvokeDotNet with the Exception object.
|
||||
// For these we'll serialize as something that's useful to receive on the JS side.
|
||||
// If the value is not an Exception, we'll just rely on it being directly JSON-serializable.
|
||||
if (!success && resultOrError is Exception ex)
|
||||
{
|
||||
resultOrError = ex.ToString();
|
||||
}
|
||||
else if (!success && resultOrError is ExceptionDispatchInfo edi)
|
||||
{
|
||||
resultOrError = edi.SourceException.ToString();
|
||||
}
|
||||
var resultOrError = dispatchResult.Success ? dispatchResult.Result : dispatchResult.Exception.ToString();
|
||||
|
||||
// We pass 0 as the async handle because we don't want the JS-side code to
|
||||
// send back any notification (we're just providing a result for an existing async call)
|
||||
var args = JsonSerializer.Serialize(new[] { callId, success, resultOrError }, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
|
||||
var args = JsonSerializer.Serialize(new[] { callInfo.CallId, dispatchResult.Success, resultOrError }, JsonSerializerOptions);
|
||||
BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", args);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,4 +20,4 @@ namespace Microsoft.AspNetCore.Testing
|
|||
public static bool IsMac =>
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System;
|
|||
using Xunit;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||||
[XunitTestCaseDiscoverer("Microsoft.AspNetCore.Testing.xunit." + nameof(ConditionalFactDiscoverer), "Microsoft.AspNetCore.Testing")]
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
internal class ConditionalFactDiscoverer : FactDiscoverer
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System;
|
|||
using Xunit;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||||
[XunitTestCaseDiscoverer("Microsoft.AspNetCore.Testing.xunit." + nameof(ConditionalTheoryDiscoverer), "Microsoft.AspNetCore.Testing")]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System.Collections.Generic;
|
|||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
internal class ConditionalTheoryDiscoverer : TheoryDiscoverer
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
|
||||
public sealed class DockerOnlyAttribute : Attribute, ITestCondition
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
/// <summary>
|
||||
/// Skips a test when the value of an environment variable matches any of the supplied values.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks a test as "Flaky" so that the build will sequester it and ignore failures.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ using System.Linq;
|
|||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
public class FlakyTestDiscoverer : ITraitDiscoverer
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||||
public class FrameworkSkipConditionAttribute : Attribute, ITestCondition
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
internal interface IEnvironmentVariable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
public interface ITestCondition
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System;
|
|||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
/// <summary>
|
||||
/// Skips a test if the OS is the given type (Windows) and the OS version is less than specified.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)]
|
||||
public class OSSkipConditionAttribute : Attribute, ITestCondition
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
[Flags]
|
||||
public enum OperatingSystems
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
[Flags]
|
||||
public enum RuntimeFrameworks
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
// 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.AspNetCore.Testing
|
||||
{
|
||||
/// <summary>
|
||||
/// Skip test if running on CI
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
|
||||
public class SkipOnCIAttribute : Attribute, ITestCondition
|
||||
{
|
||||
public SkipOnCIAttribute(string issueUrl = "")
|
||||
{
|
||||
IssueUrl = issueUrl;
|
||||
}
|
||||
|
||||
public string IssueUrl { get; }
|
||||
|
||||
public bool IsMet
|
||||
{
|
||||
get
|
||||
{
|
||||
return !OnCI();
|
||||
}
|
||||
}
|
||||
|
||||
public string SkipReason
|
||||
{
|
||||
get
|
||||
{
|
||||
return $"This test is skipped on CI";
|
||||
}
|
||||
}
|
||||
|
||||
public static bool OnCI() => OnHelix() || OnAzdo();
|
||||
public static bool OnHelix() => !string.IsNullOrEmpty(GetTargetHelixQueue());
|
||||
public static string GetTargetHelixQueue() => Environment.GetEnvironmentVariable("helix");
|
||||
public static bool OnAzdo() => !string.IsNullOrEmpty(GetIfOnAzdo());
|
||||
public static string GetIfOnAzdo() => Environment.GetEnvironmentVariable("AGENT_OS");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// 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.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
/// <summary>
|
||||
/// Skip test if running on helix (or a particular helix queue).
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
|
||||
public class SkipOnHelixAttribute : Attribute, ITestCondition
|
||||
{
|
||||
public SkipOnHelixAttribute(string issueUrl)
|
||||
{
|
||||
if (string.IsNullOrEmpty(issueUrl))
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
IssueUrl = issueUrl;
|
||||
}
|
||||
|
||||
public string IssueUrl { get; }
|
||||
|
||||
public bool IsMet
|
||||
{
|
||||
get
|
||||
{
|
||||
var skip = OnHelix() && (Queues == null || Queues.ToLowerInvariant().Split(';').Contains(GetTargetHelixQueue().ToLowerInvariant()));
|
||||
return !skip;
|
||||
}
|
||||
}
|
||||
|
||||
// Queues that should be skipped on, i.e. "Windows.10.Amd64.ClientRS4.VS2017.Open;OSX.1012.Amd64.Open"
|
||||
public string Queues { get; set; }
|
||||
|
||||
public string SkipReason
|
||||
{
|
||||
get
|
||||
{
|
||||
return $"This test is skipped on helix";
|
||||
}
|
||||
}
|
||||
|
||||
public static bool OnHelix() => !string.IsNullOrEmpty(GetTargetHelixQueue());
|
||||
|
||||
public static string GetTargetHelixQueue() => Environment.GetEnvironmentVariable("helix");
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ using System;
|
|||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
public class SkippedTestCase : XunitTestCase
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System.Linq;
|
|||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
public static class TestMethodExtensions
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
public static class WindowsVersions
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
public class EnvironmentVariableSkipConditionTest
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
// 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 Xunit;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System;
|
|||
using System.Runtime.InteropServices;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
public class OSSkipConditionAttributeTest
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System;
|
|||
using System.Runtime.InteropServices;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.xunit
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
{
|
||||
public class OSSkipConditionTest
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
// 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 Microsoft.AspNetCore.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing.Tests
|
||||
{
|
||||
public class SkipOnCITests
|
||||
{
|
||||
[ConditionalFact]
|
||||
[SkipOnCI]
|
||||
public void AlwaysSkipOnCI()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HELIX")) || !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AGENT_OS")))
|
||||
{
|
||||
throw new Exception("Flaky!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Testing
|
||||
|
|
|
|||
Loading…
Reference in New Issue