2

Is there a way in ASP.NET to get all singletons?

I want to implement a health check which would instantiate all the registered singletons and only after that return a healthy status.

I am trying to get all the singletons from the IServiceProvider, but it seems to have only one method, i.e. the object? GetService(Type serviceType);.

So, maybe someone knows a way in C# to get all singletons?

I am trying to do like so:

_servicesProvider
  .GetServices<object>()
  .Where(service => _servicesProvider.GetService(service.GetType()) == service)

But it seems to also give me back scoped services.

Seems like this:

_servicesProvider
  .GetServices<object>()
  .ToList();
return new HealthCheckResult(HealthStatus.Healthy);

should work. But I am still hoping for a more optimal solution.

manymanymore
  • 2,251
  • 3
  • 26
  • 48
  • You could use an marker interface to identify those instances you actually want to health check on (not all singletons will be ones you want to check the health of). – Neil Jul 04 '23 at 16:59

2 Answers2

2

I don't believe there is a built in way to do this.

This answer to a similar question agrees, and suggest reflection as a possible solution.

Another option is to register your ServiceCollection with itself via .AddSingleton<IServiceCollection>(serviceCollection). You can then inject this or request it from the IServiceProvider and query it to get all the service descriptions with singleton lifetime:

var serviceCollection = serviceProvider.GetRequiredService<IServiceCollection>();
var singletons = serviceCollection
    .Where(descriptor => descriptor.Lifetime == ServiceLifetime.Singleton)
    .Select(descriptor => serviceProvider.GetRequiredService(descriptor.ServiceType))
    .ToList();

foreach(object singleton in singletons)
{
    // do something with the singleton
}

Depending on your use case it may be better to make your own implemention with a reduced interface, rather than exposing all of IServiceCollection to the consumer of the DI container.

DisplayName
  • 475
  • 2
  • 13
  • It says that "No service for type 'Microsoft.Extensions.DependencyInjection.IServiceCollection' has been registered.". Is it possible to avoid having to manually register the "IServiceCollection" as a dependency? – manymanymore Jul 04 '23 at 17:48
  • This method will not get all singletons. Take the following configuration for instance: `s.AddSingleton(); s.AddTransient(); s.AddSingleton();` While the service collection contains both registrations, `GetRequiredService` will return `Foo3` twice. I have no good workaround that could handle this. – Steven Jul 04 '23 at 20:35
  • 1
    I added my own answer that tries to solve this issue of missing singletons. – Steven Jul 05 '23 at 08:05
  • @manymanymore I don't think you can skip the manual registration, but it should be simple enough to do in your Startup. – DisplayName Jul 06 '23 at 10:20
  • 1
    @Steven this is a good point, though I'd argue a bit of an edge case as most applications will only intentionally register one implementation, and most classes will use basic DI to request the latest registration of an interface. – DisplayName Jul 06 '23 at 10:24
2

@DisplayName already gave an answer that might work as a starting point. Here is a different solution:

IEnumerable<object> singletons = (
    from service in services
    group service by service.ServiceType into g
    where g.Any(descriptor => descriptor.Lifetime == ServiceLifetime.Singleton)
    from pair in serviceProvider.GetServices(g.Key).Zip(g)
    where pair.Second.Lifetime == ServiceLifetime.Singleton
    select pair.First)
    .Distinct();

Compared to @DisplayName's answer, this solution will retrieve all registered singletons, even those that are part of a collection. This solution, however, exhibits a few unfortunate downsides that might not exist in @DisplayName's answer:

  1. If multiple registrations for the same service type exist, where the registrations contain -besides singletons- transient and/or scoped registrations, those registrations will be resolved from the container as well. They will be filtered out and not be part of the list of singletons, but they will still be created nonetheless. This might cause performance issues as the health check can now cause a lot of memory allocation (depending on the size of the application).
  2. This solution might not yield the correct results when used in combination with a DI Container that plugs into the MS.DI infrastructure (such as Autofac or Lamar). They enable features like Dynamic Interception and Decoration, and changes made specifically through the API of those containers might be missed.

Pick the solution that works best for your situation.

Steven
  • 166,672
  • 24
  • 332
  • 435