ASP.NET Core – fast and automatic dependency injection setup

Last Updated: October 6, 2020 | 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).

UPDATE: New features added in version 2.

Now has attributes for defining the ServiceLifetime of your classes, e.g. adding the [RegisterAsScoped] attribute to a class will mean its ServiceLifetime in the DI will be set to Scoped. Thanks to Fedor Zhekov (GitHub @ZFi88).

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, C
{
    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 make 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 library is very simple – it will scan an assembly, or a collection of assemblies to find classes that a) are simple classes (not generic, not abstract, not nested) that have interfaces and b) have an interface(s). It will then register each class with its interfaces with the NET Core DI provider.

Here is simple example of calling this library to register all the valid classes in the current assembly (also known as a project).

service.RegisterAssemblyPublicNonGenericClasses()
   .AsPublicImplementedInterfaces();

That’s simple example, but one I use that all the time. That’s because in every project that has classes to register with the DI provider, I add a simple wrapper extension method which handles all my DI registering, as shown below

public static class NetCoreDiSetupExtensions
{
    public static void RegisterServiceLayerDi
        (this IServiceCollection services)
    {
        services.RegisterAssemblyPublicNonGenericClasses()
            .AsPublicImplementedInterfaces();

        //put any non-standard DI registration, e.g. generic types, here
    }
}

The idea of the code above is that the NetCore.AutoRegisterDi library deals with all the standard class DI registering. In the few times I need a more complex type registered, say a generic type such as BlobRepository<T>, then I add extra code to handle the registering manually.

Then, I call the RegisterServiceLayerDi extension in ASP.NET Core’s Startup class like this.

public void ConfigureServices(IServiceCollection services) //#A
{
         services.AddControllersWithViews()

        //… code setup code left out 
       services.RegisterServiceLayerDi();
       services.RegisterBizLogic();
       //... and so on - one for each project that needs DI registering
}

In case you need it the NetCore.AutoRegisterDi code has various parts that give you more control if you need it. Here is an example with the four extension methods.

service.RegisterAssemblyPublicNonGenericClasses(… your assembly ref goes here …)
.Where(x => x.Name.EndsWith(“Service”))  //optional
.IgnoreThisInterface<IMyInterface>()     //optional
.AsPublicImplementedInterfaces(ServiceLifetime.Scoped);
  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), or if there are no parameters it scans the assemble in which the method is called.
  2. The Where method in the second line is optional and it allows you to filter the classes if you need to.
  3. By default this library won’t register a class against the IDisposable and ISerializable interfaces,but you might want to add other interfaces to the interface ignore list. To do that you can use the IgnoreThisInterface method to do that. NOTE: You can call to the IgnoreThisInterface method multiple times to add each interface to ignore.
  4. The final line finds all the public, non-nested interfaces (apart from IDisposable) that each class implements and registers each interface against the class. It has an optional lifetime property which defaults to ServiceLifetime.Transient, but as shown on line three you can define another lifetime (but be warned – most services should be Transient).

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).

NOTE: See the NetCore.AutoRegisterDi README file for the full documentation.

Using attributes to define the lifetime of a service

One improvement that has been added to NetCore.AutoRegisterDi is the ability to define the lifetime of your service by using attributes, for example:

[RegisterAsScoped]
public class MyOtherScopeService : IMyOtherService
{
    public string Result { get; }
}

There are four attributes, which will override any setting given AsPublicImplementedInterfaces method.

  • [DoNotAutoRegister] – tells NetCore.AutoRegisterDi to not register this class.
  • [RegisterAsTransient] – this class will be registered with a lifetime of Transient
  • [RegisterAsScoped] – this class will be registered with a lifetime of Scoped.
  • [RegisterAsSingleton] – this class will be registered with a lifetime of Singleton.

Debugging your DI registrations

Sometimes its useful to see what the NetCore.AutoRegisterDi has registered with the .NET DI provider. In release 2.1.0 I added a return from the final stage to return a list of a) the interfaces it has ignored and b) what classes where registered with the NET DI provider.

To do this you need to capture the list of results coming out of the AsPublicImplementedInterfaces method, as shown in the code below.

var results = service
    .RegisterAssemblyPublicNonGenericClasses()
    .IgnoreThisInterface<IAnotherInterface>()
    .AsPublicImplementedInterfaces();

Then, if you put a breakpoint just after this code, then you can hover of the results variable and get a view on what was registered. See image below.

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.

PS. Andrew Lock recommends a DI library called Scrutor – see his article called “Using Scrutor to automatically register your services with the ASP.NET Core DI container“. That might fit your needs better than my library.

0 0 vote
Article Rating
Subscribe
Notify of
guest
24 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
Piotr Kołodziej
Piotr Kołodziej
3 months ago

This is a big time saver, thank you!

Chris Cottrell
Chris Cottrell
3 months ago

Awesome! Awesome! I was doing similar with Autofac and moving the app to core and wanted to explore my options and have way too many interfaces to code them all out in ConfigureServices

Jon P Smith
3 months ago
Reply to  Chris Cottrell

Hi Chris,

Glad you found it useful. Andrew Lock (author of ASP.NET Core in Action) pointed out there is similar library at https://github.com/khellang/Scrutor – you may want to look at that too.

Ravi
Ravi
3 months ago

Superb, i am looking for simple DI for my .net core application, I have using AutoRegisterDi but i am getting “Object reference not set to an instance of an object” exception while calling controller.
Step 1 : I have external dll which is implemented by using Interface in it. like
public class AlgorithamCalculator : IAlgorithamCalculation
{
//Implimentation
.}

step 2 : I want to use this AlgorithamCalculator in my project , i have created local interface class as IAlgorithamCalculation . Now In my Startup.cs class i am calling dll class like this

var assemblyScan = Assembly.GetAssembly(typeof(AlgorithamCalculator )); // here it gets the dll
services.RegisterAssemblyPublicNonGenericClasses(assemblyScan)
.Where(c => c.Name.Equals(“AlgorithamCalculator “))
.AsPublicImplementedInterfaces();
Step3: I am calling this my controller as dependency

private IAlgorithamCalculation _algorithamCalc;
public AlgorithamReportController( IAlgorithamCalculation algorithamCalc )
{
_algorithamCalc = algorithamCalc; // i should access dll functions by using algorithamCalc .
}

when i am calling AlgorithamReport Controller from url , i am getting “Object reference not set to an instance of an object” , i am unable to find what i have missed here , Can you please help me.

Thanks ,
Ravindra.

Jon P Smith
3 months ago
Reply to  Ravi

Hi Ravi,

Try registering manually and check that works. I would also change your Equals test to `Where(c => c.Name.Equals(typeof(AlgorithamCalculator ).Name)) as it safer. Also, my library assumes your interface is public.

If you still have problems then put a breakpoint after the AsPublicImplementedInterfaces and look at the services instance – it will have a very long list of registered items, yours should be near the end.

Regency Software
3 months ago

I read another article about someone using AutoFac to wire up all the dependencies. I thought well I like AutoFac (and have used it) but I only want to use Microsoft’s .NET Core DI… then came across your awesome post!

Jon P Smith
3 months ago

Hi Regency,

Yep, I think Microsoft’s DI is great – its fast, built-in and handles everything I need. Just a bit of work to automate things and its all fine.

Alok Babu
3 months ago

This is great ! But what if I want to register all services from multiple assemblies based on assembly name which follows certain conventions like Starts with “CompanyName” for example?

Jon P Smith
3 months ago
Reply to  Alok Babu

Hi Alok,

The RegisterAssemblyPublicNonGenericClasses extension method takes has the parameters (this IServiceCollection services, params Assembly[] assemblies), so you can provide multiple assemblies to scan, e.g.

 
service.RegisterAssemblyPublicNonGenericClasses(
       Assembly.GetAssembly(typeof(ClassInAssembly1)), Assembly.GetAssembly(typeof(ClassInAssembly2))) 

      // .etc.

Alok Babu
3 months ago
Reply to  Jon P Smith

Hi Jon, Say If I have many assemblies from other projects which are referenced in the current project. And how can I find them by “name”? I tried to


List allAssemblies = new List();
                string path = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
                var compAssemblyPaths = Directory.GetFiles(path, "MyCompany.*.dll").ToList();
                foreach (string dllPath in qAssemblyPaths)
                {
                    //string fileName = Assembly.LoadFile(dll);
                    allAssemblies.Add(Assembly.LoadFile(dllPath));
                }

And I passed them to Register method and it registers successfully. But It can’t still resolve the service.

Jon P Smith
2 years ago
Reply to  Alok Babu

I don’t know, but the code is so small (see https://github.com/JonPSmith/NetCore.AutoRegisterDi/blob/master/NetCore.AutoRegisterDi/AutoRegisterHelpers.cs ) you could copy it and debug it.

Remember that the classes have to be public, not nested and the interfaces must be public too.

besartkuleta
besartkuleta
3 months ago

>>>Hi Jon, thank you for your time and effort… I just wanted to know
if we have a “Clean Architecture” approach; how to inject DbContext without referencing the Infrastructure. I’m using this approach but
simply it cant resolve the DbContext.

Thank you again and amazing article btw (y)

Jon P Smith
3 months ago
Reply to  besartkuleta

Hi besartkuleta,

I’m not sure what you mean by “inject DbContext without referencing the Infrastructure”. Do you mean without access to DI? That’s difficult.

Give me some clues and I’ll try to answer.

besartkuleta
besartkuleta
1 year ago
Reply to  Jon P Smith

Let’s say I have separated my project into three layers; WebApi, Core and Infrastructure.
The Api and Infrastructure have a reference of Core but they can’t have reference of each other.

So I want to inject DbContext (which stays in infrastructure) without giving a reference to WebApi. (hope I was clear)

Jon P Smith
1 year ago
Reply to  besartkuleta

OK, I see your problem. Basically you have to have the DbContext defined so that Core can access it, which throws your assembly structure out.

The first question is “why you you need to access the DbContext in the Core layer”, as that will tell you something about what your Core is doing. Does it access the classes mapped to the database as well? If it does then you need a assembly below Core holding the EF Core parts (I call this the DataLayer).

If you are only passing the DbContext through, then you can use the trick of using the generic DbContext type (you will need to the Microsoft.EntityFrameworkCore package, but you don’t need your real DbContext). Then in your DI setup you using something like this.

services.AddScoped(d => d.GetRequiredService() )

BUT that makes things a bit harder (you don’t have a strongly typed DbContext when passing to other methods) and your really need to think if this is better than having a lower DataLayer assembly.

Val Eriano
Val Eriano
3 months ago

” it’s so easy to forget to register a service, and your live application will fail when that service is used”

And how did you test your solution that it magically found the service when ran locally and in pre-production ?

Jon P Smith
3 months ago
Reply to  Val Eriano

In my unit tests I often add checks on the registering of services if I think there may be a problem. Have a look at the test in the NetCore.AutoRegisterDi test code – https://github.com/JonPSmith/NetCore.AutoRegisterDi/blob/master/Test/TestAutoRegisterDi.cs

Pretty simple, but effective.

Daniel Budde II
Daniel Budde II
3 months ago

Hello Jon,

I’m relatively new to C# .NET (MVC and Web API), but I have programmed in other languages for years using MVC and DI. So I understand concepts mostly well, but not necessarily how to accomplish things.

The main item I trying to figure out how to register are my DbContext classes. I am trying to figure out how I replace this next line with something using this project.

services.AddDbContext(options => options.UseMySQL(Configuration.GetConnectionString("DefaultConnection")));

I am using .NET Core, Entity Framework Core, and MySQL as my DB.

santosh kumar patro
santosh kumar patro
3 months ago

Thanks a lot @JonPSmith:disqus for wonderful article. I tried to implement in my application and came across an issue. I have submitted the issue at : https://stackoverflow.com/questions/59983697/some-services-are-not-able-to-be-constructed-using-the-library-netcore-autoreg

Can you please help me to get your thoughts on this issue

default 1990
default 1990
3 months ago

How do you deal with objects out of diffrent namespaces?
I have my controllers in a diffrent namespace, then my services.

The problem that i am having is that the where c only looks at classes in the namespace of the startup.
But my logic is placed in a diffrent namespace, and i can’t find anything that says how to solve this problem.

services.RegisterAssemblyPublicNonGenericClasses()
.Where(c => c.Name.EndsWith(“Service”))
.AsPublicImplementedInterfaces();

Jon P Smith
3 months ago
Reply to  default 1990

The code you show will register classes+interfaces in the assembly you are in covering all the namespaces in that assembly. So if you run it in Startup it will only look for classes+interfaces in your ASP.NET Core assembly.

If you need to register one or more other assemblies you can. I give one example in this article – see https://www.thereformedprogrammer.net/asp-net-core-fast-and-automatic-dependency-injection-setup/#an-approach-to-using-autoregisterdi-in-real-applications

Also have a look at the ReadMe file in the git repo – https://github.com/JonPSmith/NetCore.AutoRegisterDi/blob/master/READMe.md for more examples.

Omar
Omar
3 months ago

I like this tutorial, but it is hard to read unformatted code.

Jon P Smith
3 months ago
Reply to  Omar

Hi Omar,

Thanks for calling me out on that. I edited the article to say the library has been updated and the newer WordPress altered the code. I have now updated the code and also added more changes

Omar
Omar
3 months ago
Reply to  Jon P Smith

Ok Jon, thank you for your time and effort.