Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I validate the DI container in ASP.NET Core?

In my Startup class I use the ConfigureServices(IServiceCollection services) method to set up my service container, using the built-in DI container from Microsoft.Extensions.DependencyInjection.

I want to validate the dependency graph in an unit test to check that all of the services can be constructed, so that I can fix any services missing during unit testing instead of having the app crash at runtime. In previous projects I've used Simple Injector, which has a .Verify() method for the container. But I haven't been able to find anything similar for ASP.NET Core.

Is there any built-in (or at least recommended) way of verifying that the entire dependency graph can be constructed?

(The dumbest way I can think of is something like this, but it will still fail because of the open generics that are injected by the framework itself):

startup.ConfigureServices(serviceCollection); var provider = serviceCollection.BuildServiceProvider(); foreach (var serviceDescriptor in serviceCollection) {     provider.GetService(serviceDescriptor.ServiceType); } 
like image 924
Martin Wedvich Avatar asked Mar 07 '18 10:03

Martin Wedvich


People also ask

How does DI work in .NET Core?

ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies. For more information specific to dependency injection within MVC controllers, see Dependency injection into controllers in ASP.NET Core.

What is IServiceCollection in .NET Core?

AddScoped(IServiceCollection, Type, Type) Adds a scoped service of the type specified in serviceType with an implementation of the type specified in implementationType to the specified IServiceCollection.

What is DI container in C#?

IoC Container (a.k.a. DI Container) is a framework for implementing automatic dependency injection. It manages object creation and it's life-time, and also injects dependencies to the class.

How do you implement DI?

The recommended way to implement DI is, you should use DI containers. If you compose an application without a DI CONTAINER, it is like a POOR MAN'S DI . If you want to implement DI within your ASP.NET MVC application using a DI container, please do refer to Dependency Injection in ASP.NET MVC using Unity IoC Container.


2 Answers

A built-in DI container validation was added in ASP.NET Core 3 and it is enabled only in the Development environment by default. If something is missing, the container throws a fatal exception on startup.

Keep in mind that controllers aren't created in the DI container by default, so a typical web app won't get much from this check until the controllers are registered in the DI:

public void ConfigureServices(IServiceCollection services) {     services.AddControllers()         .AddControllersAsServices(); } 

To disable/customize the validation, add a IHostBuilder.UseDefaultServiceProvider call:

public class Program {     public static IHostBuilder CreateHostBuilder(string[] args) =>         Host.CreateDefaultBuilder(args)             //...             .UseDefaultServiceProvider((context, options) =>             {                 options.ValidateOnBuild = false;             }); 

This validation feature has several limitations, read more here: https://andrewlock.net/new-in-asp-net-core-3-service-provider-validation/

like image 181
Ilya Chumakov Avatar answered Oct 04 '22 07:10

Ilya Chumakov


Actually, I just used the example from your question with a few modifications and it worked pretty well for me. The theory behind filtering by classes in my namespace is that those will end up asking for everything else I care about.

My test looked a lot like this:

[Test or Fact or Whatever] public void AllDependenciesPresentAndAccountedFor() {     // Arrange     var startup = new Startup();      // Act     startup.ConfigureServices(serviceCollection);      // Assert     var exceptions = new List<InvalidOperationException>();     var provider = serviceCollection.BuildServiceProvider();     foreach (var serviceDescriptor in services)     {         var serviceType = serviceDescriptor.ServiceType;         if (serviceType.Namespace.StartsWith("my.namespace.here"))         {             try             {                 provider.GetService(serviceType);             }             catch (InvalidOperationException e)             {                 exceptions.Add(e);             }         }     }      if (exceptions.Any())     {         throw new AggregateException("Some services are missing", exceptions);     } } 
like image 39
Benrobot Avatar answered Oct 04 '22 07:10

Benrobot