2

For .NET Generic Host it is possible to register multiple implementations of same service interface. When materializing that service the last added is obtained (that is correct even for multiple implementations registered with same key):

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Threading.Tasks;

namespace IHostTest;

internal class Program
{
    static async Task Main()
    {
        var hostBuilder = Host.CreateDefaultBuilder().ConfigureServices(services =>
        {
            services.AddSingleton<ISomeService, SomeService1>();
            services.AddSingleton<ISomeService, SomeService2>();

            services.AddKeyedSingleton<ISomeService, SomeService1>("key");
            services.AddKeyedSingleton<ISomeService, SomeService2>("key");
        });
        var host = hostBuilder.Build();
        //will get SomeService2 here
        var service = host.Services.GetRequiredService<ISomeService>();
        //will also get SomeService2 here
        var keyedService = host.Services.GetRequiredKeyedService<ISomeService>("key");

        await host.RunAsync();
    }
}

internal interface ISomeService
{ }

internal class SomeService1 : ISomeService
{ }

internal class SomeService2 : ISomeService
{ }

Is there any way to forbid multiple service implementations? I need to get an exception when building my host in that case. Or detecting such situation via unit tests is the only option?

5
  • 2
    You could consider checking/inspecting the service collection for the desired type to determine how many implementations exists and then throwing an exception as needed. Commented Jun 2 at 8:50
  • @Nkosi Do you mean some extension method like .GetServiceEnsureSingleImplementation<ISomeService>() where I should place this check before actually calling .GetRequiredService<ISomeService>()? It looks like an overkill to check this every time I need any service - checking whole container once in a unit-test looks be better from my point of view. Commented Jun 2 at 8:59
  • 1
    1) Why do you register multiple services to begin with, if you don't want all of them? 2) last added is obtained that's not correct, if you use an array eg GetRequiredService<ISomeService[]>() you'll get all implementations Commented Jun 2 at 8:59
  • 1
    @PanagiotisKanavos it wasn't intended - it was a mistake in code (many services are registered by many extension methods in several libraries and it's easy to register multiple) Commented Jun 2 at 9:01
  • 1
    What Nkosi means is to check the contents of the ServiceCollection before calling Build. You can cast IServiceCollection to the concrete ServiceCollection and iterate over its contents Commented Jun 2 at 9:05

2 Answers 2

1

Is there any way to forbid multiple service implementations?

No. You might find it surprising, but there are use cases for having multiple implementations of the same service, even without tags.

There are several approaches to avoiding multiple implementations being registered, including:

  • Code reviews and managing service collection building (hard but likely where you need to be).
  • Making use of ServiceCollectionDescriptorExtensions.TryAdd to only add if service has not been added.
  • Checking the service collection before building (as noted in the comments).
  • Using more specific service definitions (more specific and less generic).

But really here you need to ask yourself "why is this a problem?", because that will lead to how you address the problem (including potentially realising it isn't).

If you have multiple third party libraries conflicting, then you need to resolve that (this would apply for any type of conflict, not just service registration to build a DI container). It is possible you really have a people problem rather than a technical problem (library creator/maintainers are stepping on each other's toes) and people problems are rarely soluble by purely technical means.

Sign up to request clarification or add additional context in comments.

Comments

0

This is from a technical "how-to" perspective. As noted elsewhere, the need for this might point to other problems. Nonetheless, here are two approaches, both of which are startup validations.

public static class ServiceCollectionValidations
{
    // This is a more likely scenario. You're not concerned about
    // any and all duplicate descriptions, just for a specific type.
    public static void CheckForMultipleRegistrations(
        IServiceCollection serviceCollection, 
        Type serviceType)
    {
        var numberOfRegistrations = serviceCollection
            .Count(descriptor => descriptor.ServiceType == serviceType);

        if (numberOfRegistrations > 1)
            throw new Exception($"Service type {serviceType} has multiple implementations, which is not allowed.");
    }

    // This is if you want to check for any duplicate registrations.
    // In this case assemblies to check are passed as parameters.
    // This is because there may be numerous framework services that
    // are expected to have multiple implementations.
    // This probably makes no sense because I can't applying this as
    // a blanket rule.
    public static void CheckForMultipleRegistrations(
        IServiceCollection serviceCollection, 
        params Assembly[] assemblies)
    {
        IEnumerable<Type> servicesWithMultipleImplementations = serviceCollection
            .GroupBy(descriptor => descriptor.ServiceType)
            .Where(group => group.Count() > 1)
            .Select(group => group.Key);
        foreach (var service in servicesWithMultipleImplementations)
        {
            if (assemblies.Contains(service.Assembly))
            {
                throw new Exception($"Service type {service} has multiple implementations, which is not allowed.");
            }
        }
    }
}

1 Comment

I realized before I was done typing this that I'm living in 2020. Someone can ask Cursor to do this and it will probably give them the same answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.