Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get all implementations of a given type from IServiceProvider regardless of if they are keyed or not

WHY THIS ISNT A DUPLICATE

NOT TO BE CONFUSED WITH "HOW DO I GET THE KEY AND TYPE FROM KEYED SERVICES", that question only talks about how to get all the keyed types, maybe that's part of the answer but does not talk about non-keyed types and how to get them factored in, as someone who spent ages Googling this already the answers in that question do not answer the question here, and for searchability purposes that question is far more specific than this which is a much higher level "ALL IMPLEMENTATIONS" not just "KEYED IMPLEMENTATIONS" as requested in there.

ACTUAL QUESTION

Since .net 8 it looks like Microsoft has added the ability to have keyed services which seems great, but i've noticed that calling serviceProvider.GetServices(typeof(SomeInterface)); will not return instances that also have key information.

For example:

var collection = new ServiceCollection();
collection.AddKeyedSingleton<ITestInterface, TestClass1>("test1");
collection.AddKeyedSingleton<ITestInterface, TestClass2>("test2");
collection.AddSingleton<ITestInterface, TestClass3>();

var serviceProvider = collection.BuildServiceProvider();
var implementations = serviceProvider.GetServices<ITestInterface>();
Assert.NotNull(implementations);
Assert.NotEmpty(implementations);

// Expecting TestClass1, TestClass2, TestClass3
Assert.True(implementations.Count() == 3); 
// Actually just TestClass3

I am unsure of the expected behaviour of the above test, I would assume it should pass and return all bindings of that given type. So then raises the question of how to get the expected outcome from the serviceProvider.

I thought maybe I would need to use serviceProvider.GetKeyedServices<ITestInterface>() but unfortunately that requires you to provide a key, and in this case we want to ignore the keying and just get all implementations of a given type.

So can anyone advise on how to get the expected behaviour? even if it requires a union between 2 service provider methods.

like image 913
Grofit Avatar asked Oct 16 '25 10:10

Grofit


2 Answers

The behavior you are experiencing is by design, although Microsoft could very well have chosen a behavior where keyed services are part of the collection. But by default, they are not.

While this answer of mine explains how to resolve all keyed registrations as an IDictionary<TKey, TService>, that doesn't get you the behavior you wish to see.

This behavior, however, can be achieved using a custom extension method:

public static void AddBothKeyedAndDefaultSingleton<TService, TImplementation>(
    IServiceCollection services, string key)
    where TService : class
    where TImplementation : class, TService
{
    services.AddKeyedSingleton<TService, TImplementation>(key);
    services.AddSingleton(sp => sp.GetRequiredKeyedService<TService>(key));
}

If you run your test with this extension method, you get the expected output:

var collection = new ServiceCollection();
collection.AddBothKeyedAndDefaultSingleton<ITestInterface, TestClass1>("test1");
collection.AddBothKeyedAndDefaultSingleton<ITestInterface, TestClass2>("test2");
collection.AddSingleton<ITestInterface, TestClass3>();

var serviceProvider = collection.BuildServiceProvider();
var implementations = serviceProvider.GetServices<ITestInterface>();

Assert.True(implementations.Count() == 3); 

You might be tempted to iterate the entire IServiceCollection and add an extra registration for every keyed registration, making this a global behavior change, but that would be a grave mistake. Other reusable libraries and framework components that you are using (or updates you will be installing in the future) might very well depend on the current behavior.

like image 80
Steven Avatar answered Oct 19 '25 13:10

Steven


There is a little known trick to solve this problem using a special static key provided by the library:

var implementations = serviceProvider.GetKeyedServices<ITestInterface>(KeyedService.AnyKey);

There was a bug involving this method that was fixed in .NET 9. So make sure to upgrade to at least .NET 9 or get the corresponding v9.0.0 NuGet package (Microsoft.Extensions.DependencyInjection.Abstractions).

like image 21
silkfire Avatar answered Oct 19 '25 12:10

silkfire