Dependency Injection (DI) is a pattern that can help developers decouple the different pieces of their applications. It provides a mechanism for the construction of dependency graphs independent of the class definitions. Throughout this article, I will be focusing on constructor injection where dependencies are provided to consumers through their constructors.
It’s also important to mention two other injection methods:
Consider the following classes:
class Bar : IBar { // ... } class Foo { private readonly IBar _bar; public Foo(IBar bar) { _bar = bar; } }
In this example, Foo depends on IBar and somewhere we’ll have to construct an instance of Foo and specify that it depends on the implementation Bar like so:
var bar = new Bar(); var foo = new Foo(bar);
The problem with this is two-fold. Firstly, it violates the Dependency Inversion Principle because the consuming class implicitly depends on the concrete types Bar and Foo. Secondly, it results in a scattered definition of the dependency graph and can make unit testing very difficult.
The Composition Root pattern states that the entire dependency graph should be composed in a single location “as close as possible to the application’s entry point”. This could get pretty messy without the assistance of a framework. DI frameworks provide a mechanism, often referred to as an Inversion of Control (IoC) Container, for offloading the instantiation, injection, and lifetime management of dependencies to the framework. You invert the control of component instantiation from the consumers to the container, hence “Inversion of Control”.
To do this, you simply register services with a container, and then you can load the top level service. The framework will inject all child services for you. A simple example, based on the class definitions above, might look like:
container.Register<Bar>().As<IBar>(); container.Register<Foo>(); // per the Composition Root pattern, this _should_ be the only lookup on the container var foo = container.Get<Foo>();
Prior to .Net Core, the only way to get DI in your applications was through the use of a framework such as Autofac, Ninject, StructureMap and many others. However, DI is treated as a first-class citizen in ASP.Net Core. You can configure your container in your Startup.ConfigureServices method:
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<IArticleService, ArticleService>(); } // ... }
When a request gets routed to your controller, it will be resolved from the container along with all its dependencies:
public class ArticlesController : Controller { private readonly IArticleService _articleService; public ArticlesController(IArticleService articleService) { _articleService = articleService; } [HttpGet("{id}"] public async Task<IActionResult> GetAsync(int id) { var article = await _articleService.GetAsync(id); if(article == null) return NotFound(); return Ok(article); } }
In the context of .NET Core DI, you’ll often hear or read the terms singleton, transient, and scoped. These are dependency lifetimes.
At registration time, dependencies require a lifetime definition. The service lifetime defines the conditions under which a new service instance will be created. Below are the lifetimes defined by the ASP.Net DI framework. The terminology may be different if you choose to use a different framework.
If you would like to use a more mature DI framework, you can do so as long as they provide an IServiceProvider implementation. If they don’t provide one, it is a very simple interface that you should be able to implement yourself. You would just return an instance of the container in your ConfigureServices method. Here is an example using Autofac:
public class Startup { public IServiceProvider ConfigureServices(IServiceCollection services) { // setup the Autofac container var builder = new ContainerBuilder(); builder.Populate(services); builder.RegisterType<ArticleService>().As<IArticleService>(); var container = builder.Build(); // return the IServiceProvider implementation return new AutofacServiceProvider(container); } // ... }
Dependency injection can get really interesting when you start working with generics. Most DI providers allow you to register open generic types that will have their generic arguments set based on the requested generic type arguments. A great example of this is Microsoft’s new logging framework (Microsoft.Extensions.Logging). If you look under the hood you can see how they inject the open generic ILogger<>:
services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>)));
This allows you to depend on the generic ILogger<> like so:
public class Foo { public Foo(ILogger<Foo> logger) { logger.LogInformation("Constructed!!!"); } }
Another common use case is the Generic Repository Pattern. Some consider this an anti-pattern when used with an ORM like Entity Framework because it already implements the Repository Pattern. But, if you’re unfamiliar with DI and generics, I think it provides an easy entry point.
Open generic injection also provides a great mechanism for libraries (such as JsonApiDotNetCore) to offer default behaviors with easy extensibility for applications. Suppose a framework provides an out-of-the-box, implementation of the generic repository pattern. It may have an interface that looks like this, implemented by a GenericRepository:
public interface IRepository<T> where T : IIdentifiable { T Get(int id); }
The library would provide some IServiceCollection extension method like:
public static void AddDefaultRepositories(this IServiceCollection services) { services.TryAdd(ServiceDescriptor.Scoped(typeof(IRepository<>), typeof(GenericRepository<>))); }
And the default behavior could be supplemented by the application on a per resource basis by injecting a more specific type:
services.AddScoped<IRepository<Foo>, FooRepository>();
And of course FooRepository can inherit from GenericRepository<>.
class FooRepository : GenericRepository<Foo> { Foo Get(int id) { var foo = base.Get(id); // ...authorization of resources or any other application concerns can go here return foo; } }
The ASP.Net team has separated their DI framework from the ASP.Net packages into Microsoft.Extensions.DependencyInjection. What this means is that you are not limited to web apps and can leverage these new libraries in event-driven apps (such as Azure Functions and AWS Lambda) or in thread loop apps. All you need to do is:
Install-Package Microsoft.Extensions.DependencyInjection or dotnet add package Microsoft.Extensions.DependencyInjection
var serviceCollection = new ServiceCollection();
serviceCollection.AddScoped<IEmailSender, AuthMessageSender>();
serviceCollection.AddScoped<AzureFunctionEventProcessor, IEventProcessor>();
Container = serviceCollection.BuildServiceProvider();
var serviceScopeFactory = Container.GetRequiredService<IServiceScopeFactory>(); using (var scope = serviceScopeFactory.CreateScope()) { var processor = scope.ServiceProvider.GetService<IEventProcessor>(); processor.Handle(theEvent); }
Under the hood, the call to .BuildServiceProvider() will inject an IServiceScopeFactory. You can load this service and define a scope so you can use properly scoped services.
If a registered service implements IDisposable it will be disposed of when the containing scope is disposed. You can see how this is done here. For this reason, it is important to always resolve services from a scope and not the root container, as described above. If you resolve IDisposables from the root container, you may create a memory leak since these services will not be disposed of until the container gets disposed.
Some DI providers provide resolution time hooks that allow you to make runtime decisions about dependency injection. For example, Autofac provides an AttachToComponentRegistration method that can be used to make runtime decisions. At Stackify, we used this with Azure Functions to wrap the TraceWriter (before they supported the ILogger interface) behind a facade. This facade passed the logging method calls to the scoped TraceWriter instance as well as our log4net logger. To do this, we register the instance of the TraceWriter when we begin the lifetime scope:
using (var scope = ServiceProvider.BeginLifetimeScope(b => b.RegisterInstance(traceWriter))) { // ... }
I’ve created a gist here that you can reference if you’d like to see the rest of the implementation.
In general, IoC containers are an application concern. What this means is library and framework authors should think carefully about whether or not it is really necessary to create an IoC container within the package itself. An example of one that does this is the AspNetCore.Mvc framework packages. However, this framework is intended to manage the life of the application itself. This is very different than say a logging framework.
Dependency Injection describes the pattern of passing dependencies to consuming services at instantiation. DI frameworks provide IoC containers that allow developers to offload control of this process to the framework. This lets us decouple our modules from their concrete dependencies, improving testability and extensibility of our applications.
Hope this article was helpful. I often reference what we do here at Stackify and wanted to share that we also use our own tools in house to continually improve our applications. Both our free dynamic code profile, Stackify Prefix, and our full lifecycle APM, Stackify Retrace, help us make sure we are providing our clients with the best value.
[x_alert heading=”Note” type=”info”]All of the source code links used in this article are permalinks to the code on the default repository branches. These links should be used as a reference and not as the current state of the underlying implementations or APIs since these are subject to change at any time.[/x_alert]
If you would like to be a guest contributor to the Stackify blog please reach out to [email protected]