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:
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>());
Related videos on Youtube
Comments
-
Ilya Chumakov over 2 years
Let us consider a service registration in
Startup.ConfigureServices
:public void ConfigureServices(IServiceCollection services) { services.AddTransient<IFoo, FooA>(); }
Is it possible to change
IFoo
registration toFooB
afterAddTransient
has been called? It can be helpful for testing purposes (for example, inTestStartup
subclass) or if our access to codebase is limited.If we register another
IFoo
implementation:services.AddTransient<IFoo, FooA>(); services.AddTransient<IFoo, FooB>();
Then
GetService<IFoo>
returnsFooB
instead ofFooA
:IFoo service = services.BuildServiceProvider().GetService<IFoo>(); Assert.True(service is FooB);
However,
GetServices<IFoo>
successfully returns both implementations (and the same forGetService<IEnumerable<IFoo>>
):var list = services.BuildServiceProvider().GetServices<IFoo>().ToList(); Assert.Equal(2, list.Count);
There is
Remove(ServiceDescriptor)
method inIServiceCollection
contract. What should I do withServiceDescriptor
to modify a service registration? -
Ilya Chumakov about 7 years
Replace
internally calls the sameAdd
andRemove
methods which I considered. It is a good addition anyway, thanks. -
Kolky over 6 yearsAnother way (in less lines, but using the same methods);
services.Replace(ServiceDescriptor.Transient<IFoo, FooB>());
-
lbrahim almost 5 yearsHow can we reflect the change of
ServiceCollection
toIServiceProvider
? -
Gustavo Ferrufino over 3 yearsWould it be a good practice to do this but with a singleton?
-
Dustin Kingen over 3 years@Vesper There may be use cases when you need to replace a singleton.
-
Hatef. over 3 yearsHow can i use this in a controller? I mean how can I replace a service from a controller?
-
Hatef. over 3 yearsI tried to use your code in controller. but it doesn't work. do I missed something?
-
diegosasw over 3 yearsIf you provide more details about what's not working maybe I can help. But this code works fine.
-
Ilya Chumakov over 3 years@Hatef you can't do it from a controller, it should be done from
Startup.ConfigureServices
method. -
Ilya Chumakov over 3 yearsAnd the only proper way to modify
ServiceCollection
is to do it before the container is built, i.e. before object implementingIServiceProvider
is created. -
Ohad Schneider over 3 years@Vesper certainly, very useful in test scenarios (e.g. you have a singleton logger and you want to test some logs are being written)
-
lonix over 2 yearsThe first one doesn't work. The second one works.