Replace service registration in ASP.NET Core built-in DI container?
Solution 1:
This is simple using the Replace(IServiceCollection, ServiceDescriptor)
method from the ServiceCollectionDescriptorExtensions
class.
// IFoo -> FooA
services.AddTransient<IFoo, FooA>();
// Replace
// IFoo -> FooB
var descriptor =
new ServiceDescriptor(
typeof(IFoo),
typeof(FooB),
ServiceLifetime.Transient);
services.Replace(descriptor);
See also:
- ServiceDescriptor constructors
Solution 2:
It is easy to override ASP.NET Core DI functionality if you know two simple things:
1. ServiceCollection
is just a wrapper on top of List<ServiceDescriptor>
:
public class ServiceCollection : IServiceCollection
{
private List<ServiceDescriptor> _descriptors = new List<ServiceDescriptor>();
}
2. When a service is registered, a new descriptor is added to list:
private static IServiceCollection Add(
IServiceCollection collection,
Type serviceType,
Type implementationType,
ServiceLifetime lifetime)
{
var descriptor = new ServiceDescriptor(serviceType, implementationType, lifetime);
collection.Add(descriptor);
return collection;
}
Therefore, it is possible to add/remove descriptors to/from this list to replace the registration:
IFoo service = services.BuildServiceProvider().GetService<IFoo>();
Assert.True(service is FooA);
var descriptor = services.FirstOrDefault(d => d.ServiceType == typeof(IFoo));
Assert.NotNull(descriptor);
services.Remove(descriptor);
service = services.BuildServiceProvider().GetService<IFoo>();
Assert.Null(service);
We finish with Replace<TService, TImplementation>
extention method:
services.Replace<IFoo, FooB>(ServiceLifetime.Transient);
Its implementation:
public static IServiceCollection Replace<TService, TImplementation>(
this IServiceCollection services,
ServiceLifetime lifetime)
where TService : class
where TImplementation : class, TService
{
var descriptorToRemove = services.FirstOrDefault(d => d.ServiceType == typeof(TService));
services.Remove(descriptorToRemove);
var descriptorToAdd = new ServiceDescriptor(typeof(TService), typeof(TImplementation), lifetime);
services.Add(descriptorToAdd);
return services;
}
Solution 3:
Just to add on @ilya-chumakov 's great answer, here is the same method but with support for implementation factories
public static IServiceCollection Replace<TService>(
this IServiceCollection services,
Func<IServiceProvider, TService> implementationFactory,
ServiceLifetime lifetime)
where TService : class
{
var descriptorToRemove = services.FirstOrDefault(d => d.ServiceType == typeof(TService));
services.Remove(descriptorToRemove);
var descriptorToAdd = new ServiceDescriptor(typeof(TService), implementationFactory, lifetime);
services.Add(descriptorToAdd);
return services;
}
in case we want to use it with a factory that instantiates the service like the following sample:
var serviceProvider =
new ServiceCollection()
.Replace<IMyService>(sp => new MyService(), ServiceLifetime.Singleton)
.BuildServiceProvider();
Solution 4:
In the latest version of .net core(net5.0), this is how it should be.
using Microsoft.Extensions.DependencyInjection;
services.Replace<IFoo, FooB>(ServiceLifetime.Transient); // Or ServiceLifetime.Singleton
Or try this one.
services.Replace(ServiceDescriptor.Transient<IFoo, FooB>());