Dependency Injection in ASP.NET CORE

Dependency Injection in ASP.NET CORE

The Dependency Injection pattern is a implementation of Inversion of Control. Inversion of Control (IoC) means that objects do not create other objects on which they rely to do their work. Instead, they get the objects that they need from an outside source (for example, an xml configuration file).

dependency injection

Dependency Injection in ASP.NET CORE

Before discussing about dependency injection system in ASP.net Core, it is important to take a bit of time to try to illustrate the PROBLEM that Dependency Injection is designed to solve.

Consider the below code snippet,

public class EmailService
{
public async Task SendEmail ( string email, string subject, string message)
{
using ( SmtpClient smtp = new SmtpClient ( “localhost” ))
{
MailMessage mail = new MailMessage  
{
Subject = subject, 
From = new MailAddress(email),
Body = message
};
mail.To.Add( “Application@domain.com” ); 
await smtp.SendMailAsync(mail); 
}
}

                                                 

[HttpPost]
public async Task<ActionResult> SendEmail( UserViewModel model )
{
EmailService _emailService = new EmailService();
await _ emailService.SendEmail( model.Email, model.Subject, model.Message );
return View();
}

The above code snippet contains a controller (POST) action method that sends an email message by creating object of EmailService class and invoking SendEmail method of it.

What’s wrong with it?

The controller is dependent on (tightly coupled with) a specific (or concrete) type of email component i.e., EmailService class. If you want to unit test your controller’s, SendEmail method using Unit Test Project, an email will get sent. You could make changes to the code to replace the EmailService class with a MockEmailService class that doesn’t 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.

The problem Dependency injection (DI) 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 several ways. You can inject the service into the class’s constructor or, rather than inject a specific type of the service (EmailService or MockEmailService) 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 IEmailService
{
Task SendEmail ( string email, string subject, string message );
}

The IEmailSevice interface specifies a pattern that any type that wants to be an IEmailService must conform to for it to comply. Currently, that means that the type must implement a SendEmail method as specified by the interface. The existing EmailService class already meets that requirement, so now it just needs to formally implement the interface to become an IEmailService.

public class EmailService : IEmailService
{
public async Task SendEmail ( string email, string subject, string message )

….

The next iteration of the controller sees the EmailService 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
{
IEmailService _emailService; 
public HomeController( EmailService emailService)
{
_emailService = emailService;
}
[HttpPost]
public async Task<ActionResult> SendEmail(UserViewModel model) 
{
await _emailService.SendEmail(model.Email, model.Subject, model.Message);
return View();
}

The above code decouples the controller from a specific implementation and enables separation of concerns.

A private field is added to the constructor class. The data type for the field is IEmailService, and it is used to store the instance of the IEmailService that is injected via the constructor that has also been added to the class. However, if you try to run the application now, it will generate an exception as

“Unable to resolve service for type ‘IEmailService’ while attempting to activate HomeControler.”

The exception is generated because, there is no way for the application to know what concrete type IEmailService should be resolved to. 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 IEmailService. 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.

In MVC 6, DI container is included as part of the framework.

Register 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 EmailService class explicitly:

services.AddTransient<IEmailService, EmailService >();

This is the simplest method for adding services to the application – specifying the service as the first parameter and the implementation as the second.

The EmailService is 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 EmailService class will be created by the dependency injection framework every time the SendEmail method is executed.

Two other Lifetime values can be seen: 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 instance rather than leaving it to the DI system.

The built-in dependency injection system is quite light on features. Each item must 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.

Privacy Preferences
When you visit our website, it may store information through your browser from specific services, usually in form of cookies. Here you can change your privacy preferences. Please note that blocking some types of cookies may impact your experience on our website and the services we offer.