Replace service registration in ASP.NET Core built-in DI container?

C#Dependency Injectionasp.net Core.Net Core

C# Problem Overview


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 to FooB after AddTransient has been called? It can be helpful for testing purposes (for example, in TestStartup 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> returns FooB instead of FooA:

IFoo service = services.BuildServiceProvider().GetService<IFoo>();
Assert.True(service is FooB);

However, GetServices<IFoo> successfully returns both implementations (and the same for GetService<IEnumerable<IFoo>>):

var list = services.BuildServiceProvider().GetServices<IFoo>().ToList();
Assert.Equal(2, list.Count);

There is Remove(ServiceDescriptor) method in IServiceCollection contract. What should I do with ServiceDescriptor to modify a service registration?

C# Solutions


Solution 1 - C#

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 - C#

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 - C#

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 - C#

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>());

Solution 5 - C#

I replace the repository in my Test Fixture WebApplicationFactory using

    public static WebApplicationFactory<TEntryPoint> WithRepository<TEntryPoint>(
        this WebApplicationFactory<TEntryPoint> webApplicationFactory,
        IStoreRepository storeRespository)
        where TEntryPoint : class
    {
        return webApplicationFactory.WithWebHostBuilder(builder => builder.ConfigureTestServices(
               services =>
               {
                   services.Replace(ServiceDescriptor.Scoped(p => storeRespository));
               }));
    }

And use it in the test as

var respository = new ListRepository();
var client = _factory
    .WithRepository(respository)
    .CreateClient();

If the services has multiple registrations, then you have to remove first. Replace is not removing all the instances.

services.RemoveAll(typeof(IStoreRepository));

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionIlya ChumakovView Question on Stackoverflow
Solution 1 - C#Dustin KingenView Answer on Stackoverflow
Solution 2 - C#Ilya ChumakovView Answer on Stackoverflow
Solution 3 - C#diegosaswView Answer on Stackoverflow
Solution 4 - C#VivekDevView Answer on Stackoverflow
Solution 5 - C#Xavier JohnView Answer on Stackoverflow