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 JSException(string message) { }
|
||||
public JSException(string message, System.Exception innerException) { }
|
||||
}
|
||||
public abstract partial class JSInProcessRuntimeBase : Microsoft.JSInterop.JSRuntimeBase, Microsoft.JSInterop.IJSInProcessRuntime, Microsoft.JSInterop.IJSRuntime
|
||||
{
|
||||
|
|
|
|||
|
|
@ -227,11 +227,10 @@ namespace Microsoft.JSInterop
|
|||
catch
|
||||
{
|
||||
// Always dispose the JsonDocument in case of an error.
|
||||
jsonDocument.Dispose();
|
||||
jsonDocument?.Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
static bool IsIncorrectDotNetObjectRefUse(JsonElement item, Type parameterType)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
var resultType = TaskGenericsUtil.GetTaskCompletionSourceResultType(tcs);
|
||||
var result = asyncCallResult != null ?
|
||||
JsonSerializer.Parse(asyncCallResult.JsonElement.GetRawText(), resultType, JsonSerializerOptionsProvider.Options) :
|
||||
null;
|
||||
TaskGenericsUtil.SetTaskCompletionSourceResult(tcs, result);
|
||||
try
|
||||
{
|
||||
var result = asyncCallResult != null ?
|
||||
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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -363,6 +363,24 @@ namespace Microsoft.JSInterop.Tests
|
|||
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)
|
||||
{
|
||||
return WithJSRuntime(jsRuntime =>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.JSInterop.Internal;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -86,6 +87,33 @@ namespace Microsoft.JSInterop.Tests
|
|||
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]
|
||||
public void CannotCompleteSameAsyncCallMoreThanOnce()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue