Problem statement
Have you ever had to alter a lot of code due to a new straightforward requirement? Have you ever had a difficult time attempting to refactor an application?
If you replied yes to any of the above questions, perhaps your codebase mourns from dependency. It's a specific challenge of the code of an application when its components are too coupled. In other words, when a component depends on another one in a too-tight way. The major effect of component dependency is the maintenance hardship of the code, which, of course, signifies a more heightened cost.
What is Dependency Injection?
In software engineering, there is a term design pattern that could facilitate solving the problem by reusing the solution that is already found, In which Dependency Injection(DI) is one of the good design patterns in which a technique, “an object achieves other objects that it depends on, called dependencies”. The “injection” does mean the passing of a dependency “a service” into the client that uses it. The service is made part of the client's state. It actually helps us to create a loosely coupled application.
DI is one of the most common practices that assist you to create better maintainable code. The main advantage is the application which is going to be created is loosely coupled and has provided superb maintainability, reusability, and testability as well. It is loosely coupled due to dependency required by the class being injected from the external world rather than made themselves directly win in code.
.Net Core also provides you facility with ample support to DI, but it may not always be obvious how to use it. In this article, I want to explain various DI concepts and introduce you to support provided by .Net Core.
Use DI in .Net Core
.NET Core provided a built-in IoC Container that facilitates DI management. The IoC Container is responsible for helping automatic DI. It includes:
- Registration: The IoC Container ought to know which type of object to make for a specific dependency so, it delivers a way to map a type to a class so that it can make the correct dependency instance.
- Resolution: The IoC Container helps to resolve a dependency by making an object and injecting it into the requesting class. We do not have to instantiate objects manually to handle dependencies.
- Disposition: The IoC Container manages the lifetime of the dependencies.
The IoC Container implements the 'IServiceProvider' interface. If you want to create your own IoC Container, you must implement this interface. In .NET Core, the dependencies managed by the container are called services. We have 2 types of services:
- Framework services: are part of the .NET Core framework
e.g IApplicationBuilder, IConfiguration, ILoggerFactory, etc.
- Application services: we create in our application while IoC doesn't know them, we need to register them explicitly.
Framework Services (Example)
The Startup class in an ASP.NET application uses Dependency Injection much:
The above example shows that the Startup() constructor needs a configuration parameter implementing the IConfiguration type. While IConfiguration is one of the framework service types, IoC Container knows how to make an instance of it and inject it into the Startup class using the Constructor Injection technique. The same is used in the Configure() method. Here the thing which we have to Keep in mind is that only the below mention framework service types can be injected in the Startup() constructor and the Configure() method of a standard ASP.NET application:
- IWebHostEnvironment
- IHostEnvironment
- IConfiguration
These are certain cases for framework services because we don't need to register them.
Register Framework Services
ConfigureServices() method of the Startup class is used for registering of services, It has an IServiceCollection parameter illustrating the list of services our application depends on. The collection portrayed by this parameter allows us to register a service in the IoC Container. Following example illustrate it:
In the above illustration, we are registering a dependency for handling authentication for our application. In this particular case, are using the extension method AddAuthentication(). The framework provides extension methods to register and configure dependencies for the common services.
Application Services (Example)
For explaining this we have to create some practical scenario, we want to fetch all the students from the database and display them in the UI layer. So for this, we have to create a service (API) that will be called by the UI layer. Then, in API, we require to create one 'GET' method that will call the repository, and the repository will get a list from the database. For the purpose to call this repository, we have to create an instance of the repo in API's GET method, which means, it is mandatory to create an instance of repository for API.
So the dependency of API is the instance of the repository. So, let’s get practical and see code how we can inject this dependency into our API.
The Student Model class has 2 properties.
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
}
The Student Repository Interface.
public interface IStudentRepository
{
List<Student> GetStudents();
}
The implementation class of above interface having dummy data.
public class StudentRepository : IStudentRepository
{
public List<Student> GetStudents()
{
List<Student> students = new List<Student>();
Student student = new Student() { StudentId = 1, Name = "Student1" };
students.Add(student);
student = new Student() { StudentId = 2, Name = "Student2" };
students.Add(student);
return students;
}
}
Then we have to create a Controller(API) for calling this repo, and inject this interface into this.
public class StudentController : Microsoft.AspNetCore.Mvc.Controller
{
private IStudentRepository studentRepository { get; set; }
public StudentController(IStudentRepository _studentRepository)
{
studentRepository = _studentRepository;
}
[HttpGet]
public async Task<IActionResult> Get()
{
List<Student> categories = studentRepository.GetStudents();
return Ok(categories);
}
}
In the last, we have to register it in Startup class. Also mention which type of instance want to inject - (the lifetime) of our instance.
public void ConfigureServices(IServiceCollection services)
{
// its lifetime so get only one
services.AddSingleton<IStudentRepository, StudentRepository>();
//services.AddTransient<IStudentRepository, StudentRepository>();
//services.AddScoped<IStudentRepository, StudentRepository>();
//services.AddControllersWithViews();
}
What is the lifetime?
Transient
- Makes an instance each time.
- Never shared.
- Used for lightweight stateless services.
Singleton
- Creates only single instance.
- Shared among all components that demand it.
Scoped
- Creates an instance once per scope.
- Created on every request to the application.
Finally, we got the above result from our API. Hopefully, it has been helpful to you to understand the concept of DI. Thanks!