In this blog post, I talked about how to build a microservice using ASP.NET 5. While it was a trivial, contrived sample, it did show how easy it is to get started with ASP.NET 5 (did I mention this was on a Mac? That’s never going to get old).
Dependency Injection works on the Inversion of Control principle. Rather than using what has colloquially been referred to as “new glue” where every class creates instances of the classes on which they depend, you avoid this tight coupling by simply being explicit about the dependencies required for your class by listing them in your application’s constructor. This gives you a ton more flexibility, but, most importantly, it also makes it much easier to test your application’s classes in isolation without resorting to terrible, space-time-altering hacks to test things.
The old (“new glue”) way:
public MyConstructor() { subordinateClass1 = new SubordinateClass1(); subordinateClass2 = new SubordinateClass2(); }
The new (DI) way:
public MyConstructor(ISubordinate1 subordinate1, ISubordinate2 subordinate2) { this.subordinate1 = subordinate1; this.subordinate2 = subordinate2; }
Among the many advantages of the second way is that the class is usable both with and without a formal dependency injection framework, and is far more testable than the first option.
So, now that we’ve got a “why do we care about DI?” primer out of the way, let’s take a look at how this might be used to apply to an ASP.NET 5 application.
In the ConfigureServices method of our application startup, we can choose to add built-in services (like ASP.NET MVC, identity, security, entity framework, etc) or we can add services that we have created with one of 3 different lifetime configurations:
- Transient – Every time an instance of this interface is requested, the receiving object gets a new instance.
- Scoped – This is request scope, so the instance of the injected object will remain intact throughout the lifetime of a web request. This is a particularly useful lifetime.
- Singleton – A single instance of this object will be dispensed to all requesting injection points.
Let’s say that we want to replace the hacky controller-embedded code we put in the previous sample for the Get() method of our zombie controller with an actual zombie repository. If we did this the old way, we would new up an instance of the repository inside our controller, incur a tight coupling penalty, and then make it horribly difficult to test said controller. Using DI, we can modify our controller so it looks like this:
[Route("api/zombies")] public class ZombieController { private IZombieRepository repository; private IGlobalCounter counter; public ZombieController(IZombieRepository zombieRepository, IGlobalCounter globalCounter) { repository = zombieRepository; counter = globalCounter; } [HttpGet] public IEnumerable<Zombie> Get() { counter.Increment(); Console.WriteLine("count: " + counter.Count); return repository.ListAll(); } }
Now our controller’s dependencies are explicit in the constructor, and are satisfied by some external source, such as our testing framework or a DI framework. Nowhere in this controller will you find “new glue” or tight coupling to a particular implementation of these classes.
To register specific implementations and lifetimes of these objects, we can modify the ConfigureServices method of our Startup class as shown here:
public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(); services.AddScoped<IZombieRepository, ZombieRepository>(); services.AddSingleton<IGlobalCounter, GlobalCounter>(); }
When we run this application now, a new instance of ZombieRepository will be created for each HTTP request and, because it’s a hacked implementation, it just returns the same array as the previous blog post. In the console we will see a global counter continue to increment beyond the span of individual HTTP requests, and this value will continue to increment until the host process (e.g. Kestrel) is shut down.
While there are too many possibilities to list here, as you go forward building your ASP.NET 5 applications, you should always be asking yourself, “Could this be a service?” Any time you find yourself isolating some piece of functionality into a module, check to see whether it fits nicely into the services framework in ASP.NET. This isn’t just a third party extension point, remember that ASP.NET 5 is now a decoupled, modular thing and nearly all of ASP.NET’s own functionality is added to the core with this same services framework.