Resolve the JSInterop call if deserialization fails (dotnet/extensions#1802)
* Resolve the JSInterop call if deserialization fails
\n\nCommit migrated from 26cf0c9f4e
This commit is contained in:
parent
2dd8eb61ba
commit
867453a06d
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"solution": {
|
||||||
|
"path": "..\\..\\Extensions.sln",
|
||||||
|
"projects": [
|
||||||
|
"src\\JSInterop\\Microsoft.JSInterop\\src\\Microsoft.JSInterop.csproj",
|
||||||
|
"src\\JSInterop\\Microsoft.JSInterop\\test\\Microsoft.JSInterop.Tests.csproj",
|
||||||
|
"src\\JSInterop\\Mono.WebAssembly.Interop\\src\\Mono.WebAssembly.Interop.csproj",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -37,6 +37,7 @@ namespace Microsoft.JSInterop
|
||||||
public partial class JSException : System.Exception
|
public partial class JSException : System.Exception
|
||||||
{
|
{
|
||||||
public JSException(string message) { }
|
public JSException(string message) { }
|
||||||
|
public JSException(string message, System.Exception innerException) { }
|
||||||
}
|
}
|
||||||
public abstract partial class JSInProcessRuntimeBase : Microsoft.JSInterop.JSRuntimeBase, Microsoft.JSInterop.IJSInProcessRuntime, Microsoft.JSInterop.IJSRuntime
|
public abstract partial class JSInProcessRuntimeBase : Microsoft.JSInterop.JSRuntimeBase, Microsoft.JSInterop.IJSInProcessRuntime, Microsoft.JSInterop.IJSRuntime
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -227,11 +227,10 @@ namespace Microsoft.JSInterop
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// Always dispose the JsonDocument in case of an error.
|
// Always dispose the JsonDocument in case of an error.
|
||||||
jsonDocument.Dispose();
|
jsonDocument?.Dispose();
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool IsIncorrectDotNetObjectRefUse(JsonElement item, Type parameterType)
|
static bool IsIncorrectDotNetObjectRefUse(JsonElement item, Type parameterType)
|
||||||
{
|
{
|
||||||
// Check for incorrect use of DotNetObjectRef<T> at the top level. We know it's
|
// Check for incorrect use of DotNetObjectRef<T> at the top level. We know it's
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,14 @@ namespace Microsoft.JSInterop
|
||||||
public JSException(string message) : base(message)
|
public JSException(string message) : base(message)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs an instance of <see cref="JSException"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The exception message.</param>
|
||||||
|
/// <param name="innerException">The inner exception.</param>
|
||||||
|
public JSException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,10 +94,18 @@ namespace Microsoft.JSInterop
|
||||||
if (succeeded)
|
if (succeeded)
|
||||||
{
|
{
|
||||||
var resultType = TaskGenericsUtil.GetTaskCompletionSourceResultType(tcs);
|
var resultType = TaskGenericsUtil.GetTaskCompletionSourceResultType(tcs);
|
||||||
var result = asyncCallResult != null ?
|
try
|
||||||
JsonSerializer.Parse(asyncCallResult.JsonElement.GetRawText(), resultType, JsonSerializerOptionsProvider.Options) :
|
{
|
||||||
null;
|
var result = asyncCallResult != null ?
|
||||||
TaskGenericsUtil.SetTaskCompletionSourceResult(tcs, result);
|
JsonSerializer.Parse(asyncCallResult.JsonElement.GetRawText(), resultType, JsonSerializerOptionsProvider.Options) :
|
||||||
|
null;
|
||||||
|
TaskGenericsUtil.SetTaskCompletionSourceResult(tcs, result);
|
||||||
|
}
|
||||||
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -363,6 +363,24 @@ namespace Microsoft.JSInterop.Tests
|
||||||
Assert.Contains(nameof(ThrowingClass.AsyncThrowingMethod), exception);
|
Assert.Contains(nameof(ThrowingClass.AsyncThrowingMethod), exception);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public Task BeginInvoke_ThrowsWithInvalidArgsJson_WithCallId() => WithJSRuntime(async jsRuntime =>
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var callId = "123";
|
||||||
|
var resultTask = jsRuntime.NextInvocationTask;
|
||||||
|
DotNetDispatcher.BeginInvoke(callId, thisAssemblyName, "InvocableStaticWithParams", default, "<xml>not json</xml>");
|
||||||
|
|
||||||
|
await resultTask; // This won't throw, it sets properties on the jsRuntime.
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
using var jsonDocument = JsonDocument.Parse(jsRuntime.LastInvocationArgsJson);
|
||||||
|
var result = jsonDocument.RootElement;
|
||||||
|
Assert.Equal(callId, result[0].GetString());
|
||||||
|
Assert.False(result[1].GetBoolean()); // Fails
|
||||||
|
Assert.Contains("JsonReaderException: '<' is an invalid start of a value.", result[2].GetString());
|
||||||
|
});
|
||||||
|
|
||||||
Task WithJSRuntime(Action<TestJSRuntime> testCode)
|
Task WithJSRuntime(Action<TestJSRuntime> testCode)
|
||||||
{
|
{
|
||||||
return WithJSRuntime(jsRuntime =>
|
return WithJSRuntime(jsRuntime =>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.JSInterop.Internal;
|
using Microsoft.JSInterop.Internal;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -86,6 +87,33 @@ namespace Microsoft.JSInterop.Tests
|
||||||
Assert.Equal("This is a test exception", ((JSException)task.Exception.InnerException).Message);
|
Assert.Equal("This is a test exception", ((JSException)task.Exception.InnerException).Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async 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);
|
||||||
|
using var jsonDocument = JsonDocument.Parse("\"Not a string\"");
|
||||||
|
|
||||||
|
// Act/Assert: Task can be failed
|
||||||
|
runtime.OnEndInvoke(
|
||||||
|
runtime.BeginInvokeCalls[1].AsyncHandle,
|
||||||
|
/* succeeded: */ true,
|
||||||
|
new JSAsyncCallResult(jsonDocument, jsonDocument.RootElement));
|
||||||
|
Assert.False(unrelatedTask.IsCompleted);
|
||||||
|
|
||||||
|
var jsException = await Assert.ThrowsAsync<JSException>(() => task);
|
||||||
|
Assert.IsType<JsonException>(jsException.InnerException);
|
||||||
|
|
||||||
|
// Verify we've disposed the JsonDocument.
|
||||||
|
Assert.Throws<ObjectDisposedException>(() => jsonDocument.RootElement.Type);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CannotCompleteSameAsyncCallMoreThanOnce()
|
public void CannotCompleteSameAsyncCallMoreThanOnce()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue