[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)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
HandleException(e);
|
HandleException(e);
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -456,7 +456,7 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CanDispatchEventsToTopLevelComponents()
|
public void CanDispatchEventsToTopLevelComponents()
|
||||||
{
|
{
|
||||||
// Arrange: Render a component with an event handler
|
// Arrange: Render a component with an event handler
|
||||||
var renderer = new TestRenderer();
|
var renderer = new TestRenderer();
|
||||||
|
|
@ -482,12 +482,42 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
var renderTask = renderer.DispatchEventAsync(eventHandlerId, eventArgs);
|
var renderTask = renderer.DispatchEventAsync(eventHandlerId, eventArgs);
|
||||||
Assert.True(renderTask.IsCompletedSuccessfully);
|
Assert.True(renderTask.IsCompletedSuccessfully);
|
||||||
Assert.Same(eventArgs, receivedArgs);
|
Assert.Same(eventArgs, receivedArgs);
|
||||||
|
|
||||||
await renderTask; // Does not throw
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[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
|
// Arrange: Render a component with an event handler
|
||||||
var renderer = new TestRenderer();
|
var renderer = new TestRenderer();
|
||||||
|
|
@ -513,12 +543,10 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
var renderTask = renderer.DispatchEventAsync(eventHandlerId, eventArgs);
|
var renderTask = renderer.DispatchEventAsync(eventHandlerId, eventArgs);
|
||||||
Assert.True(renderTask.IsCompletedSuccessfully);
|
Assert.True(renderTask.IsCompletedSuccessfully);
|
||||||
Assert.Same(eventArgs, receivedArgs);
|
Assert.Same(eventArgs, receivedArgs);
|
||||||
|
|
||||||
await renderTask; // does not throw
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CanDispatchActionEventsToTopLevelComponents()
|
public void CanDispatchActionEventsToTopLevelComponents()
|
||||||
{
|
{
|
||||||
// Arrange: Render a component with an event handler
|
// Arrange: Render a component with an event handler
|
||||||
var renderer = new TestRenderer();
|
var renderer = new TestRenderer();
|
||||||
|
|
@ -544,12 +572,10 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
var renderTask = renderer.DispatchEventAsync(eventHandlerId, eventArgs);
|
var renderTask = renderer.DispatchEventAsync(eventHandlerId, eventArgs);
|
||||||
Assert.True(renderTask.IsCompletedSuccessfully);
|
Assert.True(renderTask.IsCompletedSuccessfully);
|
||||||
Assert.NotNull(receivedArgs);
|
Assert.NotNull(receivedArgs);
|
||||||
|
|
||||||
await renderTask; // does not throw
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CanDispatchEventsToNestedComponents()
|
public void CanDispatchEventsToNestedComponents()
|
||||||
{
|
{
|
||||||
UIEventArgs receivedArgs = null;
|
UIEventArgs receivedArgs = null;
|
||||||
|
|
||||||
|
|
@ -586,8 +612,6 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
var renderTask = renderer.DispatchEventAsync(eventHandlerId, eventArgs);
|
var renderTask = renderer.DispatchEventAsync(eventHandlerId, eventArgs);
|
||||||
Assert.True(renderTask.IsCompletedSuccessfully);
|
Assert.True(renderTask.IsCompletedSuccessfully);
|
||||||
Assert.Same(eventArgs, receivedArgs);
|
Assert.Same(eventArgs, receivedArgs);
|
||||||
|
|
||||||
await renderTask; // does not throw
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -490,6 +490,29 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
await ValidateClientKeepsWorking(Client, batches);
|
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) =>
|
private Task ValidateClientKeepsWorking(BlazorClient Client, List<(int, int, byte[])> batches) =>
|
||||||
ValidateClientKeepsWorking(Client, () => batches.Count);
|
ValidateClientKeepsWorking(Client, () => batches.Count);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@
|
||||||
<button id="triggerjsinterop-success" @onclick="@TriggerJSInteropSuccess">Trigger successfull JS interop callback</button>
|
<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="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>
|
<button id="thecounter" @onclick="@IncrementCount">Click me</button>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
|
|
@ -33,6 +35,8 @@
|
||||||
currentCount++;
|
currentCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TriggerSyncException() => throw new Exception("Handler threw an exception");
|
||||||
|
|
||||||
async Task TriggerJSInterop()
|
async Task TriggerJSInterop()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue