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.

More articles

Your opinion matters!

comments powered by Disqus