This commit is contained in:
parent
18b81bacce
commit
03357bf92b
|
|
@ -108,20 +108,31 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
{
|
{
|
||||||
await Renderer.InvokeAsync(async () =>
|
await Renderer.InvokeAsync(async () =>
|
||||||
{
|
{
|
||||||
SetCurrentCircuitHost(this);
|
try
|
||||||
|
|
||||||
for (var i = 0; i < Descriptors.Count; i++)
|
|
||||||
{
|
{
|
||||||
var (componentType, domElementSelector) = Descriptors[i];
|
SetCurrentCircuitHost(this);
|
||||||
await Renderer.AddComponentAsync(componentType, domElementSelector);
|
_initialized = true; // We're ready to accept incoming JSInterop calls from here on
|
||||||
|
|
||||||
|
await OnCircuitOpenedAsync(cancellationToken);
|
||||||
|
await OnConnectionUpAsync(cancellationToken);
|
||||||
|
|
||||||
|
// We add the root components *after* the circuit is flagged as open.
|
||||||
|
// That's because AddComponentAsync waits for quiescence, which can take
|
||||||
|
// arbitrarily long. In the meantime we might need to be receiving and
|
||||||
|
// processing incoming JSInterop calls or similar.
|
||||||
|
for (var i = 0; i < Descriptors.Count; i++)
|
||||||
|
{
|
||||||
|
var (componentType, domElementSelector) = Descriptors[i];
|
||||||
|
await Renderer.AddComponentAsync(componentType, domElementSelector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// We have to handle all our own errors here, because the upstream caller
|
||||||
|
// has to fire-and-forget this
|
||||||
|
Renderer_UnhandledException(this, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
await OnCircuitOpenedAsync(cancellationToken);
|
|
||||||
|
|
||||||
await OnConnectionUpAsync(cancellationToken);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
_initialized = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void BeginInvokeDotNetFromJS(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson)
|
public async void BeginInvokeDotNetFromJS(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson)
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Components.Server
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Intended for framework use only. Applications should not call this method directly.
|
/// Intended for framework use only. Applications should not call this method directly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<string> StartCircuit(string uriAbsolute, string baseUriAbsolute)
|
public string StartCircuit(string uriAbsolute, string baseUriAbsolute)
|
||||||
{
|
{
|
||||||
var circuitClient = new CircuitClientProxy(Clients.Caller, Context.ConnectionId);
|
var circuitClient = new CircuitClientProxy(Clients.Caller, Context.ConnectionId);
|
||||||
|
|
||||||
|
|
@ -76,8 +76,11 @@ namespace Microsoft.AspNetCore.Components.Server
|
||||||
|
|
||||||
circuitHost.UnhandledException += CircuitHost_UnhandledException;
|
circuitHost.UnhandledException += CircuitHost_UnhandledException;
|
||||||
|
|
||||||
// If initialization fails, this will throw. The caller will fail if they try to call into any interop API.
|
// Fire-and-forget the initialization process, because we can't block the
|
||||||
await circuitHost.InitializeAsync(Context.ConnectionAborted);
|
// SignalR message loop (we'd get a deadlock if any of the initialization
|
||||||
|
// logic relied on receiving a subsequent message from SignalR), and it will
|
||||||
|
// take care of its own errors anyway.
|
||||||
|
_ = circuitHost.InitializeAsync(Context.ConnectionAborted);
|
||||||
|
|
||||||
_circuitRegistry.Register(circuitHost);
|
_circuitRegistry.Register(circuitHost);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -81,6 +82,46 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
handler2.VerifyAll();
|
handler2.VerifyAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InitializeAsync_ReportsOwnAsyncExceptions()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var handler = new Mock<CircuitHandler>(MockBehavior.Strict);
|
||||||
|
var tcs = new TaskCompletionSource<object>();
|
||||||
|
var reportedErrors = new List<UnhandledExceptionEventArgs>();
|
||||||
|
|
||||||
|
handler
|
||||||
|
.Setup(h => h.OnCircuitOpenedAsync(It.IsAny<Circuit>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(tcs.Task)
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
|
var circuitHost = TestCircuitHost.Create(handlers: new[] { handler.Object });
|
||||||
|
circuitHost.UnhandledException += (sender, errorInfo) =>
|
||||||
|
{
|
||||||
|
Assert.Same(circuitHost, sender);
|
||||||
|
reportedErrors.Add(errorInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var initializeAsyncTask = circuitHost.InitializeAsync(new CancellationToken());
|
||||||
|
|
||||||
|
// Assert: No synchronous exceptions
|
||||||
|
handler.VerifyAll();
|
||||||
|
Assert.Empty(reportedErrors);
|
||||||
|
|
||||||
|
// Act: Trigger async exception
|
||||||
|
var ex = new InvalidTimeZoneException();
|
||||||
|
tcs.SetException(ex);
|
||||||
|
|
||||||
|
// Assert: The top-level task still succeeds, because the intended usage
|
||||||
|
// pattern is fire-and-forget.
|
||||||
|
await initializeAsyncTask;
|
||||||
|
|
||||||
|
// Assert: The async exception was reported via the side-channel
|
||||||
|
Assert.Same(ex, reportedErrors.Single().ExceptionObject);
|
||||||
|
Assert.False(reportedErrors.Single().IsTerminating);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task DisposeAsync_InvokesCircuitHandler()
|
public async Task DisposeAsync_InvokesCircuitHandler()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue