Before I discuss the ASP.NET Core dependency injection system, I feel it is important to take a bit of time to try to illustrate the problem that Dependency Injection is designed to solve. To do that, I have had to move the Empty template on a bit from where I left off in the last article. This has involved adding some standard MVC stuff to the project, including the HomeController and its associated Views, along with the site Javascript and CSS files from the Web Application template. I also had to add a couple of extra packages to the project to enable the serving of static files and to cater for the TagHelpers used in the views.
The Problem
A common pattern within web development - especially among beginners - is to pile a whole lot of code that does everything that a web page needs (data, emailing, error handling, logging, authentication etc) into one place. In the Microsoft world, this practice has evolved from spaghetti code in a classic ASP code file, to the code behind file for a web form, and then to a controller in an MVC application. Take a look at the following:
[HttpPost] public async Task<ActionResult> Contact(ContactViewModel model) { using (var smtp = new SmtpClient("localhost")) { var mail = new MailMessage { Subject = model.Subject, From = new MailAddress(model.Email), Body = model.Message }; mail.To.Add("[email protected]"); await smtp.SendMailAsync(mail); } return View(); }
This is an example of the type of code that can be found in many demo samples (including some on my site). It features a controller action method that responds to a POST
request and generates and sends an email message from the posted form values represented as a view model. So what's wrong with it?
Dependencies
The main problem is that the controller class has a responsibility for something which is not part of its primary concern. The code that manages the emailing is a dependency of the controller class. This potentially introduces the following problems:
- If you want to change your emailing system over to e.g. Exchange Web Services, you have to make changes to the controller class . This violates the Single Responsibility Principal. The controller class should only be responsible for martialling data for views. It should not be responsible for sending email. Changing the code concerning one of the controller class's responsibilities could result in bugs being introduced into its other responsibilities.
- If you have similar emailing functionality in multiple places in the application, you have to make the same change multiple times. The more places you have to make changes in, the more chance there is of you introducing other bugs to other parts of the application - or overlooking one or more areas where the change needs to be applied.
- If you want to unit test your controller's
Contact
method (to ensure it returns the correct View, for example), you will not be able to do so without generating an email. This will require communication with outside processes, which will slow tests down. - Your test may fail because of some failure in the mail sending routine rather than any logical failure in view selection (which is what a unit test is supposed to cover).
Good design practice recommends that you create specific components or services to cover discrete and separate pieces of functionality (or separate your concerns), and then call upon these services where needed. In this example, the code concerned with mailing should be separated out into its own component which will expose a public method that can be called from within the controller. This is how the code might be refactored to create a separate class called EmailSender
, which is responsible for the actual sending of the email:
using System.Net.Mail; using System.Threading.Tasks; namespace WebApplication2.Services { public class EmailSender { public async Task SendEmailAsync(string email, string subject, string message) { using (var smtp = new SmtpClient("localhost")) { var mail = new MailMessage { Subject = subject, From = new MailAddress(email), Body = message }; mail.To.Add("[email protected]"); await smtp.SendMailAsync(mail); } } } }
Note that this is purely for illustration only. A real email component will have a lot of validation and error handling included. And this is how the EmailSender
component is consumed within the controller action:
[HttpPost] public async Task<ActionResult> Contact(ContactViewModel model) { EmailSender _emailSender = new EmailSender(); await _emailSender.SendEmailAsync(model.Email, model.Subject, model.Message); return View(); }
This is better, because now the controller has no knowledge of how emails are sent. As far as the controller is concerned, the EmailSender
class is a black box that encapsulates the details of how emails are generated and dispatched. All the controller class needs to know is that the EmailSender
class offers a SendEmailAsync
method that accepts some strings. If you want to change anything to do with sending emails, you change the EmailSender
class/component only. You don't have to change the controller code at all. This eliminates the possibility of new errors being introduced into the controller class when updates to emailing routines are being made. It also saves time as you don't have to make identical changes in any other places where email is sent.
But there is still a problem. The controller is still dependent on (tightly coupled with) a specific (or concrete) type of email component. When you test the controller action, an email will still get sent. You could makes changes to the code to replace the EmailSender
class with a MockEmailSender
class that doesn't actually do anything whenever you want to run your tests. However, having to do that in multiple places, and for all the other services that rely on outside processes (logging, data access etc.) every time you wanted to run your tests, and then reversing all the changes afterwards is not a practical solution. The reality is that you are unlikely to bother to execute your tests at all.
Dependency Injection
Dependency injection (DI) is a design pattern. The problem it attempts to address is the one outlined above, where a class is tightly coupled to a specific implementation of a service. DI offers one way to loosen that coupling by having the service injected into the dependent class. You can do this in a number of ways. You can inject the service into the class's constructor or into one of its properties using the setter. Or, rather than inject a specific type of the service (EmailSender
or MockEmailSender
or ExchangeEmailSender
) you inject an abstraction that represents the service. The abstraction is defined in an interface. In this very simple example, the interface specifies one method:
public interface IEmailSender { Task SendEmailAsync(string email, string subject, string message); }
The IEmailSender
interface specifies a pattern (contract is another term for the same thing) that any type that wants to be seen as an IEmailSender
must conform to in order for it to comply. Currently, that means that the type must implement a SendEmailAsync
method that takes three strings as specified by the interface. The existing EmailSender
class already meets that requirement, so now it just needs to formally implement the interface to become an IEmailSender
. This is achieved by adding a colon after the class name followed by the name of the interface that it should implement:
public class EmailSender : IEmailSender { public async Task SendEmailAsync(string email, string subject, string message) { ....
The next iteration of the controller sees the EmailSender
class injected via the class constructor. It is saved to a private backing field for use wherever required within the controller:
public class HomeController : Controller { IEmailSender _emailSender; public HomeController(EmailSender emailSender) { _emailSender = emailSender; } .... [HttpPost] public async Task<ActionResult> Contact(ContactViewModel model) { await _emailSender.SendEmailAsync(model.Email, model.Subject, model.Message); return View(); } ....
The truth is that you can stop refactoring at this point. This is an example of dependency injection - known by the slightly pejorative term "poor man's dependency injection". The code within the controller follows the maxim "program to an interface", which decouples the controller from a specific implementation (apart from in its constructor), and enables separation of concerns. If you have no plans to unit test your code, and you can see no good reason for changing the implementation of your IEmailSender
at the moment (i.e. it is not part of the requirement spec to do so) then you can go on your merry way and this code is just fine. However, you might want to continue reading to find out how to further decouple the dependency, and to learn about the dependency injection system in ASP.NET Core.
The following code shows how you replace the concrete implementation of the EmailSender
class with the interface in the controller class, removing all dependencies on EmailSender
:
public class HomeController : Controller { IEmailSender _emailSender; public HomeController(IEmailSender emailSender) { _emailSender = emailSender; } .... [HttpPost] public async Task<ActionResult> Contact(ContactViewModel model) { await _emailSender.SendEmailAsync(model.Email, model.Subject, model.Message); return View(); } ....
A private field is added to the constructor class. The data type for the field is IEmailSender
, and it is used to store the instance of the IEmailSender
that is injected via the constructor that has also been added to the class. That instance is then referenced in the Contact
method as before. However, if you try to run the application at the moment, it will generate an exception
The exception is generated because, at the moment, there is no way for the application to know what concrete type IEmailSender
should be resolved to. You cannot instantiate an interface and start calling methods on it. It's an abstraction and the methods it defines have no implementation. You can see from the message above that the exception is generated by the new Dependency Injection framework, and it is this that needs to be told what to use whenever it encounters IEmailSender
. You do this by adding a registration to a dependency injection container.
DI Containers at their simplest are little more than dictionary-like structures that store the abstraction and the concrete type that should be invoked wherever the abstraction used. Most DI containers offer far more functionality than that, but, at their core, that's all they are - a kind of look-up table.
Dependency Injection is not new in ASP.NET MVC. People have been doing it for years and using a variety of third party DI containers to manage the the resolving of types. What is new in MVC 6 is that a very basic DI container is included as part of the framework. It is minimalistic and doesn't cover advanced use cases, but should be adequate for most common scenarios.
In the preceding tutorial, I registered MVC as a service with the DI container using the AddMvc
extension method in the Startup class's ConfigureServices
method. This is where you will generally register other services , either explicitly, or through extension methods. The following line of code shows how to register the EmailSender
class explicitly:
services.AddTransient<IEmailSender, EmailSender>();
This is the simplest method for adding services to the application - specifying the service as the first parameter and the implementation as the second. If you set a breakpoint on the ConfigureServices method and explore the services, you can see that a lot are already registered with varying lifetime values:
The EmailSender
was registered with Transient
scope via the AddTransient<TService, TImplementation>
method. Services registered with Transient
scope are created whenever it is needed within the application. That means that a new instance of the EmailSender
class will be created by the dependency injection framework every time the Contact
method is executed. Two other Lifetime values can be seen in the image: Singleton
and Scoped
. Singletons are registered via the AddSingleton
method and will result in one instance of the service being created on application start and being made available to all requests thereafter. Items added with a Scoped
lifetime via the AddScoped
method are available for the duration of the request. There is also an AddInstance
method that enables you to register a singleton, but you are responsible for creating the intance rather than leaving it to the DI system.
As I said previously, the built in dependency injection system is quite light on features. Each item has to be registered manually. It is expected that the developers of existing, more advanced dependency injection containers will undertake the work required to make their systems compatible with the requirements of ASP.NET Core to enable easy use of them instead of the default container.
Summary
This article began by taking a look at the need for dependency injection in an ASP.NET MVC application, and then explored the ASP.NET Core dependency injection system and covered the creation, registration and consumption of a simple service.