Assert your IoC Container Services

Verify your Registered DI Services, actually work!

Ikechi Michael
2 min readJun 7, 2024

Have you ever spent time crafting a new feature, registering your dependencies in your Startup.cs file, wrote unit tests, maybe some integration tests, confirmed the feature works, and deployed.

Then oh no! Some other feature is broken because your feature updated dependency Foo from version 0.0.1 to 0.0.2 and a sub-project expects it to be 0.0.1 , and this wasn’t caught?

Or maybe you missed a logic when wiring up your dependencies in your Startup.cs file, and who hasn’t? Those things can get complicated.

What if you could assert on startup, that every registered service in your IoC can be instantiated? How many bugs would be caught much earlier than usual?

In .NET, we register a service with:

services.AddSingleton<IPersonService, PersonService>();
services.AddScoped<IFoo>(() => new Foo());
services.AddTransient<IBar>(() => new Bar());

I wish we had an in-built way to keep track of every registered service via their Type , which can be obtained with typeof(IPersonService) and personService.GetType() in C# e.g. a List<Type> would be perfect.

Because then, we could loop through that list, and check that the ServiceProvider can actually instantiate or/and return those types e.g.


public void Configure(IApplicationBuilder app)
{
var provider = app.BuildServiceProvider();
provider.AssertTrackedServices(); // will throw when a service cannot be resolved
}

And this can be called immediately on startup, ensuring that the app fails to run when services are misconfigured, or can somehow not be instantiated.

internal static class ServiceProviderExtensions
{
private static List<Type> _trackedTypes = new List<Type>();

public static void AssertTrackedServices(this IServiceProvider provider)
{
var (services, exceptions) = GetTrackedServices(provider);
if (exceptions.Any())
{
throw new AggregateException(exceptions);
}
}

public static (List<(Type, object)>, List<Exception>) GetTrackedServices(this IServiceProvider provider)
{
var services = new List<(Type, object)>();
var exceptions = new List<Exception>();
foreach (var type in _trackedTypes)
{
try
{
var service = provider.GetRequiredService(type);
services.Add((type, service));
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
return (services, exceptions);
}

public static IServiceCollection TrackScoped<TInterface, TService>(
this IServiceCollection services,
Func<IServiceProvider, TService> factory
) where TService : class, TInterface where TInterface : class
{
_trackedTypes.Add(typeof(TInterface));
services.AddScoped<TInterface, TService>(provider => factory(provider));
return services;
}
}

Now you can track a registered service by using:

services.TrackScoped<IPersonService>(provider => new PersonService());

You can also see which services fail to be instantiated with:

var (trackedServices, exceptions) = provider.GetTrackedServices();

Feel free to extend this code for other variations of service registrations.

--

--

Ikechi Michael

I’ve learned I don’t know anything. I've also learned that people will pay for what I know. Maybe that's why they never pay.