Activities of "RedEnzian"

i ran into a bug inside our application and after multiple debug sessions, me and claude found the problem. We also could reproduce the problem in a blank ABP project created with ABP Studio.

Summary

ContentToolbar and Breadcrumbs subscribe to the scoped PageLayout with anonymous async void handlers and never unsubscribe (neither component implements IDisposable).

On a Blazor Web App using InteractiveAuto, a layout change can reach a component the renderer has already discarded. Its render then resolves child components from a disposed Autofac scope, and because the handler is async void the resulting ObjectDisposedException is rethrown on a thread-pool thread — which terminates the whole host process, not just the circuit.

This is not a failed render. dotnet run exits; in production the container dies and restarts.

Severity

High. Unhandled process termination triggered by ordinary navigation, with no application code at fault and no way for the application to catch it.

Environment

| | | |---|---| | ABP Commercial | 10.6.0 (also reproduced on 10.5.0) | | Volo.Abp.AspNetCore.Components.Server.LeptonXTheme | 5.6.0 (also 5.5.0) | | UI | Blazor Web App, @rendermode="InteractiveAuto", LeptonX side-menu layout | | DI | Autofac (Volo.Abp.Autofac 10.6.0) | | Runtime | .NET SDK 10.0.400, Windows |

Reproduction — 100% reproducible (3/3 attempts)

  1. Create a new ABP solution: Blazor Web App UI, LeptonX theme, EF Core. (I used the stock template unmodified apart from step 2.)
  2. Add the page below to the .Blazor.Client project as Pages/Repro.razor.
  3. Run DbMigrator, start the host, log in as admin.
  4. Navigate to /repro.

The page renders, then the host process exits.

@page "/repro"
@using AbpDefaultSolution.Books
@using AbpDefaultSolution.Permissions
@using Microsoft.AspNetCore.Authorization
@using Volo.Abp.Application.Dtos
@using Volo.Abp.AspNetCore.Components.Web.Theming.Layout
@using Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars
@using Volo.Abp.BlazoriseUI.Components
@inherits AbpDefaultSolutionComponentBase
@inject IBookAppService BookAppService
@inject IAuthorizationService AuthorizationService

<PageHeader Title="Repro" BreadcrumbItems="BreadcrumbItems" Toolbar="Toolbar"></PageHeader>

@code {
    protected List<Volo.Abp.BlazoriseUI.BreadcrumbItem> BreadcrumbItems = [];
    protected PageToolbar Toolbar { get; } = new();

    protected override void OnInitialized()
    {
        BreadcrumbItems.Add(new Volo.Abp.BlazoriseUI.BreadcrumbItem("Repro"));
        Toolbar.AddButton("New", () => Task.CompletedTask, IconName.Add,
            requiredPolicyName: AbpDefaultSolutionPermissions.Books.Create);
    }

    // Several sequential awaits, as ABP Suite generates for a CRUD page.
    protected override async Task OnInitializedAsync()
    {
        await AuthorizationService.IsGrantedAsync(AbpDefaultSolutionPermissions.Books.Default);
        await AuthorizationService.IsGrantedAsync(AbpDefaultSolutionPermissions.Books.Create);
        await AuthorizationService.IsGrantedAsync(AbpDefaultSolutionPermissions.Books.Edit);
        await AuthorizationService.IsGrantedAsync(AbpDefaultSolutionPermissions.Books.Delete);
        await BookAppService.GetListAsync(new PagedAndSortedResultRequestDto());
    }
}

Important: the built-in module pages do not trigger this

I tested the following on the same clean template and all of them survived, including those that render a PageHeader toolbar:

/Identity/Users, /Identity/Roles, /Saas/Tenants, /AuditLogs, /TextTemplates, /LanguageManagement/Languages, /OpenIddict/Applications, /FileManagement

The multi-await OnInitializedAsync in the repro page is what widens the window enough for the InteractiveAuto hand-off to dispose the circuit scope while the theme's handler is still queued. In our production application, every ABP Suite generated CRUD page crashes the host this way.

Actual result

Unhandled exception. System.ObjectDisposedException: Instances cannot be resolved and nested
lifetimes cannot be created from this LifetimeScope as it (or one of its parent scopes) has
already been disposed.
   at Autofac.Core.Lifetime.LifetimeScope.ThrowDisposedException()
   at Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(ResolveRequest& request)
   at Autofac.Extensions.DependencyInjection.AutofacServiceProvider.GetService(Type serviceType)
   at Blazorise.ComponentActivator.CreateInstance(Type componentType)
   at Microsoft.AspNetCore.Components.ComponentFactory.InstantiateComponent(IServiceProvider serviceProvider, Type componentType, IComponentRenderMode callerSpecifiedRenderMode, Nullable`1 parentComponentId)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.InstantiateChildComponentOnFrame(RenderTreeFrame[] frames, Int32 frameIndex, Int32 parentComponentId)
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewComponentFrame(DiffContext& diffContext, Int32 frameIndex)
   ...
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
   at Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()
   at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.&lt;InvokeAsync&gt;g__Execute|8_0(ValueTuple`3 state)
   at Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.Common.ContentToolbar.<obfuscated>(Object s, PropertyChangedEventArgs)
   at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__124_1(Object state)
   at System.Threading.QueueUserWorkItemCallback.Execute()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
   at System.Threading.Thread.StartCallback()

Client side, the corrupted render batch surfaces as:

Error: There was an error applying batch N
SyntaxError: Unexpected number in JSON at position 1 (line 1 column 2)

Expected result

A layout change reaching a disposed component should be a no-op. Under no circumstances should a theme component be able to terminate the host process.

Root cause

Volo.Abp.AspNetCore.Components.Web.LeptonXTheme/Components/ApplicationLayout/Common/ContentToolbar.razor.cs:

protected override Task OnInitializedAsync()
{
    PageLayout.ToolbarItems.CollectionChanged += async (s, e) => await RenderAsync();
    PageLayout.PropertyChanged            += async (s, e) => await InvokeAsync(StateHasChanged);
    return base.OnInitializedAsync();
}

Three problems compound:

  1. The handlers are async void — an exception escapes to the thread pool and kills the process.
  2. They are anonymous, so they could not be unsubscribed even if the component wanted to.
  3. The component has no Dispose, and PageLayout is scoped — so it holds every ContentToolbar instance created in that scope alive, including discarded ones.

Breadcrumbs.razor.cs has the identical defect:

PageLayout.BreadcrumbItems.CollectionChanged += async (s, e) => await InvokeAsync(StateHasChanged);
PageLayout.PropertyChanged                  += async (s, e) => await InvokeAsync(StateHasChanged);

For contrast, SideMenu/Navigation/MainMenuItem.razor.cs does this correctly — named handler, IDisposable, unsubscribes. TopMenu/Navigation/MainMenuItem.razor.cs subscribes in OnParametersSet and never unsubscribes (a leak rather than a crash, since its handler is not async).

Suggested fix

For both ContentToolbar and Breadcrumbs:

  • store the handlers in fields rather than anonymous lambdas;
  • implement IDisposable and unsubscribe there;
  • make the handlers non-async void, so a failed render is observed on a task instead of crashing the host.

Workaround (verified)

Replace both components via DI:

services.Replace(ServiceDescriptor.Transient<ContentToolbar, MySafeContentToolbar>());
services.Replace(ServiceDescriptor.Transient<Breadcrumbs, MySafeBreadcrumbs>());

with subclasses that override OnInitializedAsync without calling base — so the anonymous handlers are never attached — subscribe named handlers instead, and unsubscribe them in Dispose. Skipping base is safe: it only chains to ComponentBase.OnInitializedAsync(), which returns a completed task, and the base OnAfterRenderAsync still performs the initial render.

Implemented as a plain .cs subclass (not a .razor file) so the base component's generated markup is inherited unchanged.

Verified across all affected pages in our application: zero process terminations afterwards.

For a Blazor Web App the replacement must be registered in both the server module and the .Client module, since the layout renders in both.

Showing 1 to 1 of 1 entries
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on October 21, 2026, 11:09
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.