Friday, May 19, 2017
Dependency Injection in ASP.NET Core
The Dependency Injection concept became an essential practice when it comes to writing loosely coupled code, and in the ASP.NET Core, it is now available out of the box. The framework includes a simple built-in Dependency Injection container which enables building dependencies without using the third-party library. If you’re not familiar with the Dependency Injection concept, you can read more about it here.
How it works
Let’s say our HomeController
needs to contain some logic we want to separate into a HomeService
. First, we will declare an IHomeService
interface with defined method signatures.
using MVC6Demo.Models;
namespace MVC6Demo.Services
{
public interface IHomeService
{
ICompany GetCompany();
IEnumerable<string> GetProjects();
}
}
For demonstration purposes, the service will have two methods: GetCompany
, with ICompany
as a return object, and GetProjects
which returns a list of strings. The ICompany
interface is declared as follows:
namespace MVC6Demo.Models
{
public interface ICompany
{
string Title { get; set; }
string Description { get; set; }
}
}
ICompany
interface needs implementation, so we will declare a simple model object Company
with its constructor:
namespace MVC6Demo.Models
{
public class Company : ICompany
{
public Company(string title, string description)
{
Title = title;
Description = description;
}
public string Title { get; set; }
public string Description { get; set; }
}
}
Next thing to do is to create a HomeService
that will implement IHomeService
. To keep things simple, we will instantiate dummy data in both, GetCompany
and GetProjects
methods, and return it.
using MVC6Demo.Models;
namespace MVC6Demo.Services
{
public class HomeService : IHomeService
{
public ICompany GetCompany()
{
var company = new Company("MVC6Demo Company", "MVC6Demo Company for Dependency Injection demonstration.");
return company;
}
public IEnumerable<string> GetProjects()
{
return new List<string>() { "Project X", "Project Y", "Project Z" };
}
}
}
Once the services are defined, it is necessary to register the dependencies in ConfigureServices
method inside the Startup.cs
class, so ASP.NET can know about them. When declaring a service, in ASP.NET Core context, there are three kinds of object lifetimes to choose from: Singleton, Scoped, or Transient. It is important to understand how each of them works, so we know which one to use, in what situations.
Singleton
Declaring a service as a singleton will create an instance of HomeService
when IHomeService
is asked for the first time, and every other time a component asks for it, that single instance will be shared through the lifetime of the application.
services.AddSingleton<IHomeService, HomeService>();
Providing the implementation as a second type parameter will lazy-load the service the first time it is requested. Another option, but not a better one, would be to create a specific instance of the concrete implementation ourselves, inside the ConfigureServices
method.
services.AddSingleton<IHomeService>(new HomeService());
Singletons are useful for resources which are shared by the entire application and need to be managed. A logger is used as a classic example of a singleton. If we only use one log stream, e.g., console, all we need is one logger instance for our outputs.
Scoped
Scoped can be observed as a singleton but in the context of a single HTTP request. For example, when IHomeService
is asked for the first time, an instance of HomeService will be created, and every subsequent request for that object (as long as it’s within the same HTTP request) will receive the same instance of HomeService
. However, on the next HTTP request, a new instance will be created and used instead.
services.AddScoped<IHomeService, HomeService>();
Scoped services provide isolation from other requests that might be happening at the same time, so they are useful for maintaining some context or state during a given request. For example, scoped should be used for Entity Framework contexts as well as repositories that will make use of Entity Framework.
Transient
Declaring a service as a transient will create a brand new instance of HomeService every time a component asks for IHomeService
.
services.AddTransient<IHomeService, HomeService>();
The transient scope is the most commonly used, and it is the one we’ll use in our example, so our ConfigureServices
method looks as follows:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IHomeService, HomeService>();
// Adds services MVC requires.
services.AddMvc();
}
When services are registered, we can inject the service into the controller constructor using the constructor injection, and ASP.NET will know that HomeService
is the object to instantiate whenever IHomeService
is requested.
protected IHomeService HomeService { get; set; }
public HomeController(IHomeService homeService)
{
HomeService = homeService;
}
Notice that we injected the IHomeService
, so the controller is not tightly coupled to the concrete implementation HomeService
. Now we can simply use it in any controller method as follows:
public IActionResult About()
{
var company = HomeService.GetCompany();
return View(company);
}
Finally, in our About.cshtml
view, we can access the data like this:
@model MVC6Demo.Models.Company
<h2>@Model.Title</h2>
<p>@Model.Description</p>
Injecting into Views
The possibility of injection into views is a new thing that came with ASP.NET Core. It can be useful for view-specific services, like when the data is required only for populating view elements. For injecting a service into a view, there’s the @inject
directive which can be declared using the following syntax:
@inject <type> <name>
Therefore, this is how we would inject the IHomeService
into the About.cshtml
view, and access the earlier created project list:
@inject MVC6Demo.Services.IHomeService Service
<h3>Projects List</h3>
<ul>
@foreach (var name in Service.GetProjects())
{
<li>@name</li>
}
</ul>
Although cool, injection into views might not be suitable for most scenarios. Usually, it is better to pass data as models from the controller.
We can see that ASP.NET Core DI container is very simple to use, and can be a good choice for small applications, as well as learning purposes. It is also interesting to see how ASP.NET develops having DI concept in mind. However, the default container does not have the intention to replace other containers, such as Ninject, Unity, Autofac and others, which are still more suitable for larger applications, and more complex scenarios.