Skip to content

Commit cddbc2e

Browse files
authored
Improve Components error handling (#7165)
* Improve Components error handling * Change event handlers IHandleEvent, IHandleAfterEvent to be async. * Return faulted tasks to Renderer instead of handling exceptions in ComponentBase * Use ILogger in RemoteRenderer, and log to console in WebAssemblyRenderer * Cleaning up touched files Fixes #4964
1 parent 758ba23 commit cddbc2e

27 files changed

+1087
-497
lines changed

src/Components/Blazor/Blazor/src/Hosting/WebAssemblyBlazorApplicationBuilder.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Threading.Tasks;
67
using Microsoft.AspNetCore.Blazor.Rendering;
78
using Microsoft.AspNetCore.Components.Builder;
89

@@ -35,13 +36,13 @@ public void AddComponent(Type componentType, string domElementSelector)
3536
Entries.Add((componentType, domElementSelector));
3637
}
3738

38-
public WebAssemblyRenderer CreateRenderer()
39+
public async Task<WebAssemblyRenderer> CreateRendererAsync()
3940
{
4041
var renderer = new WebAssemblyRenderer(Services);
4142
for (var i = 0; i < Entries.Count; i++)
4243
{
43-
var entry = Entries[i];
44-
renderer.AddComponent(entry.componentType, entry.domElementSelector);
44+
var (componentType, domElementSelector) = Entries[i];
45+
await renderer.AddComponentAsync(componentType, domElementSelector);
4546
}
4647

4748
return renderer;

src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHost.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ public Task StartAsync(CancellationToken cancellationToken = default)
4242
JSRuntime.SetCurrentJSRuntime(_runtime);
4343
SetBrowserHttpMessageHandlerAsDefault();
4444

45+
return StartAsyncAwaited();
46+
}
47+
48+
private async Task StartAsyncAwaited()
49+
{
4550
var scopeFactory = Services.GetRequiredService<IServiceScopeFactory>();
4651
_scope = scopeFactory.CreateScope();
4752

@@ -61,7 +66,7 @@ public Task StartAsync(CancellationToken cancellationToken = default)
6166
var builder = new WebAssemblyBlazorApplicationBuilder(_scope.ServiceProvider);
6267
startup.Configure(builder, _scope.ServiceProvider);
6368

64-
_renderer = builder.CreateRenderer();
69+
_renderer = await builder.CreateRendererAsync();
6570
}
6671
catch
6772
{
@@ -76,9 +81,6 @@ public Task StartAsync(CancellationToken cancellationToken = default)
7681

7782
throw;
7883
}
79-
80-
81-
return Task.CompletedTask;
8284
}
8385

8486
public Task StopAsync(CancellationToken cancellationToken = default)

src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
5+
using System.Threading.Tasks;
46
using Microsoft.AspNetCore.Components;
57
using Microsoft.AspNetCore.Components.Browser;
68
using Microsoft.AspNetCore.Components.Rendering;
79
using Microsoft.JSInterop;
810
using Mono.WebAssembly.Interop;
9-
using System;
10-
using System.Threading;
11-
using System.Threading.Tasks;
1211

1312
namespace Microsoft.AspNetCore.Blazor.Rendering
1413
{
@@ -24,7 +23,7 @@ public class WebAssemblyRenderer : Renderer
2423
/// Constructs an instance of <see cref="WebAssemblyRenderer"/>.
2524
/// </summary>
2625
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> to use when initializing components.</param>
27-
public WebAssemblyRenderer(IServiceProvider serviceProvider): base(serviceProvider)
26+
public WebAssemblyRenderer(IServiceProvider serviceProvider) : base(serviceProvider)
2827
{
2928
// The browser renderer registers and unregisters itself with the static
3029
// registry. This works well with the WebAssembly runtime, and is simple for the
@@ -38,19 +37,26 @@ public WebAssemblyRenderer(IServiceProvider serviceProvider): base(serviceProvid
3837
/// </summary>
3938
/// <typeparam name="TComponent">The type of the component.</typeparam>
4039
/// <param name="domElementSelector">A CSS selector that uniquely identifies a DOM element.</param>
41-
public void AddComponent<TComponent>(string domElementSelector)
42-
where TComponent: IComponent
43-
{
44-
AddComponent(typeof(TComponent), domElementSelector);
45-
}
40+
/// <returns>A <see cref="Task"/> that represents the asynchronous rendering of the added component.</returns>
41+
/// <remarks>
42+
/// Callers of this method may choose to ignore the returned <see cref="Task"/> if they do not
43+
/// want to await the rendering of the added component.
44+
/// </remarks>
45+
public Task AddComponentAsync<TComponent>(string domElementSelector) where TComponent : IComponent
46+
=> AddComponentAsync(typeof(TComponent), domElementSelector);
4647

4748
/// <summary>
4849
/// Associates the <see cref="IComponent"/> with the <see cref="WebAssemblyRenderer"/>,
4950
/// causing it to be displayed in the specified DOM element.
5051
/// </summary>
5152
/// <param name="componentType">The type of the component.</param>
5253
/// <param name="domElementSelector">A CSS selector that uniquely identifies a DOM element.</param>
53-
public void AddComponent(Type componentType, string domElementSelector)
54+
/// <returns>A <see cref="Task"/> that represents the asynchronous rendering of the added component.</returns>
55+
/// <remarks>
56+
/// Callers of this method may choose to ignore the returned <see cref="Task"/> if they do not
57+
/// want to await the rendering of the added component.
58+
/// </remarks>
59+
public Task AddComponentAsync(Type componentType, string domElementSelector)
5460
{
5561
var component = InstantiateComponent(componentType);
5662
var componentId = AssignRootComponentId(component);
@@ -66,7 +72,7 @@ public void AddComponent(Type componentType, string domElementSelector)
6672
domElementSelector,
6773
componentId);
6874

69-
RenderRootComponent(componentId);
75+
return RenderRootComponentAsync(componentId);
7076
}
7177

7278
/// <inheritdoc />
@@ -93,5 +99,22 @@ protected override Task UpdateDisplayAsync(in RenderBatch batch)
9399
throw new NotImplementedException($"{nameof(WebAssemblyRenderer)} is supported only with in-process JS runtimes.");
94100
}
95101
}
102+
103+
/// <inheritdoc />
104+
protected override void HandleException(Exception exception)
105+
{
106+
Console.Error.WriteLine($"Unhandled exception rendering component:");
107+
if (exception is AggregateException aggregateException)
108+
{
109+
foreach (var innerException in aggregateException.Flatten().InnerExceptions)
110+
{
111+
Console.Error.WriteLine(innerException);
112+
}
113+
}
114+
else
115+
{
116+
Console.Error.WriteLine(exception);
117+
}
118+
}
96119
}
97120
}

src/Components/Blazor/Build/test/RazorIntegrationTestBase.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,11 @@ public TestRenderer() : base(new TestServiceProvider(), CreateDefaultDispatcher(
443443
public void AttachComponent(IComponent component)
444444
=> AssignRootComponentId(component);
445445

446+
protected override void HandleException(Exception exception)
447+
{
448+
throw new NotImplementedException();
449+
}
450+
446451
protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
447452
{
448453
LatestBatchReferenceFrames = renderBatch.ReferenceFrames.ToArray();

src/Components/Browser.JS/src/package-lock.json

Lines changed: 5 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Browser/src/RendererRegistryEventDispatcher.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
5+
using System.Threading.Tasks;
46
using Microsoft.AspNetCore.Components.Rendering;
57
using Microsoft.JSInterop;
6-
using System;
78

89
namespace Microsoft.AspNetCore.Components.Browser
910
{
@@ -16,12 +17,13 @@ public static class RendererRegistryEventDispatcher
1617
/// For framework use only.
1718
/// </summary>
1819
[JSInvokable(nameof(DispatchEvent))]
19-
public static void DispatchEvent(
20+
public static Task DispatchEvent(
2021
BrowserEventDescriptor eventDescriptor, string eventArgsJson)
2122
{
2223
var eventArgs = ParseEventArgsJson(eventDescriptor.EventArgsType, eventArgsJson);
2324
var renderer = RendererRegistry.Current.Find(eventDescriptor.BrowserRendererId);
24-
renderer.DispatchEvent(
25+
26+
return renderer.DispatchEventAsync(
2527
eventDescriptor.ComponentId,
2628
eventDescriptor.EventHandlerId,
2729
eventArgs);

src/Components/Components/perf/RenderTreeDiffBuilderBenchmark.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ public FakeRenderer()
9191
{
9292
}
9393

94+
protected override void HandleException(Exception exception)
95+
{
96+
throw new NotImplementedException();
97+
}
98+
9499
protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
95100
=> Task.CompletedTask;
96101
}

0 commit comments

Comments
 (0)