Dependency Injection in ASP.NET Core

Dependency Injection is an object-oriented programming design pattern that allows the creation of loosely coupled code. It allows the creation and binding of dependent objects outside of the class that depends on them. An interface can be used to break the dependencies between the class that implement the service and the class that uses it. The purpose of dependency injection is to make the code maintainable and easy to update.

Instantiating the service class

The code in this article simulate the creation of a user account and use the username and password as parameters to create an email account. This application simply returns a confirmation message has if the objects were really created. The main class depend on a service class to create a mail account. Later we use an interface, and make sure the main class is aware of the abstraction rather than instantiating the service class directly. This way, we can change the implementation of the interface at any time without changing the code in the main class that depends on it.

In the example below, the mail service is provided in a traditional way meaning we instantiate the Google service directly in the main class that will be using it.

Index.cshtml.cs
     
private UserAccount _useraccount;
private GoogleMailService _googlemailservice;

public IndexModel()
{
    _useraccount = new UserAccount();
    _googlemailservice = new GoogleMailService();
}

public void OnGet()
{
    _useraccount.Username = "bruce.wayne";
    _useraccount.Password = "Batman";

    EmailConfirmation = _googlemailservice.CreateMailAccount(_useraccount.Username, _useraccount.Password);
}
GoogleMailService.cs
           
public class GoogleMailService
{
    public string CreateMailAccount(string username, string password)
    {
        var domain = "@gmail.com";
        return $"\nEmail \"{username}{domain}\" with password \"{password}\" has been created successfully.\n";
    }
}
UserAccount.cs
public class UserAccount
{
    public string Username { get; set; }
    public string Password { get; set; }
}

Here we get the confirmation message from our model page as if we were creating a Google mail account. It doesn't matter if a real account gets created at this point, we simply need a basic class to apply the dependency later.

This would work but it is not the right way to do things because a lot of code would have to be modified if a new mail service was to be implemented. The next section will show an example.

Implement a new mail service

Looking at the mail service we used to create the mail account, we can see that using a different service would imply modifying codes in multiple locations. Changing all that code is quite simple in a small application like this but can become an issue when a service is used in multiple parts of a large project.

For instance, when the mail service is replaced with Outlook, as shown below, the tightly coupled service also need to be changed everywhere it is used in the application.

Index.cshtml.cs
private UserAccount _useraccount;
private OutlookMailService _outlookmailservice;

public IndexModel()
{
    _useraccount = new UserAccount();
    _outlookmailservice = new OutlookMailService();
}

public void OnGet()
{
    _useraccount.Username = "bruce.wayne";
    _useraccount.Password = "Batman";

    EmailConfirmation = _outlookmailservice.CreateMailAccount(username, password);
}
OutlookMailService.cs
public class OutlookMailService
{
    public string CreateMailAccount(string username, string password)
    {
        var domain = "@outlook.com";
        return $"\nEmail \"{username}{domain}\" with password \"{password}\" has been created successfully.\n";
    }
}

Now the confirmation message indicates the email account was created using Outlook. The new service is working but too much code needed to be changed inside the main class. The best way to make this work would be to create a dependency outside the class that uses it.

Constructor Injection

Looking at both mail services, we can see that there is a generic method that both services provide. The method that creates a mail account can provide an abstraction of the mail service, which is going to be used even when we do not know which exact service (Google or Outlook) is to be used.

First, we need an interface that will defines the method that both services provide to the application.

IEmailService.cs
public interface IEmailService
{
    public string CreateMailAccount(string username, string password);
}

Then the corresponding services (classes) must inherit from that interface.

GoogleMailService.cs
public class GoogleMailService : IEmailService
{
    public string CreateMailAccount(string username, string password)
    {
        var domain = "@gmail.com";
        return $"\nEmail \"{username}{domain}\" with password \"{password}\" has been created successfully.\n";
    }
}
OutlookMailService.cs
public class OutlookMailService : IEmailService
{
    public string CreateMailAccount(string username, string password)
    {
        var domain = "@outlook.com";
        return $"\nEmail \"{username}{domain}\" with password \"{password}\" has been created successfully.\n";
    }
}

The abstraction can now be used in the main class. We can inject it using a type of dependency injection known as constructor injection. Constructor injection is done by supplying the dependency through the class constructor.

Index.cshtml.cs
public class IndexModel : PageModel
{
	private UserAccount _useraccount;
    private IEmailService _emailservice;

    public IndexModel(IEmailService emailservice)
    {
        _useraccount = new UserAccount();
        _emailservice = emailservice;
    }

    public string CreateNewEmail(string username, string password)
    {
        return _emailservice.CreateMailAccount(username, password);
    }
}

The code that is calling the main class would have to specify what mail service is going to be used and pass it to the constructor when instantiating the service class.

Index.cshtml.cs
public void OnGet()
{
    _useraccount.Username = "bruce.wayne";
    _useraccount.Password = "Batman";

    GoogleMailService googleMailService = new GoogleMailService();
    IndexModel indexModel = new IndexModel(googleMailService);

    EmailConfirmation = indexModel.CreateNewEmail(_useraccount.Username, _useraccount.Password);
}

Finally, IMailService must be registered in the service container inside the ConfigureServices method of the Startup class with the concrete type GoogleMailService and OutlookMailService. Singleton services are created only once per application and then used for the entire application lifetime.

Startup.cs
            
public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IEmailService, GoogleMailService>();
    services.AddSingleton<IEmailService, OutlookMailService>();
}

Because the binding takes place outside of the main class, the code is now much more maintainable. Switching to Outlook now only requires instantiating that service and pass it to the main class constructor.

Index.cshtml.cs
public void OnGet()
{
    _useraccount.Username = "bruce.wayne";
    _useraccount.Password = "Batman";

    OutlookMailService outlookMailService = new OutlookMailService();
    IndexModel indexModel = new IndexModel(outlookMailService);

    EmailConfirmation = indexModel.CreateNewEmail(_useraccount.Username, _useraccount.Password);
}

Setter Injection

The abstraction can also be injected using another type of dependency injection called setter injection. Setter or property injection is done by supplying the dependency through the property set accessor.

Index.cshtml.cs
public class IndexModel : PageModel
{
    private IEmailService _emailservice;
    public IEmailService EmailService 
    {
        get {
                return _emailservice;
            }
        set {
                _emailservice = value;
            }
    }
}

The code that is calling the main class would have to specify what mail service is going to be used and pass it to the property.

Index.cshtml.cs
public void OnGet()
{
    _useraccount.Username = "bruce.wayne";
    _useraccount.Password = "Batman";

    GoogleMailService googleMailService = new GoogleMailService();
    EmailService = googleMailService;

    EmailConfirmation = EmailService.CreateMailAccount(_useraccount.Username, _useraccount.Password);
}

Method Injection

The abstraction can also be injected using another type of dependency injection called method injection. Method injection is done by injecting the dependency into a single method and generally for the use of that method.

Index.cshtml.cs
public class IndexModel : PageModel
{
    public string CreateNewEmail(IEmailService emailservice, string username, string password)
    {
        return emailservice.CreateMailAccount(username, password);
    }
}

The code that is calling the main class would have to specify what mail service is going to be used and pass it to the method.

Index.cshtml.cs
public void OnGet()
{
    GoogleMailService googleMailService = new GoogleMailService();
    IndexModel indexModel = new IndexModel();

    EmailConfirmation = indexModel.CreateNewEmail(googleMailService, _useraccount.Username, _useraccount.Password);
}

Summary

Dependency Injection help reduce class coupling. Class coupling is a measure on how a class is dependent with another class. It’s best to implement fewer dependence among classes to reduce the complexity of your application. Also, less dependent classes will make unit testing possible. In order to implement unit testing, codes need to be modular so having less interdependent code to test with, the unintended impact of modifying your application will be reduced.

Eric Lacroix picture

By January 15, 2020

I’ve been working in IT as a technical support technician and quality assurance analyst for 20 years. I completed high school with a passion for computer technologies and that passion followed me up until today.