Dependency Injection in ASP.NET Core - Demystified

Posted by: Daniel Jimenez Garcia , on 3/14/2018, in Category ASP.NET Core
Views: 25821
Abstract: Brief introduction to Dependency Injection in ASP.NET Core, a comparison from ASP.NET MVC 5, followed by a look at the built-in dependency injection support in ASP.NET Core and using 3rd party containers like Autofac and StructureMap.

.NET Core and ASP.NET Core have been around for almost 2 years since its 1.0 release. With it came a new modern and multi-platform framework built from the ground up.

One of the design principles of ASP.NET Core was embracing dependency injection as a first-class citizen. It not only provides the necessary hooks and infrastructure to inject your dependencies, it is also the way framework objects are composed and wired together.

Are you keeping up with new developer technologies? Advance your IT career with our Free Developer magazines covering .NET Core, MVC, C#, Patterns, Azure, Angular, React, and more. Subscribe to the DotNetCurry (DNC) Magazine for FREE and download all previous, current and upcoming editions.

To achieve this vision, ASP.NET Core ships with a simple built-in dependency injection container. Not only that, but the container itself is another dependency abstracted behind the IServiceProvider interface, easily allowing for authors of fully-featured containers to be compatible with the framework and for users to replace the built-in container.

This article provides a brief introduction to the subject, followed by a look at the dependency injection support in ASP.NET Core and its built-in container. We will also wrap up the article by replacing the built-in container using two popular containers like Autofac and StructureMap.

Brief introduction to Dependency Injection

While most of you might have already come across dependency injection, let me do a quick recap to establish the context. This might also help new joiners to follow along.

Dependency Injection is nothing more than a pattern where classes or modules declare their dependencies for others to fulfil at runtime.

In object-oriented languages this means classes won’t instantiate themselves the dependencies they need, instead they will simply declare them for someone else to instantiate and provide (or inject). Constructor injection is the most common way of doing so, but is not the only one as you can also consider injecting using a property setter or as a method argument.

Let’s see some code using constructor injection:

class Greeter
{
    private readonly IMessageWriter writer;

    public Greeter (IMessageWriter writer)
    {
        this.writer = writer;
    }

    public void Greet()
    {
        writer.Write("Hello World!");
    }
}
interface IMessageWriter
{
    void Write(string message);
}

The Greeter class needs an instance of IMessageWriter but instead of creating an instance itself, it simply declares what it needs in its constructor and relies on someone else to provide that at runtime. So rather than doing this.writer = new MessageWriter() in its constructor, the dependency is injected as a constructor parameter.

See also how the dependency is declared using the IMessageWriter interface rather than a specific implementation class. This is important since now our Greeter class doesn’t depend on a specific writer. Any writer that implements such interface will work.

What we gain by following dependency injection is loosely coupled code. And loosely coupled code is key in achieving some desirable properties in your code:

  • Extensibility – makes is possible to add new functionality by simply providing a different implementation of the interface and provides a natural hook for patterns like decorators, adapters, composites, facades, etc
  • Testability - Code can be independently tested, and test mocks injected during tests.
  • Maintainability - Having code which is both extensible and testable goes at great lengths towards enabling maintainability!

Dependency Injection is also a key tool that helps you adhere to the SOLID principles (Yes, dependency injection is not one of the principles, but a tool that allows you to follow them!) Since they are all related to loosely coupled code, dependency injection helps in some form with all of them, but it is key in two of them:

  • Open-Closed principle. The interfaces and declaration of dependencies provide a natural hook to add new functionality to the system without modifying the existing code.
  • Dependency Inversion. Your higher-level modules (the ones implementing your important core business logic) can declare and control the interfaces they need, for lower level modules to implement.

One thing we haven’t seen in this example is the concrete implementation of IMessageWriter or where the actual instances are created. That is because from the point of view of the Greeter class, it’s someone else’s problem! This might feel hard to let go at first, so let’s complete the example assuming we are writing a simple hello world console application:

static void Main(string[] args)
{
    var greeter = new Greeter(new MessageWriter());
    greeter.Greet();

    Console.ReadKey();
}
class MessageWriter: IMessageWriter
{
    public void Write(string message) => Console.WriteLine(message);
}

Consider carefully the first line of the Program’s Main method:

var greeter = new Greeter(new MessageWriter()); 

This is considered our composition root, the place where we cannot keep declaring dependencies anymore and instead needs to create concrete instances. In such a simple example with only two objects involved, we have been able to manually do this. In more complex applications, the composition root might need to create and compose complex object graphs which is why we use dependency injection containers.

While dependency injection per se doesn’t require the usage of containers, managing the dependencies manually on any non-trivial application would be a huge headache. A container basically acts as a catalogue of all our dependencies, allowing the composition root to simply ask for a resolved object.

Of course, containers provide many other desirable features when following dependency injection like lifetime management, convention-based registration or interceptors to name a few.

Since this was never meant as a full discussion on the topic of dependency injection, we have reached the end of our introduction! For the ones who want to read more on the subject, there are plenty of resources out there, of which I will highlight Mark Seamann’s great book Dependency Injection in .NET (second edition in the works) and Martin Fowler’s excellent entry on the subject (from as early as 2004)

Dependency Injection in ASP.NET Core vs ASP.NET MVC 5

Dependency Injection support

Since its early versions, ASP.NET MVC supported dependency injection, however it was opt-in and until ASP.NET MVC 3 came and introduced the IDependencyResolver interface, its usage was somewhat limited to controllers and its dependencies through the IControllerFactory.

Note: ASP.NET MVC doesn’t support dependency injection out of the box and won’t be able to instantiate a controller with parameters. You need to manually configure DI in your application to make this possible.

The contract was this: whoever wants to use dependency injection can do so by replacing the default implementations of IDependencyResolver or IControllerFactory with one that knows how to resolve the required dependencies.

Over time, popular containers integrated with MVC and provided their own IDependencyResolver instances that you could then wire during application start. Code invoked during application start will look like this (this example uses Autofac and its IDependencyResolver implementation, AutofacDependencyResolver, but the same pattern was used for every other container):

// Create and configure an Autofac container  
var builder = new ContainerBuilder();

// Call builder methods to register dependencies
builder.RegisterControllers(typeof(MvcApplication).Assembly);
…

// Set the dependency resolver to be Autofac.
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

Limitations

It is important to look back and realize there wasn’t any built-in dependency injection container within the framework. If you wanted to use dependency injection with your controllers, you had to install a 3rd party container, updating your application startup code to configure it and replace the default IDependencyResolver (or worse, create your own implementation)

While overtime all the popular libraries supported MVC out of the box and converged on a very similar setup pattern during application startup, it was something that a developer would need to manually add to the project.

Another important consequence of dependency injection not being a first-class citizen was that framework dependencies themselves wouldn’t be available for dependency injection! For example, if you needed access to the HTTP context of the current request, you would need to use the HttpContext.Current static property whereas in ASP.NET Core the framework provides the IHttpContextAccessor interface which can be injected in your classes.

Finally, as soon as you went outside ASP.NET MVC and reached the rest of the ASP.NET framework, the dependency injection support would end! (Although to be fair, this became less and less of a problem as ASP.NET MVC gained support and started receiving its own libraries for logging, tracing, membership, authentication, etc)

The ASP.NET Core built-in container

Back on 2018, we are now dealing with version 2.1 of the ASP.NET Core framework which was designed with dependency injection being a first-class citizen from day one:

  • The framework comes with its own simple dependency injection container which provides basic functionality for registering and resolving dependencies
  • All the framework dependencies like routing, loggers, configuration, etc use dependency injection and are registered themselves within the dependency injection container

Open an existing ASP.NET Core application or create a new one, then take another look at the ConfigureServices method of the Startup class:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
}

The interface IServiceCollection simply represents the catalogue of dependencies, which you will see referred to as services within the ASP.NET Core code and documentation:

public interface IServiceCollection: 
IList<ServiceDescriptor>, 
ICollection<ServiceDescriptor>, 
IEnumerable<ServiceDescriptor>, IEnumerable
{
} 

This catalogue is simply a map between target dependencies like IArticlesRepository and the classes that actually implement those dependencies like ArticlesRepository.

Several extension methods defined in the Microsoft.Extensions.DependencyInjection namespace provide all the methods necessary to register the dependencies (or services) within an IServiceCollection like AddTransient, AddSingleton, etc. (We will learn about these methods later on).

Let’s keep exploring how the framework itself register the dependencies. It has become idiomatic to create extension methods that register all the dependencies required for a given feature with a single call. That is exactly what is going on with the AddMvc() call.

If you read through the source code, you will see inside the following:

var builder = services.AddMvcCore();
builder.AddApiExplorer();
builder.AddAuthorization();

Where each of these are extension methods themselves that add the dependencies required for each of those subsections of the MVC framework. If we dig more through the source code of AddMvcCore we will reach 100+ lines of registering services like:

services.TryAddSingleton<MvcMarkerService, MvcMarkerService>();
services.TryAddSingleton<ITypeActivatorCache, TypeActivatorCache>();
services.TryAddSingleton<IUrlHelperFactory, UrlHelperFactory>();

So as you can see, the framework really does register all its dependencies within the IServiceCollection. But that is just one part of the puzzle. In order to get a working application, dependencies also need to be resolved.

That is where the IServiceProvider comes into play, which in itself is a really simple interface that allows you to resolve a dependency:

public interface IServiceProvider
{
    object GetService(Type serviceType);
}

Complimented by very useful extensions that provide a simpler API as in GetRequiredService<IRepository>():

public static class ServiceProviderServiceExtensions
{
    public static IServiceScope CreateScope(this IServiceProvider provider);
    public static object GetRequiredService(this IServiceProvider provider, Type serviceType);
    public static T GetRequiredService<T>(this IServiceProvider provider);
    public static T GetService<T>(this IServiceProvider provider);
    public static IEnumerable<T> GetServices<T>(this IServiceProvider provider);
    public static IEnumerable<object> GetServices(this IServiceProvider provider, Type serviceType);
}

However, you would very rarely need to directly use the IServiceProvider directly. Instead you just need to make sure to use dependency injection, declaring dependencies in the constructor of your class and that your dependencies are registered within the IServiceCollection. The framework will then be able to resolve and inject your dependencies at runtime.

The built-in container is nothing more than the implementation of IServiceProvider in a way that understands the catalogue of dependencies in a given IServiceCollection!

Registering your own dependencies

We have seen how the framework itself registers its dependencies. Now let’s take a look at how to register your own dependencies. Consider a classic example where you create a controller that needs to use a repository:

private readonly IArticlesRepository articlesRepository;

public ArticlesController(IArticlesRepository articlesRepository)
{
    this.articlesRepository = articlesRepository;
}

By virtue of the routes and controller convention, the MVC framework will find your controller class. However, it won’t know how to resolve the IArticlesRepository dependency that is required by its constructor. This means we need to ensure this is added to the IServiceCollection catalogue of dependencies! The easiest way is to update the ConfigureServices of your Startup class and let the framework know about the dependency:

// Inject an ArticlesRepository whenever an IArticlesRepository is needed
services.AddTransient<IArticlesRepository, ArticlesRepository>();

That’s it, now the MVC framework can ask the IServiceProvider for an instance of your controller, along with an instance of the repository.

At this point you might be wondering what happens if ArticlesRepository itself has some dependency. Nothing changes, you just need to make sure the new dependency is registered within the IServiceCollection and the container will be able to recursively resolve the dependencies, building the object graph.

Lifetime Management

You might have noticed the usage of different methods to register dependencies like services.AddScoped or services.AddTransient. When registering your dependencies, you need to think about lifetime management and choose between one of these 3 options (if you use a 3rd party container, it might have additional lifetime options):

  • Transient. A new instance will be provided every time the dependency is resolved. This is the equivalent of calling new every time the dependency needs to be injected.
  • Scoped. The same instance will be provided every time the dependency is resolved within the same HTTP request. You can think of it as a singleton in the context of one request.
  • Singleton. The same one instance is provided every time the dependency is resolved. This is the same as implementing the singleton pattern.

Each of these lifetimes has several extension methods for IServiceCollection that let you register a dependency with that lifetime. The variants are the same for every lifetime:

// Register using generic types
services.AddTransient<IArticlesRepository, ArticlesRepository>();

// Register using System.Type
services.AddTransient(typeof(IArticlesRepository), typeof(ArticlesRepository));

// Register using generic target type and factory function
services.AddTransient<IArticlesRepository>((s) => {
    return new ArticlesRepository(s.GetRequiredService<ApplicationDbContext>());
});

// Register using Sytem.type target type and factory function
services.AddTransient(typeof(IArticlesRepository), (s) => {
    return new ArticlesRepository(s.GetRequiredService<ApplicationDbContext>());
});

All those statements will achieve the exact same result, which is registering the IArticlesRepository dependency using the transient lifetime and the implementation class ArticlesRepository. And very rarely would you need to use anything other than its first variant using the generic types!

Note: The most common exception being registering open generics as in services.AddTransient(typeof(IRepository<>), typeof(MyGenericRepository<>)); which saves you from individually having to register a dependency for each entity like services.AddTransient<Repository<Foo>, MyGenericRepository<Foo>>(); and services.AddTransient<Repository<Bar>, MyGenericRepository<Bar>>();

This is all great, your dependencies will be created for you using one of those lifetime options.

But what about cleaning those dependencies, and more specifically about calling Dispose in IDisposable dependencies? You will be glad to hear the framework will take care of calling the dispose method on any dependency that has been resolved once it is no longer needed.

Using other Dependency Injection containers

While having dependency injection as a first-class citizen in the project is great, on larger projects you might soon realize the built-in container doesn’t cover all your needs. Just to mention a couple of features these containers typically provide:

  • Convention based registration might save you from manually registering the likes of IService to Service
  • Interceptors might enable powerful aspect-oriented scenarios down the line.

There are already many dependency injection containers that support ASP.NET Core and the process to replace the built-in container is roughly the same on all of them. Within the ConfigureServices method you will need to:

1. Register the framework services into IServiceCollection as usual

2. Push the IServiceCollection with framework services into your container

3. create an instance of your preferred container

4. register your application specific dependencies directly within the container (here is where you take advantage of the container features)

5. Resolve an IServiceProvider from the container and use it as the return value from ConfigureServices (This method returns void when using the default container!)

Of course, the authors of the container should have added support for ASP.NET Core, otherwise the container won’t know what to do with the IServiceCollection nor how to implement an IServiceProvider.

Let’s take a look at a couple of popular examples next.

Autofac

Start by adding to your project the Autofac.Extensions.DependencyInjection Nuget package. This is the package that integrates Autofac with the .NET Core dependency injection interfaces.

Now let’s update the ConfigureServices method following the pattern described above:

using Autofac;
using Autofac.Extensions.DependencyInjection;

…

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    // Add framework services 
    …
    EF, Identity, Authentication, etc
    …
    services.AddMvc();
    
    // Create an Autofac Container and push the framework services
    var containerBuilder = new ContainerBuilder();            
    containerBuilder.Populate(services);

    // Register your own services within Autofac
    containerBuilder.RegisterType<ArticlesRepository>().As<IArticlesRepository>();
    …

    // Build the container and return an IServiceProvider from Autofac
    var container = containerBuilder.Build();
    return container.Resolve<IServiceProvider>();            
}

That’s it, now you can start using Autofac’s features in your project!

StructureMap

The process is pretty much the same than with Autofac. Start by installing the StructureMap.Microsoft.DependencyInjection NuGet package.

Then update the ConfigureServices method in a very similar fashion to what we did for Autofac:

using StructureMap;

…

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    // Add framework services 
    …
    EF, Identity, Authentication, etc
    …
    services.AddMvc();
    
    // Create an StructureMap Container
    var container = new Container();
    container.Configure(c =>
    {
        // Push the framework services
        c.Populate(services);

        // Register your own services
        // NOTE: StructureMap recommends you to use registry classes!
        c.For<IArticlesRepository>().Use<ArticlesRepository>();
    });

    // Return an IServiceProvider from StructureMap
    return container.GetInstance<IServiceProvider>();            
}

As you can see it is essentially the same code using StructureMap’s API instead of the Autofac one.

Conclusion

Dependency Injection is a critical pattern that every developer writing object-oriented code should learn. But how easy it is to put it into practice depends a lot on the framework you are using.

As we have seen through the article, ASP.NET Core has been designed with dependency injection in mind and even its own simple container out of the box. This means there are no obstacles to use dependency injection and create loosely coupled code from day one.

Not only that, dependency injection itself has been abstracted by the framework in a way that makes replacing the built-in container, seamless. Provided your container of choice has implemented the required interfaces, you can replace the built-in container in a few minutes.

If it wasn’t already, this should make dependency injection second nature for ASP.NET Core developers!

This article was technically reviewed by Ravi Kiran.

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
Daniel Jimenez Garcia is a passionate software developer with 10+ years of experience. He started as a Microsoft developer and learned to love C# in general and ASP.NET MVC in particular. In the latter half of his career he worked on a broader set of technologies and platforms while these days he is particularly interested in .Net Core and Node.js. He is always looking for better practices and can be seen answering questions on Stack Overflow.


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!

Categories

JOIN OUR COMMUNITY

POPULAR ARTICLES

FREE .NET MAGAZINES

Free DNC .NET Magazine

Tags

JQUERY COOKBOOK

jQuery CookBook