EXPLOIT DEPENDENCY INJECTION OF ASP.NET CORE
Let’s start with a picture that shows us a car engine, look at how many small parts there are. If engineers decided to make so many small components, it is because each one has a different life cycle from the others. Take spark plugs for example, they have a different life cycle than the engine, and the designers decided that through an interface represented by the threading, the spark plug should not be melted into the engine but easily replaced by simply unscrewing the old one and housing the new one. In this section, we will see that ASP.NET Core is also modular; as the requirements of a project change, we need to be ready when one software component needs to be replaced with another, all with minimal effort.
If engineers have chosen to divide a car engine into many small components, it is because each has its own role within the whole and because, above all, it must be able to be replaced independently of the other components. Within an engine as well as within an application there are components that have a different life cycle than others and must be replaceable. Let’s go back inside the CoursesController.
The Controller and CourseService are not weakly coupled, not at all, because when the Index or Detail action runs an instance of CourseService is also created. It is as if the spark plug of a car has no thread to be disassembled, but is cast inside the engine. Let us see how the code should be modified to make the two components less coupled.
As you can see in the Controller I have inserted a constructor having an instance of CourseService as a parameter. However, if we launch the application we get an error because the ASP.NET Core CourseService instance did not build it. To remedy this we go to the startup file and modify it as follows.
With this statement we are telling ASP.NET Core services that they must prepare to handle objects of type CourseService when they encounter a component such as our controller that has a dependency on it and that must be built and passed to the controller’s constructor. Through dependency injection ASP.NET Core can create component instances and inject them, in this case into the controller. Now, however, the coupling problem is not completely solved, that is, the two components are said to be strongly coupled. The CoursesController cannot work unless you provide an instance of CourseService.
MAKE THE COMPONENTS WEAKLY COUPLED
If we wanted to replace CourseService with another class it would be a problem because we would have to go back into the Controller again and change CourseService to the name of the new class. This is a bit like how a car engine can run only with spark plugs from a specific manufacturer. What we are going to do now is that CoursesController no longer depends on CourseService but on an interface, when we should replace CourseService with something else we could do it without CoursesController knowing anything about it.
We go back inside the CoursesController and make the change, that is, we replace a concrete implementation such as CourseService with an interface that has no application logic.
Now it seems like a triviality what we did, but now actually the Controller depends on an interface that has, by its definition, no implementation details. What matters to the Controller is that this service exposes the GetCourses() and GetCourse(id) methods no matter where the data comes from, a Web Services, a database, etc. Now we have to go to the Startup.cs class and change the registration of the service which will no longer be CourseService but ICourseService. If we try to run the program we get an error since an interface does not have any application logic and must be registered with its concrete implementation.
Dependency injection when it encounters in the Controller a dependency to ICourseService actually builds an instance of CourseService. Now with this little ploy we can change CourseService to any other class, the important thing is that it implements the interface.
CHOOSE THE LIFE CYCLE OF A SERVICE
When we create a service we are not only delegating ASP.NET Core to create the service but also to manage its lifecycle. Instances created by dependency injection will have to be destroyed at some point in order to keep the memory occupied at acceptable levels.
When the Garbage Collector realizes that these instances are no longer needed because they are unused, it removes them from memory.
RACE CONDITION PROBLEMS
ASP.NET Core is a Multithread technology, when using AddSingleton you have to be careful about Race Condition issues , this is because it may happen that two Threads access the same resource at the same time.
If two Threads arrive at the same time, assuming they read the initial value both valued in the private field to zero, they both increment it by one, but this is wrong because two requests have arrived.
Let’s look at the thread-safe implementation of the counter. Each thread accesses the private field one at a time, has plenty of time to read the counter value and increment it. It is kind of like there is a moderator.
SECTION SUMMARY
STRONG AND WEAK COUPLING OF COMPONENTS
When we build an application, we should always make sure that the various components are individually replaceable, much like replacing the spark plugs in a car does not involve replacing the engine or the body.
This is an important caveat to keep in mind because requirements change over time, and our developer will ask us to change the behavior of the application at some points. Therefore, we need to be ready to comply with his request with minimum expenditure of time and energy.
In the last section we made two components interact: the CoursesController and the CourseService. Let’s review what was the code we had used within the Index action.
- public IActionResult Index()
- {
- var courseService = new CourseService();
- List<CourseViewModel> courses = courseService.GetCourses();
- return View(courses);
- }
This code presents a problem because it makes the CoursesController strongly coupled to the CoursesService. That is, the moment we want to replace CourseService with another implementation, perhaps drawing data from another source, we have to go back inside the CoursesController and replace all occurrences of CourseService by hand. In large applications, this can be time-consuming and error-prone. It is as if the automobile’s spark plugs are encased inside the engine, thus forcing us to replace the entire engine when the spark plugs wear out after just 50,000 kilometers driven.
It is to avoid this problem that mechanical engineers designed an easily accessible threaded housing on the engine from the outside, which is the connecting interface with the spark plug.
An old spark plug can be easily replaced with a new one by simply unscrewing it. A simple expedient that saves a lot of time and money since the spark plug wears out much sooner than the engine.
We, too, in our application, should make sure that components are easily replaceable. For this, the C# language comes to us with interfaces.
We start by defining an interface that defines the public members of the application service .
- public interface ICourseService
- {
- List<CourseViewModel> GetCourses();
- CourseDetailViewModel GetCourse(int id);
- }
As can be seen, the interface simply describes what public members a class is to implement, not how it is to implement them. Instead, the mode will be defined by our CourseService class, which is a concrete implementation of the interface we just wrote.
Then let’s go to the CoursesController and make it have a dependency on that interface. Dependencies can be expressed as constructor parameters.
Now our CoursesController has become weakly coupled to the application service that provides the courses because it no longer depends on a concrete implementation but on an interface. In fact, if we want to replace CourseService with another implementation we can do so without touching the controller.
We simply go to the ConfigureServices method of the Startup class and tell ASP.NET Core which concrete class it should build when it encounters components that depend on the ICourseService interface. This is necessary because interfaces cannot be constructed.
This practice is called dependency injection, because a component’s dependencies are passed from outside (also called “injected”). The usefulness of this practice will become even more apparent in the future when we address the topic of testing.
SERVICE LIFE CYCLE
ASP.NET Core, thanks to its dependency injection mechanism, will take care instead of us building the instances of services such as CourseService on which the controllers and other components depend.
But-when does ASP.NET Core destroy the instances it has created, thus freeing up memory?
It depends on the lifecycle we indicated when we registered the service from the ConfigureServices method.
- If we use services.AddTransient<ICourseService, CourseService>(), the instance can be destroyed when the component that used it is destroyed, i.e., when the controller has finished its execution;
- If we use services.AddScoped<ICourseService, CourseService>(), the instance can be destroyed only at the end of the current HTTP request. This means that ASP.NET Core will pass the exact same instance to other components that need it, at least as long as the HTTP request is still in progress. As we will see later, this can be a great way to effectively reuse services that are “expensive” to build such as Entity Framework Core’s DbContext which we will see later;
- If we use services.AddSingleton<ICourseService, CourseService>(), the instance can be destroyed only when the application is stopped. This means that ASP.NET Core will create at most one instance and pass it to each component that needs it, even for HTTP requests coming from different users. Care must be taken because multiple threads simultaneously will access the same instance, causing severe side effects if it is not designed to be thread-safe.
LINK TO CODE ON GITHUB
Download the section09 code or clone the GITHUB repository to have all sections available in your favorite editor.
Leave A Comment