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());
break;
default:
// We are not in rendering the root component.
if (_pendingTasks == null)
{
return;
}
// It's important to evaluate the following even if we're not going to use
// handledErrorTask below, because it has the side-effect of calling HandleException.
var handledErrorTask = GetErrorHandledTask(task);
_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;
}
}

View File

@ -2683,7 +2683,7 @@ namespace Microsoft.AspNetCore.Components.Test
}
[Fact]
public async Task ExceptionsThrownAsynchronouslyCanBeHandled()
public async Task ExceptionsThrownAsynchronouslyDuringFirstRenderCanBeHandled()
{
// Arrange
var renderer = new TestRenderer { ShouldHandleExceptions = true };
@ -2722,6 +2722,36 @@ namespace Microsoft.AspNetCore.Components.Test
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]
public async Task ExceptionsThrownAsynchronouslyFromMultipleComponentsCanBeHandled()
{
@ -3696,5 +3726,15 @@ namespace Microsoft.AspNetCore.Components.Test
OnAfterRenderAsync,
}
}
private class ComponentThatAwaitsTask : ComponentBase
{
[Parameter] public Task TaskToAwait { get; set; }
protected override async Task OnParametersSetAsync()
{
await TaskToAwait;
}
}
}
}