Skip to content

Commit 11fd744

Browse files
Zacharias Neubauerzachneu
authored andcommitted
[blazor] Add parameter to ErrorBoundary to let it render ChildContent
1 parent df9f19a commit 11fd744

14 files changed

Lines changed: 148 additions & 9 deletions

File tree

src/Components/Components/src/ComponentBase.cs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ public ComponentBase()
4242
};
4343
}
4444

45+
/// <summary>
46+
/// Nearest ErrorBoundaryBase component
47+
/// </summary>
48+
[CascadingParameter] internal IErrorBoundary? ErrorBoundary { get; set; }
49+
4550
/// <summary>
4651
/// Gets the <see cref="Components.RendererInfo"/> the component is running on.
4752
/// </summary>
@@ -273,7 +278,23 @@ public virtual Task SetParametersAsync(ParameterView parameters)
273278

274279
private async Task RunInitAndSetParametersAsync()
275280
{
276-
OnInitialized();
281+
try
282+
{
283+
OnInitialized();
284+
}
285+
catch(Exception err)
286+
{
287+
// If we are guarded by ErrorBoundary that should render ChildContent,
288+
// even there was thrown one or more exceptions
289+
if (ErrorBoundary?.RenderOnException == true)
290+
{
291+
_ = _renderHandle.DispatchExceptionAsync(err);
292+
}
293+
else
294+
{
295+
throw;
296+
}
297+
}
277298
var task = OnInitializedAsync();
278299

279300
if (task.Status != TaskStatus.RanToCompletion && task.Status != TaskStatus.Canceled)
@@ -283,7 +304,7 @@ private async Task RunInitAndSetParametersAsync()
283304
// to defer calling StateHasChanged up until the first bit of async code happens or until
284305
// the end. Additionally, we want to avoid calling StateHasChanged if no
285306
// async work is to be performed.
286-
if (task.Status != TaskStatus.Faulted)
307+
if (task.Status != TaskStatus.Faulted || ErrorBoundary?.RenderOnException == true)
287308
{
288309
StateHasChanged();
289310
}
@@ -312,7 +333,24 @@ private async Task RunInitAndSetParametersAsync()
312333

313334
private Task CallOnParametersSetAsync()
314335
{
315-
OnParametersSet();
336+
try
337+
{
338+
OnParametersSet();
339+
}
340+
catch (Exception err)
341+
{
342+
// If we are guarded by ErrorBoundary that should render ChildContent,
343+
// even there was thrown one or more exceptions
344+
if (ErrorBoundary?.RenderOnException == true)
345+
{
346+
_ = _renderHandle.DispatchExceptionAsync(err);
347+
}
348+
else
349+
{
350+
throw;
351+
}
352+
}
353+
316354
var task = OnParametersSetAsync();
317355
// If no async work is to be performed, i.e. the task has already ran to completion
318356
// or was canceled by the time we got to inspect it, avoid going async and re-invoking
@@ -322,7 +360,7 @@ private Task CallOnParametersSetAsync()
322360

323361
// We always call StateHasChanged here as we want to trigger a rerender after OnParametersSet and
324362
// the synchronous part of OnParametersSetAsync has run.
325-
if (task.Status != TaskStatus.Faulted)
363+
if (task.Status != TaskStatus.Faulted || ErrorBoundary?.RenderOnException == true)
326364
{
327365
StateHasChanged();
328366
}

src/Components/Components/src/ErrorBoundaryBase.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Runtime.ExceptionServices;
5+
using Microsoft.AspNetCore.Components.Rendering;
56

67
namespace Microsoft.AspNetCore.Components;
78

@@ -28,6 +29,9 @@ public abstract class ErrorBoundaryBase : ComponentBase, IErrorBoundary
2829
/// </summary>
2930
[Parameter] public int MaximumErrorCount { get; set; } = 100;
3031

32+
/// <inheritdoc/>
33+
[Parameter] public virtual bool RenderOnException { get; set; }
34+
3135
/// <summary>
3236
/// Gets the current exception, or null if there is no exception.
3337
/// </summary>
@@ -47,6 +51,21 @@ public void Recover()
4751
}
4852
}
4953

54+
/// <inheritdoc/>
55+
protected override void BuildRenderTree(RenderTreeBuilder builder)
56+
{
57+
// Making this ErrorBoundary available as a CascadingParameter to ComponentBase
58+
builder.OpenComponent<CascadingValue<IErrorBoundary>>(0);
59+
builder.AddAttribute(1, nameof(CascadingValue<>.Value), this);
60+
61+
builder.AddAttribute(2, nameof(CascadingValue<>.ChildContent), (RenderFragment)((builder2) =>
62+
{
63+
builder2.AddContent(3, ChildContent);
64+
}));
65+
66+
builder.CloseComponent();
67+
}
68+
5069
/// <summary>
5170
/// Invoked by the base class when an error is being handled. Typically, derived classes
5271
/// should log the exception from this method.

src/Components/Components/src/IErrorBoundary.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,8 @@ namespace Microsoft.AspNetCore.Components;
1717
internal interface IErrorBoundary
1818
{
1919
void HandleException(Exception error);
20+
/// <summary>
21+
/// Setting this to true will stop Renderer requeue rendering of the candidate component
22+
/// </summary>
23+
bool RenderOnException { get; }
2024
}

src/Components/Components/src/PublicAPI.Unshipped.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
#nullable enable
2+
Microsoft.AspNetCore.Components.ComponentBase.ErrorBoundary.get -> Microsoft.AspNetCore.Components.ErrorBoundaryBase?
3+
Microsoft.AspNetCore.Components.ComponentBase.ErrorBoundary.set -> void
4+
override Microsoft.AspNetCore.Components.ErrorBoundaryBase.BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder! builder) -> void
25
static Microsoft.AspNetCore.Components.NavigationManagerExtensions.GetUriWithHash(this Microsoft.AspNetCore.Components.NavigationManager! navigationManager, string! hash) -> string!
36
Microsoft.AspNetCore.Components.NavigationOptions.RelativeToCurrentUri.get -> bool
47
Microsoft.AspNetCore.Components.NavigationOptions.RelativeToCurrentUri.init -> void
@@ -7,3 +10,5 @@ Microsoft.AspNetCore.Components.IComponentPropertyActivator.GetActivator(System.
710
*REMOVED*Microsoft.AspNetCore.Components.ResourceAsset.ResourceAsset(string! url, System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Components.ResourceAssetProperty!>? properties) -> void
811
*REMOVED*Microsoft.AspNetCore.Components.Routing.Router.PreferExactMatches.get -> bool
912
*REMOVED*Microsoft.AspNetCore.Components.Routing.Router.PreferExactMatches.set -> void
13+
virtual Microsoft.AspNetCore.Components.ErrorBoundaryBase.RenderOnException.get -> bool
14+
virtual Microsoft.AspNetCore.Components.ErrorBoundaryBase.RenderOnException.set -> void

src/Components/Components/src/RenderTree/Renderer.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,17 +1185,27 @@ private void HandleExceptionViaErrorBoundary(Exception error, ComponentState? er
11851185
{
11861186
if (candidate.Component is IErrorBoundary errorBoundary)
11871187
{
1188-
// Don't just trust the error boundary to dispose its subtree - force it to do so by
1189-
// making it render an empty fragment. Ensures that failed components don't continue to
1190-
// operate, which would be a whole new kind of edge case to support forever.
1191-
AddToRenderQueue(candidate.ComponentId, builder => { });
1188+
// Check if the error boundery do not allow to continue to render the component
1189+
// when one or more exceptions have been thrown
1190+
if (!errorBoundary.RenderOnException)
1191+
{
1192+
// Don't just trust the error boundary to dispose its subtree - force it to do so by
1193+
// making it render an empty fragment. Ensures that failed components don't continue to
1194+
// operate, which would be a whole new kind of edge case to support forever.
1195+
AddToRenderQueue(candidate.ComponentId, builder => { });
1196+
}
11921197

11931198
try
11941199
{
11951200
errorBoundary.HandleException(error);
11961201
}
11971202
catch (Exception errorBoundaryException)
11981203
{
1204+
if (errorBoundary.RenderOnException)
1205+
{
1206+
AddToRenderQueue(candidate.ComponentId, builder => { });
1207+
}
1208+
11991209
// If *notifying* about an exception fails, it's OK for that to be fatal
12001210
HandleException(errorBoundaryException);
12011211
}

src/Components/Components/test/RendererTest.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6168,6 +6168,7 @@ private class TestErrorBoundary : AutoRenderComponent, IErrorBoundary
61686168
public Task ReceivedErrorTask => receivedErrorTaskCompletionSource.Task;
61696169

61706170
[Parameter] public RenderFragment ChildContent { get; set; }
6171+
public bool RenderOnException { get; }
61716172

61726173
protected override void BuildRenderTree(RenderTreeBuilder builder)
61736174
=> ChildContent(builder);

src/Components/Samples/BlazorUnitedApp.Client/BlazorUnitedApp.Client.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,8 @@
1515
<Folder Include="wwwroot\" />
1616
</ItemGroup>
1717

18+
<ItemGroup>
19+
<None Include="ThrowExceptionsChild.razor.cs" />
20+
</ItemGroup>
21+
1822
</Project>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Components;
5+
using Microsoft.JSInterop;
6+
7+
namespace BlazorUnitedApp.Client;
8+
9+
public class ErrorBoundaryTest(IJSRuntime jSRuntime) : ErrorBoundaryBase
10+
{
11+
protected override async Task OnErrorAsync(Exception exception)
12+
{
13+
await jSRuntime.InvokeVoidAsync("alert", $"An unknown error occurred: {exception.Message}");
14+
}
15+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<ErrorBoundaryTest RenderOnException="true">
2+
<ThrowExceptionsChild />
3+
</ErrorBoundaryTest>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<h3>ThrowExceptionsChild</h3>
2+
3+
<button @onclick="Save">
4+
Test saving with exception
5+
</button>

0 commit comments

Comments
 (0)