[Blazor] [Fixes #11847] Renderer.DispatchEventAsync throws null reference exception if event handler throws synchronously (#12393)

[Blazor] [Fixes #11847] Renderer.DispatchEventAsync throws null
reference exception if event handler throws synchronously
* Returns after handling the exception.
* Adds a unit test and an E2E test to validate expected behavior.
This commit is contained in:
Javier Calvarro Nelson 2019-07-22 19:14:30 +02:00 committed by GitHub
parent d846cb4b97
commit c918d72f36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 64 additions and 12 deletions

View File

@ -235,6 +235,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
catch (Exception e)
{
HandleException(e);
return Task.CompletedTask;
}
finally
{

View File

@ -456,7 +456,7 @@ namespace Microsoft.AspNetCore.Components.Test
}
[Fact]
public async Task CanDispatchEventsToTopLevelComponents()
public void CanDispatchEventsToTopLevelComponents()
{
// Arrange: Render a component with an event handler
var renderer = new TestRenderer();
@ -482,12 +482,42 @@ namespace Microsoft.AspNetCore.Components.Test
var renderTask = renderer.DispatchEventAsync(eventHandlerId, eventArgs);
Assert.True(renderTask.IsCompletedSuccessfully);
Assert.Same(eventArgs, receivedArgs);
await renderTask; // Does not throw
}
[Fact]
public async Task CanDispatchTypedEventsToTopLevelComponents()
public void DispatchEventHandlesSynchronousExceptionsFromEventHandlers()
{
// Arrange: Render a component with an event handler
var renderer = new TestRenderer {
ShouldHandleExceptions = true
};
var component = new EventComponent
{
OnTest = args => throw new Exception("Error")
};
var componentId = renderer.AssignRootComponentId(component);
component.TriggerRender();
var eventHandlerId = renderer.Batches.Single()
.ReferenceFrames
.First(frame => frame.AttributeValue != null)
.AttributeEventHandlerId;
// Assert: Event not yet fired
Assert.Empty(renderer.HandledExceptions);
// Act/Assert: Event can be fired
var eventArgs = new UIEventArgs();
var renderTask = renderer.DispatchEventAsync(eventHandlerId, eventArgs);
Assert.True(renderTask.IsCompletedSuccessfully);
var exception = Assert.Single(renderer.HandledExceptions);
Assert.Equal("Error", exception.Message);
}
[Fact]
public void CanDispatchTypedEventsToTopLevelComponents()
{
// Arrange: Render a component with an event handler
var renderer = new TestRenderer();
@ -513,12 +543,10 @@ namespace Microsoft.AspNetCore.Components.Test
var renderTask = renderer.DispatchEventAsync(eventHandlerId, eventArgs);
Assert.True(renderTask.IsCompletedSuccessfully);
Assert.Same(eventArgs, receivedArgs);
await renderTask; // does not throw
}
[Fact]
public async Task CanDispatchActionEventsToTopLevelComponents()
public void CanDispatchActionEventsToTopLevelComponents()
{
// Arrange: Render a component with an event handler
var renderer = new TestRenderer();
@ -544,12 +572,10 @@ namespace Microsoft.AspNetCore.Components.Test
var renderTask = renderer.DispatchEventAsync(eventHandlerId, eventArgs);
Assert.True(renderTask.IsCompletedSuccessfully);
Assert.NotNull(receivedArgs);
await renderTask; // does not throw
}
[Fact]
public async Task CanDispatchEventsToNestedComponents()
public void CanDispatchEventsToNestedComponents()
{
UIEventArgs receivedArgs = null;
@ -586,8 +612,6 @@ namespace Microsoft.AspNetCore.Components.Test
var renderTask = renderer.DispatchEventAsync(eventHandlerId, eventArgs);
Assert.True(renderTask.IsCompletedSuccessfully);
Assert.Same(eventArgs, receivedArgs);
await renderTask; // does not throw
}
[Fact]

View File

@ -490,6 +490,29 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
await ValidateClientKeepsWorking(Client, batches);
}
[Fact]
public async Task EventHandlerThrowsSyncExceptionTerminatesTheCircuit()
{
// Arrange
var (interopCalls, dotNetCompletions, batches) = ConfigureClient();
await GoToTestComponent(batches);
var sink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
var logEvents = new List<(LogLevel logLevel, string eventIdName, Exception exception)>();
sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name, wc.Exception));
// Act
await Client.ClickAsync("event-handler-throw-sync");
Assert.Contains(
logEvents,
e => LogLevel.Warning == e.logLevel &&
"UnhandledExceptionInCircuit" == e.eventIdName &&
"Handler threw an exception" == e.exception.Message);
await ValidateClientKeepsWorking(Client, batches);
}
private Task ValidateClientKeepsWorking(BlazorClient Client, List<(int, int, byte[])> batches) =>
ValidateClientKeepsWorking(Client, () => batches.Count);

View File

@ -19,6 +19,8 @@
<button id="triggerjsinterop-success" @onclick="@TriggerJSInteropSuccess">Trigger successfull JS interop callback</button>
<button id="triggerjsinterop-failure" @onclick="@TriggerJSInteropFailure">Trigger error JS interop callback</button>
<button id="event-handler-throw-sync" @onclick="@TriggerSyncException">Trigger sync exception</button>
<button id="thecounter" @onclick="@IncrementCount">Click me</button>
@code
@ -33,6 +35,8 @@
currentCount++;
}
void TriggerSyncException() => throw new Exception("Handler threw an exception");
async Task TriggerJSInterop()
{
try