X

Easy way to resolve dependency injection in ASP.Net Core

Dung Do Tien Jun 28 2020 2241
Dependency injection is an important technique in application programming in general and in asp.net core in particular. Dependency injection helps reduce the dependence of classes on each other while initializing them. Initializing instances of classes maybe only once for each request or when initiating the application, it helps make the short code and more maintainable.

Dependency injection in asp.net core is not a new concept, in the above version of the asp.net framework you also apply Dependency injection into your project but you have to use a library of third-party for implementation.

1. Why do we need to resolve dependency injection?

To explain the problem of dependency injection in asp.net core, I provide an example below:

I need to create an application to manage the students of a class in the school. We have many actors such as Student, Class, School… 
Now I will create an interface of Student actor and a class to implement this interface (this is a class in the data access layer - DAL).

Interface IStudentDal

public interface IStudentDal
{
    List<Student> GetAll();
    Student GetById();
    Student GetByName();
}

Implement class

public class StudentDal : IStudentDal
{
    private static List<Student> lstStudent = new List<Student>() {
           new Student(){ Id = 1, Email ="test1@gmail.com", FullName ="Test 1" },
           new Student(){ Id = 2, Email ="test2@gmail.com", FullName ="Test 2" },
           new Student(){ Id = 3, Email ="test3@gmail.com", FullName ="Test 3" },
           new Student(){ Id = 4, Email ="test4@gmail.com", FullName ="Test 4" }
        };

    public List<Student> GetAll()
    {
        return lstStudent;
    }

    public Student GetById(int id)
    {
        return lstStudent.FirstOrDefault(x => x.Id == id);
    }

    public Student GetByName(string name)
    {
       return lstStudent.FirstOrDefault(x => x.FullName.Equals(name));
    }
}

Now we need to create an interface and an implement the class for Student actor in the business layer(BSL) as below :

Interface IStudentBsl

public interface IStudentBsl
{
    List<Student> GetAll();
    Student GetById(int id);
    Student GetByName(string name);
}

Implement class

public class StudentBsl : IStudentBsl
{
    public List<Student> GetAll()
    {
        throw new NotImplementedException();
    }

    public Student GetById(int id)
    {
        throw new NotImplementedException();
    }

    public Student GetByName(string name)
    {
        throw new NotImplementedException();
    }
}

Now the method  GetAll() of StudentBsl class needs to call the method GetAll() of StudentDal class to return the list data of students' information. How can I do it?
In normal we usually use 2 ways below to call:

Way 1: Initial StudentDal class from every method if that method gets info ex:

public List<Student> GetAll()
{
    IStudentDal studentDal = new StudentDal();
    var lstStudent = studentDal.GetAll();
    return lstStudent;
}

Way 2: Initial StudentDal class in constructor of class and use for all method ex :

public class StudentBsl : IStudentBsl
{
    private IStudentDal _studentDal;

    public StudentBsl()
    {
        _studentDal = new StudentDal();
    }

    public List<Student> GetAll()
    {
        var lstStudent = _studentDal.GetAll();
        return lstStudent;
    }
}

Uhm hum, you can see the way 2 is better than because you no need to instance class many times. But both ways have the common problem, they have to instance StudentDal class in many places if you have a lot of classes that need to use the StudentDal class, this class will be instantiated at least once in those classes.

And if this class changes the business and needs to change the constructor's parameters, what will happen? You will have to update code in many places. If your site is big I think it very difficult to maintain. This is called dependency injection in asp.net core. So how to resolve this problem? 

2. How to resolve Dependency injection?

Before resolving this problem we need to understand about Inversion of Control(IoC).

2.1 What is Inversion of Control(IoC)?

You can understand the short and simple way that IoC is a programming principle, it helps automatically initialize all instances of classes or interfaces. You just declare the classes, interfaces need to use and IoC will auto inject all instances for them.

Maybe you will feel difficult to understand. If I provide an example you will easily understand but It only is a principle. I will provide some examples when implementing IoC below.

2.2. What is the advantage and disadvantage of IoC?

2.2.1.  Advantage

- Making it easier to switch between different implementations
- Easy maintenance.
- Easy write unit test.
- Decouples the application.
- It is only slow for the first visitor and then it is fast for all (if you use singleton).
- Short code.
- Reduced Dependencies.
- Readable Code.

2.2.2. Disadvantage 

- Difficult to trace code
- Increases complexity in the linkages between classes. It may become harder to manage such complexity outside the implementation of a class.

2.3. How to implement IoC?

We have many ways to implement IoC, you can use DI, Service Locator, Delegates, Events…. The most popular way is to use DI(Dependency Injection) to implement IoC.

 

2.4. How to achieve the Dependency Injection?

We have many ways to get Dependency Injection (DI). We can use some DI container like Unity container, Ninject, AutoFac, StructureMap…. 

2.5. Dependency injection lifetime

Service lifetime is very important because we can determine when a class will be instances and destroy. Microsoft introduced three services lifetime for you choose:

2.5.1. Transient 

Transient lifetime services are created each time they're requested from the service container. This lifetime works best for lightweight, stateless services. So you can understand that a class can be instanced many times for one request if you have many components call that class.

* Note: You need to consider when to use this lifetime because if you use it many times in a request maybe it makes the decrease time load page.

2.5.2. Scoped

Scoped lifetime services are created once per client request. In web apps process requests, scoped services are disposed of at the end of the request.

2.5.3. Singleton

Singleton lifetime services are created once when the application starts, it only destroys when that application stops.

2.5.4 When we should use Transient, Scoped and Singleton???

I take three cases as below:

Case 1: In the Home page of QuizDev blog, In this page we have about 7 view components and they also use to IArticleService service to get data.

Case 2: Also in the Home page of QuizDev blog but I need count visitor. I create ICountVisitorService to help increase by one when have more visitor visit this page.

Case 3: Related to Member login of QuizDev blog, Member has more infomation is work history. After login, go to manager profile and click work history button you will see a popup will some info. I created IWorkHistoryService to help manage the work history of member. Note that members rarely use this feature.

Okay, now we discuss together.

+ With case 1:  

-> Use Transient : You will see that IArticleService service has to initial many times. If visitor traffic is enough big your server will be down. Because Ram or CPU is full. In an other my project when CCU(concurrent user) about 3k CPU almost full with ram 8gb & cpu 2 cores. I know it's also depend on many other factors.

-> Use Scoped:  In this method the IArticleService service only initial once time for a visitor. But if CCU is 27k maybe your server still down.

-> Use Singleton: hmm I think this is a good option because service only initial once time. But It's will increase ram in all other pages although other pages no need for this service but it very small.

Okay in this case you can consider Scoped or Singleton.

+ With case 2:

-> Use Transient : No problem but follow theory the ICountVisitorService service is not a stateless service.

-> Use Scoped:  Very good, Scoped initial by request so we will initial for each visitor to increase count view.

-> Use Singleton:  No, we can't use this lifetime because it only initial once time so from second user count view will not increase.

+ With case 3: 

-> Use Transient : Wow this good option because this function very rarely use so we can initial that service and dispose right now. It helps reduce ram memory.

-> Use Scoped: No problem

-> Use Singleton:  No problem but it make increase ram so I do not recommend this lifetime.

* SUMMARY:

From 3 cases above we can see that anything service has frequency use large, we have to use Singleton. Anything service needs to change or reload something information by request you can use to Scoped. And anything service rarely uses you can use the Transient lifetime.

2.6. Setup DI in Asp.Net Core web application

2.6.1 Register class or interface to DI container

In Asp.net core web application, Microsoft has provided DI default, all ready to use.
You can register DI In method ConfigureServices() of Startup.cs file, IServiceCollection interface looks like a DI container, In this interface provides three methods to help you register DI lifetime for your class or interface.
- AddTransient(): It is a transient lifetime service.

services.AddTransient<IMyInterface, MyImplement>();

- AddScoped(): It is a scoped lifetime service.

services.AddScoped<IMyInterface, MyImplement>();

- AddSingleton(): It is a singleton lifetime service.

services.AddSingleton<IMyInterface, MyImplement>();

We have three ways to implement DI, that is through Constructor, Property and Method. In this article, I will use the Constructor method to resolve dependency injection.
Back to the above example, I will make a constructor for StudentBsl class and initial IStudentDal class here. You can see bellow code:

public class StudentBsl : IStudentBsl
{
    private IStudentDal _studentDal;

    public StudentBsl(IStudentDal studentDal)
    {
        _studentDal = studentDal;
    }
    public List<Student> GetAll()
    {
       return _studentDal.GetAll();
    }

    public Student GetById(int id)
    {
        return _studentDal.GetById(id);
    }

    public Student GetByName(string name)
    {
        return _studentDal.GetByName(name);
    }
}

Now, this is the most important step, We need to register IStudentBsl and IStudentDal interfaces to the DI container. See syntax above we will use the AddScoped() method to register because I want them to be disposed of after finishing a request. It looks like as below :

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();

    services.AddScoped<IStudentDal, StudentDal>();
    services.AddScoped<IStudentBsl, StudentBsl>();
}

Okay, now they are ready to call anywhere with controllers. I will display all students on the Home page of this example. In HomeController we also register IStudentBsl through the constructor and get all data in Index action and pass it to view for display.

public class HomeController : Controller
{
    private IStudentBsl _studentBsl;
    public HomeController(IStudentBsl studentBsl)
    {
        _studentBsl = studentBsl;
    }

    public IActionResult Index()
    {
        var lstStudent = _studentBsl.GetAll();
        return View(lstStudent);
    }
}

And this is in view binding:

@model List<Student>
@if(Model != null && Model.Any())
{
    <ul>
        <li style="font-weight: bold;"> # | Name | Email</li>
        @foreach (var student in Model)
        {
            <li> @student.Id | @student.FullName | @student.Email</li>
        }
    </ul>
}

And the result will display as below :

Okay, you can see now that making an instance of the class is very simple and short. You only register one time that class will be auto initial in all other classes you declare.

* Note: If your class has many constructors, DI will default select constructor has the most parameters. So I recommend you should define only one constructor in each class. You also can add a default constructor with no parameter.

2.6.2. DI Service registration methods

We have some way to register a class or interface to a DI container. 

- Register interface and implement

Syntax

services.Add{LIFETIME}<{INTERFACE}, {IMPLEMENTATION}>();

Example

services.AddSingleton<IStudentDal, StudentDal>();

- Register interface and implement dynamic with parameter

Syntax 

services.Add{LIFETIME}<{INTERFACE}>(sp => new {IMPLEMENTATION});

Examples

services.AddSingleton<IMyImp>(sp => new MyImp());
services.AddSingleton<IMyImp>(sp => new MyImp("A string!"));

- Register with the only implementation

Syntax

services.Add{LIFETIME}<{IMPLEMENTATION}>();

Example:

services.AddSingleton<MyImp>();

3. Summary

In this article, I want to only talk to you about what is IoC? and why we need to resolve Dependency injection in Asp.net core. And how to add DI to your project using Asp.net core. I hope this article is helpful to you. 

* Short summary: Some concept & keyword you need to understand to implement IoC :

- What is Inversion of Control (IoC)?

- Dependency injection container.

Inversion of Control container.

Dependency injection lifetime.

You don't forget Dependency injection is only a principle and you can implement it in many high-level programming languages such as .net, java, PHP, Python ...

You can download source code examples from Github here.

Happy code!!!