ASP.NET Core: Dependency Injection and Services in MVC

This is the fourth in a series of articles that explores ASP.NET Core by reconstructing the Visual Studio 2015 Web Application template from an Empty template. This article looks at the role of services and the new ASP.NET Core dependency injection system. The series of articles has been developed using Visual Studio RTM and ASP.NET Core Beta 6. It will be kept updated along with newer releases.

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:

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:

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:

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:

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:

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:

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.

You might also like...

13 Comments

07 October 2015 17:26 - Vaclav Elias

Hi Mike, this is very nicely explained and elaborated from non dependency to dependency on real example. I am not experienced with DI so have got question. You mentioned a few times "If you want to unit test your controller's Contact method.. ..you will not be able to do so without generating an email" but even after your last example you will still generate the email?

I guess at least you would change ONLY in one place services.AddTransient<IEmailSender, EmailSender>(); some dummy MockEmailSender which doesn't change email instead across whole project?

Thanks.

09 October 2015 14:19 - Mike

@Vaclav

Yes. Your test would use a mock. Then the controller code will execute the SendMailAsync call against something that does nothing.

22 October 2015 22:35 - Franco

Hi.

In the case of Onion architecture, would you recommend putting the DI resolution in a separate project, "in the edges of the onion"?
Like that, I can make references towards the center, since DI surely will act over the whole solution.

Thanks

24 October 2015 20:37 - Mike

@Franco,

I am not at all familiar with Onion Architecture.

26 October 2015 20:02 - Evan

Was this supposed to be in the Summary: This article has taken a closer look at configuration within an ASP.NET 5 application. It showed how to register a JSON file and how to reference a section of it in a strongly typed way. ???

I didn't quite follow what the difference between a poor man DI and using a DI container were.

26 October 2015 20:51 - Mike

@Evan,

No, that was not supposed to be included.

If you use poor man's DI, you inject concrete implementations of your service rather than abstractions (interfaces). If you want to change the implementation, you have to make changes to the constructor of the controller(s) where you injected it. If that's only on one or two places, usually there is no problem and the poor man's approach is perfectly acceptable.

Something like logging may be injected all over the app. It's much easier to change the implementation if you use a DI container. You simply change the type that the abstraction resolves to in the DI configuration.

15 November 2015 03:12 - João

Hi Mike!
Just wondering if .NET 5 DI supports name/key resolution.
ie, make one interface resolve two different classes according to a name/key on config or anywhere else.

Thanks!

19 November 2015 09:43 - Mike

@João

Not as far as I know.

04 December 2015 17:40 - chujanen

Thanks so much for posting this Mike. The way I would like to organize the code is to inject this service to the view model, but since the ASP.NET mode binder is handling it, it doesn't seem possible.

To morph your example... the ContactViewModel should have a constructor with a parameter of IEmailSender. That breaks things though because ASP.NET MVC won't map the postback to the action method Contact(ContactViewModel model) if there isn't a parameterless constructor for ContactViewModel.

Any ideas on how you would do such a thing?

I thought there would be two options (custom model binding or adding DI to the view model layer, but haven't found any good leads on those approaches).

Thanks!

04 December 2015 20:56 - Mike

@chujanen

I can't think of any good reason to inject the service into a viewmodel. I wouldn't do such a thing, or spend any time considering how it could be done.

10 January 2016 15:29 - Larry Watson

Mike,
Your blog is becoming 'goto' for me - thanks.
Most examples I see show DI in the web application. Suppose the web app has reference to a Service Layer which in turn has reference to a DAL layer with the context and repositories. Shouldn't the Service layer deal with its own DI for IRepository? For the web app to do this it has to have reference to the DAL layer which it should be ignorant of.s
In Asp.Net5 I am having difficulty enough on referencing these layers let alone manage DI in them.
Can you point me at anything that covers this?

Best Regards,

13 February 2016 11:31 - Ashish

Thank you very much Mike. Your article helped me a lot after searching for so long about DI.
Thanks again!

16 February 2016 18:04 - kapo

What a great article! Been starting to work with .NET again after more than 2 years & all this new DI management was giving me some headache. I've read quite a few articles about it before but none of them were so nicely put. Will forward anyone interested in this here. Thanks again!

Unfortunately, something went wrong and your message or comments have not been submitted
successfully. I'll try to fix whatever the problem is as soon as I can.

Thanks for your comments. They have been successfully sent to me. I will try to respond
if necessary as soon as I can.