Handle async exceptions when not rendering root component. Fixes #8151

This commit is contained in:
Steve Sanderson 2019-03-04 10:05:39 +00:00
parent 7b24304739
commit 7111c8ffd7
2 changed files with 48 additions and 7 deletions

View File

@ -328,13 +328,14 @@ namespace Microsoft.AspNetCore.Components.Rendering
HandleException(task.Exception.GetBaseException()); HandleException(task.Exception.GetBaseException());
break; break;
default: default:
// We are not in rendering the root component. // It's important to evaluate the following even if we're not going to use
if (_pendingTasks == null) // handledErrorTask below, because it has the side-effect of calling HandleException.
{ var handledErrorTask = GetErrorHandledTask(task);
return;
}
_pendingTasks.Add(GetErrorHandledTask(task)); // The pendingTasks collection is only used during prerendering to track quiescence,
// so will be null at other times.
_pendingTasks?.Add(handledErrorTask);
break; break;
} }
} }

View File

@ -2683,7 +2683,7 @@ namespace Microsoft.AspNetCore.Components.Test
} }
[Fact] [Fact]
public async Task ExceptionsThrownAsynchronouslyCanBeHandled() public async Task ExceptionsThrownAsynchronouslyDuringFirstRenderCanBeHandled()
{ {
// Arrange // Arrange
var renderer = new TestRenderer { ShouldHandleExceptions = true }; var renderer = new TestRenderer { ShouldHandleExceptions = true };
@ -2722,6 +2722,36 @@ namespace Microsoft.AspNetCore.Components.Test
Assert.Same(exception, Assert.Single(renderer.HandledExceptions).GetBaseException()); Assert.Same(exception, Assert.Single(renderer.HandledExceptions).GetBaseException());
} }
[Fact]
public async Task ExceptionsThrownAsynchronouslyAfterFirstRenderCanBeHandled()
{
// This differs from the "during first render" case, because some aspects of the rendering
// code paths are special cased for the first render because of prerendering.
// Arrange
var renderer = new TestRenderer { ShouldHandleExceptions = true };
var taskToAwait = Task.CompletedTask;
var component = new TestComponent(builder =>
{
builder.OpenComponent<ComponentThatAwaitsTask>(0);
builder.AddAttribute(1, nameof(ComponentThatAwaitsTask.TaskToAwait), taskToAwait);
builder.CloseComponent();
});
var componentId = renderer.AssignRootComponentId(component);
await renderer.RenderRootComponentAsync(componentId); // Not throwing on first render
var asyncExceptionTcs = new TaskCompletionSource<object>();
taskToAwait = asyncExceptionTcs.Task;
await renderer.Invoke(component.TriggerRender);
// Act
var exception = new InvalidOperationException();
asyncExceptionTcs.SetException(exception);
// Assert
Assert.Same(exception, Assert.Single(renderer.HandledExceptions).GetBaseException());
}
[Fact] [Fact]
public async Task ExceptionsThrownAsynchronouslyFromMultipleComponentsCanBeHandled() public async Task ExceptionsThrownAsynchronouslyFromMultipleComponentsCanBeHandled()
{ {
@ -3696,5 +3726,15 @@ namespace Microsoft.AspNetCore.Components.Test
OnAfterRenderAsync, OnAfterRenderAsync,
} }
} }
private class ComponentThatAwaitsTask : ComponentBase
{
[Parameter] public Task TaskToAwait { get; set; }
protected override async Task OnParametersSetAsync()
{
await TaskToAwait;
}
}
} }
} }