Skip to content

Blazor doesn't DisposeAsync DI scope #12918

Closed
@analogrelay

Description

@analogrelay

Blazor doesn't call DisposeAsync on the DI scope, which leads to the following exception being logged during Circuit disposal:

fail: Microsoft.AspNetCore.Components.Server.Circuits.CircuitRegistry[100]
      Unhandled exception disposing circuit host: 'blazor_async_dispose.MyScopedDisposableService' type only implements IAsyncDisposable. Use DisposeAsync to dispose the container.
System.InvalidOperationException: 'blazor_async_dispose.MyScopedDisposableService' type only implements IAsyncDisposable. Use DisposeAsync to dispose the container.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.Dispose()
   at Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost.<DisposeAsync>b__51_0()
   at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.<>c.<<InvokeAsync>b__9_0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost.DisposeAsync()
   at Microsoft.AspNetCore.Components.Server.Circuits.CircuitRegistry.DisposeCircuitEntry(DisconnectedCircuitEntry entry)

The DI scope/container is rigid about which kind of disposal you use depending on the services you have. This matrix lays out what happens. Across the top are the kinds of services you have, down the left is how you dispose the scope:

How you dispose the scope Implement IDisposable only Implement both IAsyncDisposable and IDisposable Implement IAsyncDisposable only
DisposeAsync OK (calls Dispose) OK (calls DisposeAsync) OK (calls DisposeAsync)
Dispose OK (calls Dispose) OK (calls Dispose) ❌Throws❌

It's definitely arguable that throwing this exception in DI isn't appropriate. I just want to open the discussion somewhere.

Repro Steps

  1. dotnet new blazorserver
  2. Add file MyScopedDisposableService.cs:
public class MyScopedDisposableService : IAsyncDisposable
{
    public MyScopedDisposableService()
    {
        Console.WriteLine("Scoped Service Constructed");
    }

    public ValueTask DisposeAsync()
    {
        Console.WriteLine("Scoped Service Disposed async!");
        return default;
    }
}
  1. Register in ConfigureServices:
services.AddScoped<MyScopedDisposableService>();
  1. Inject into Pages/Index.razor:
@inject MyScopedDisposableService Service
  1. Launch the app.
  2. Navigate to the index page.
  3. Close the tab.
  4. Wait about ~5 minutes for the circuit to get disposed.

Expected Behavior

No error is logged

Actual Behavior

The following error is logged:

fail: Microsoft.AspNetCore.Components.Server.Circuits.CircuitRegistry[100]
      Unhandled exception disposing circuit host: 'blazor_async_dispose.MyScopedDisposableService' type only implements IAsyncDisposable. Use DisposeAsync to dispose the container.
System.InvalidOperationException: 'blazor_async_dispose.MyScopedDisposableService' type only implements IAsyncDisposable. Use DisposeAsync to dispose the container.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.Dispose()
   at Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost.<DisposeAsync>b__51_0()
   at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.<>c.<<InvokeAsync>b__9_0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost.DisposeAsync()
   at Microsoft.AspNetCore.Components.Server.Circuits.CircuitRegistry.DisposeCircuitEntry(DisconnectedCircuitEntry entry)

Metadata

Metadata

Assignees

Labels

DoneThis issue has been fixedarea-blazorIncludes: Blazor, Razor ComponentsbugThis issue describes a behavior which is not expected - a bug.

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions