-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Description
Background and Motivation
IAsyncDisposable support in DI was added a while ago (see dotnet/extensions#426), but then it was agreed to only add interface implementation to ServiceProvider. I didn't find any tracking issue on implementing it also on scopes.
Currently, the following code will throw exception:
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
await using var provider = new ServiceCollection()
.AddScoped<Foo>()
.BuildServiceProvider();
using (var scope = provider.CreateScope())
{
var foo = scope.ServiceProvider.GetRequiredService<Foo>();
}
class Foo : IAsyncDisposable
{
public ValueTask DisposeAsync() => default;
}Unhandled exception. System.InvalidOperationException: 'Foo' type only implements IAsyncDisposable. Use DisposeAsync to dispose the container.
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.Dispose()
at <Program>$.<<Main>$>d__0.MoveNext() in C:\src\tmp\asyncdisposablescope\Program.cs:line 12
--- End of stack trace from previous location ---
at <Program>$.<<Main>$>d__0.MoveNext() in C:\src\tmp\asyncdisposablescope\Program.cs:line 12
--- End of stack trace from previous location ---
at <Program>$.<Main>(String[] args)
Workaround today is to cast the returned scope to IAsyncDisposable, because the concrete implementation in DI implements that.
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
await using var provider = new ServiceCollection()
.AddScoped<Foo>()
.BuildServiceProvider();
{
var scope = provider.CreateScope();
var foo = scope.ServiceProvider.GetRequiredService<Foo>();
await ((IAsyncDisposable)scope).DisposeAsync();
}
class Foo : IAsyncDisposable
{
public ValueTask DisposeAsync() => default;
}Proposed API
IServiceScope implements IAsyncDisposable
namespace Microsoft.Extensions.DependencyInjection
{
- public interface IServiceScope : IDisposable
+ public interface IServiceScope : IDisposable, IAsyncDisposable
}Usage Examples
Changing this would allow us to use DisposeAsync on the returned scope.
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
await using var provider = new ServiceCollection()
.AddScoped<Foo>()
.BuildServiceProvider();
await using (var scope = provider.CreateScope())
{
var foo = scope.ServiceProvider.GetRequiredService<Foo>();
}
class Foo : IAsyncDisposable
{
public ValueTask DisposeAsync() => default;
}Alternative Designs
Create a new interface IAsyncDisposableServiceScope (needs better naming), which implements IServiceScope and IAsyncDisposable and let IServiceScopeFactory.CreateScope() return that.
namespace Microsoft.Extensions.DependencyInjection
{
+ public interface IAsyncDisposableServiceScope : IServiceScope, IAsyncDisposable { }
public interface IServiceScopeFactory
{
- IServiceScope CreateScope();
+ IAsyncDisposableServiceScope CreateScope();
}
public static class ServiceProviderServiceExtensions
{
- public static IServiceScope CreateScope(this IServiceProvider provider)
+ public static IAsyncDisposableServiceScope CreateScope(this IServiceProvider provider)
}
}Risks
Will probably break some 3rd party implementations of Microsoft.Extensions.DependencyInjection.Abstractions and consumers.