ASP.NET Core – fast and automatic dependency injection setup

Last Updated: June 21, 2018 | Created: June 21, 2018

Microsoft’s documentation says “ASP.NET Core is designed from the ground up to support and leverage dependency injection”. It also says that “Dependency injection (DI) is a technique for achieving loose coupling between objects and their collaborators, or dependencies.” (read Martin Fowler’s article for the in-depth coverage of DI).

I have used DI for years and I love it – it allows me to compartmentalize my applications and have DI link everything up at run time. The only chore is making sure all my services are registered properly at startup. This article is about a library I build to automatically register classes in an assembly into Microsoft’s DI provider (known as a DI container).

TL;DR; – summary

This article introduces a small library called NetCore.AutoRegisterDi (NuGet link) that can automatically register classes with interfaces in an assembly into the built-in ASP.NET Core DI container. The reason I built this library is because ASP.NET Core’s DI container turns out to be quick – at least 5 times quicker than AutoFac. I also describe how I use this in applications that have a lot of assemblies.

Setting the scene

The typical approach shown in the Microsoft’s documentation to registering services is to add a line of code to register each service in the ConfigureServices method in ASP.NET Core’s Startup class. For me that is a horrible maintenance issue, because I might forget to register a service I need: and if I do forget then my application will fail when that service is used.

Until now my go-to solution for automatically registering my services in DI is AutoFac’s RegisterAssemblyTypes method. But when I saw David Fowler’s tweet with a link to a set of dependency Injection container benchmarks, I found out that the ASP.NET Core DI container is a lot faster than AutoFac.

I worry about performance, so I thought – how hard would it be to build a similar extension to AutoFac’s RegisterAssemblyTypes method, but for the NET Core DI container,  Microsoft.Extensions.DependencyInjection? Turns out it was easy (2 hours + 2 hours for NuGet+docs). This article is about how you can use the NetCore.AutoRegisterDi in your own applications.

NOTE: Microsoft docs says, “The default services container provided by ASP.NET Core provides a minimal feature set and isn’t intended to replace other containers.” However, I find Microsoft’s DI library has all the things I need for most applications, but some features aren’t well publicised – see Steve Gordon’s articles on three Microsoft DI features that I had to find the hard way:

Introduction to DI – what is a service?

Note: If you already understand DI you can skip this section.

I refer to a service, which is a combination of a class that implements that service, known as the implementation type in the NET Core DI container, which is linked (known as registered) to a service type, which is typically an interface. Here is an example of an interface, followed by the class that implements that interface.

public interface IMyService
{
    string IntToString(int num);
}

public class MyService : IMyService
{
    public string IntToString(int num)
    {
        return num.ToString();
    }
}

There are various ways to use a service in another class. The standard way is to add a parameter to the class’s construction and the DI container will resolve that interface to the implementation class – this is known as constructor injection. Here is an example.

public class UseService
{
    private readonly IMyService _service;

    public UseService(IMyService service)
    {
        _service = service;
    }

    public string CallService()
    {
        int i = 1; //... some calculation
        return _service.IntToString(i);
    }
}

This interface/class pattern “achieving loose coupling between objects and their collaborators” (to quote Microsoft). This has lots of benefits, but here are the two main ones for me:

  • The interface clearly defines the properties, methods, classes etc. that user of the service can access. By using an interface I can’t access things I’m not supposed to use, so interfaces makes me focus on what I should use and ignore the rest.
  • The two classes are “loosely coupled”, i.e. it is easy to replace the original version of the implementing class if it conforms to the interface. This is especially useful when unit testing, as it’s easy to replace the interface with a mock that allows you to test the UseService class on its own.

How to NetCore.AutoRegisterDi works

The NetCore.AutoRegisterDi provides three extension methods – here is an example that uses all three methods.

service.RegisterAssemblyPublicNonGenericClasses(… your assembly ref goes here …)
    .Where(x => x.Name.EndsWith(“Service”))
    .AsPublicImplementedInterfaces();
  1. The first line finds all the public, non-generic classes (see docs for detailed definition of what classes it finds) in the assembly you provide (or assemblies – it takes params Assembly[] assemblies).
  2. The Where method in the second line is optional and it allows you to filter the classes if you need to.
  3. The final line finds all the public, non-nested interfaces (apart from IDisposable) that each class implements and registers each interface against the class.

The AsPublicImplementedInterfaces method takes an optional parameter of the ServiceLifetime you want for the service. It defaults to Transient lifetime where a new service instance is created on every injection, which is a good default for services. The other options are Singleton (same instance used on every injection) or Scoped (a new service instance is created for each HTTP request).

TIP: The NetCore.AutoRegisterDi Where method is useful if you want different ServiceLifetime – I name my classes with an ending that tells me what sort of lifetime I need, and filter on that.

An approach to using AutoRegisterDi in real applications

Now, you could call the RegisterAssemblyPublicNonGenericClasses method directly in the ConfigureServices method in the Startup class, and it would work fine. You just need to define the assemblies you want to scan. Here is how you would do this.

public void ConfigureServices(IServiceCollection services)
{
   //... other configure code removed

   var assemblyToScan = Assembly.GetAssembly(typeof(YourClass)); //..or whatever assembly you need

   service.RegisterAssemblyPublicNonGenericClasses(assemblyToScan)
     .Where(c => c.Name.EndsWith("Service"))
     .AsPublicImplementedInterfaces();

That’s fine, but it’s not what I do. I create an extension method inside each key assembly which registers itself and any other assemblies that it ‘owns’. In a small applciation I would call my ServiceLayer assembly, and it would look after setting up all the other assemblies.

Here is an example taken from a larger application where I have multiple grouped assemblies (see Simon Brown’s package by component approach) .

public static void DevModStartup(this IServiceCollection services, 
    IConfiguration configuration)
{
    //This registers the service layer: I only register the classes who name ends with "Service" (at the moment)
    services.RegisterAssemblyPublicNonGenericClasses(
              Assembly.GetExecutingAssembly())
        .Where(c => c.Name.EndsWith("Service"))
        .AsPublicImplementedInterfaces();

    //Now I register the DevModBizLogic assembly used by DevModService
    services.RegisterAssemblyPublicNonGenericClasses(
             Assembly.GetAssembly(typeof(BuildTestTenant)))
        .AsPublicImplementedInterfaces();

    //Put any code here to initialise values from the configuration parameter
}

NOTE: This shows the two ways of getting an assembly. Assembly.GetExecutingAssembly() gets the assembly that is running and Assembly.GetAssembly(typeof(SomeType)) gets the assembly that the SomeType is defined in.

I then call this DevModStartup extension method in the ASP.NET Core ConfigureServices method like so.

public void ConfigureServices(IServiceCollection services)
{
   //... other configure code removed

   services.DevModStartup(Configuration);
   //... add other assembly startup extension methods here

I think this is cleaner, especially as it keeps the rules about what classes to scan and ServiceLifeTime in the assembly that knows what is going on.

You should also note that I provide the Configuration property to the Startup extension method. This gives the extension method access to the appsetting.json file from which it can read static values, like setting or crypto keys etc. on application startup and store them in internal static variables.

WARNING: I wouldn’t use RegisterAssemblyPublicNonGenericClasses with the ASP.NET Core assembly without a good Where clause. The ASP.NET Core assembly has a LOT of services which it sets up itself, so you don’t want to re-register those.

Conclusion

DI is a great tool and one that I use a lot. The problem is in real applications I can have many hundreds (maybe thousands) of DI services so I want the DI to a) be easy to set up and b) be fast. I fact I think not automatically registering your services is an anti-pattern because it’s so easy to forget to register a service, and your live application will fail when that service is used.

I used to use AutoFac’s RegisterAssemblyTypes method, but that was about the only feature I used. Now that I have the NetCore.AutoRegisterDi library I have an assembly scanning feature that works with ASP.NET Core’s built-in DI container, and it makes my DI faster.

Happy coding!