[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:
parent
d846cb4b97
commit
c918d72f36
|
|
@ -235,6 +235,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
catch (Exception e)
|
||||
{
|
||||
HandleException(e);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue