The 'Unable to Resolve Service' Error
This is part 19 of ASP.NET Core tutorial. In this video, we'll discuss dependency injection in detail with an example. This is the same example that we worked with in our previous video. Notice our HomeController has a dependency on the service IEmployeeRepository.
Now, the important point to keep in mind is this HomeController is not creating an instance of this dependency using the new keyword. Instead, we are injecting this IEmployeeRepository into the HomeController class using its constructor. So this is called constructor injection. And if we run this project as it stands right now, we get this error: "unable to resolve service for type IEmployeeRepository while attempting to activate HomeController."
Now, let's understand why we are getting this error. We are injecting the dependency IEmployeeRepository using the constructor, storing the constructor parameter in this private field _employeeRepository, and then from the Index action method, we are calling GetEmployee method of the interface which returns an Employee object, and we are accessing the Name property of that Employee object.
And if you look at this IEmployeeRepository, it's actually an interface, so it doesn't contain any code, meaning the interface will not actually do the work. The actual work is done by this MockEmployeeRepository. At the moment, as it stands right now with our project, we only have one implementation for this interface IEmployeeRepository, and that implementation is within this MockEmployeeRepository class. So here is what we want...
Registering Services in Startup.cs
...ASP.NET Core to do. When someone like this HomeController requests our IEmployeeRepository, we want ASP.NET Core to create an instance of the implementation of this interface, IEmployeeRepository. At the moment, we only have one implementation, and that implementation is within this MockEmployeeRepository class. So when someone requests IEmployeeRepository service, we want ASP.NET Core to create an instance of MockEmployeeRepository class and inject that instance into the controller.
But by default, the ASP.NET Core dependency injection system will not be able to do that. We'll have to register our interface, that is IEmployeeRepository, and its implementation, in this case MockEmployeeRepository class, with the ASP.NET Core dependency injection container. We do that in this file, Startup.cs.
We have the Startup class here, and it contains two methods: ConfigureServices and Configure. If you recall from our previous videos in the series, we use this Configure method to set up our application's request processing pipeline. And we use this ConfigureServices method to configure the services required for our application. We use the same method to configure both the ASP.NET framework services, such as the MVC services, as well as our custom services, such as this IEmployeeRepository service.
Notice we have an incoming parameter on this ConfigureServices method, and the parameter type is IServiceCollection. We use this interface to add our services to the ASP.NET Core dependency injection container. Notice when I type services. we have a method called AddSingleton. This is one of the methods that we can use to add our services to the ASP.NET Core dependency injection container. In addition to AddSingleton, we also have AddTransient and AddScoped. We'll discuss the difference between these three methods—that is AddScoped, AddTransient, and AddSingleton—in just a bit.
For now, let's use AddSingleton to add our dependency to the dependency injection container. For that, we use the generic parameter. First, we specify our interface, that is IEmployeeRepository. We don't have the namespace; let's bring in the required namespace. And then we specify the implementation of this interface. At the moment, we only have one implementation for IEmployeeRepository, and that is our MockEmployeeRepository.
So with this one line of code, we are basically saying if someone like this HomeController requests our IEmployeeRepository service, then create an instance of this MockEmployeeRepository class and then inject that instance. So with this change in place, when we reload the webpage, the error is gone, and we see the name of the employee as expected.
Why Avoid the 'new' Keyword? (Tight Coupling)
At this point, you might be thinking, "Why do we have to do all this? Define a constructor parameter and then register our dependency with the dependency injection container? Why can't we simply do something like this?" So we'll not have the constructor parameter, and then we want a new instance of MockEmployeeRepository, so we create a new instance of the class using the new keyword. And our application will work.
But a problem with this approach is that it tightly couples our HomeController with the MockEmployeeRepository. Later, if we provide a new implementation for this IEmployeeRepository and if we want to use that new implementation instead of MockEmployeeRepository, the code in this HomeController has to change.
At this point, you might be thinking, "What is so difficult about it? It is just one line of code. Instead of using new MockEmployeeRepository, we may use new SqlEmployeeRepository if we are providing an implementation to retrieve data from a SQL Server database. So what's so difficult about it?" Well, at the moment in our project, we only have one controller, but in a typical real-world application, depending on the complexity of the project, you may have 50, 60, or even 100 controllers. And if then all those 100 controllers are using MockEmployeeRepository and we provide a new implementation, and then we want to swap out MockEmployeeRepository with that new implementation, then the code in all those 50, 60, or 100 controllers has to change. Imagine how much tedious that job is. And it's not only tedious, it's also error-prone.
So in short, using the new keyword like this to create instances of dependencies creates tight coupling. And as a result, our application will be extremely difficult to change and maintain. With dependency injection, we don't have this tight coupling. Even if we have used MockEmployeeRepository in 50 other controllers in our application and if we want to swap out the MockEmployeeRepository implementation with a new implementation, only one line of code has to change. Instead of using MockEmployeeRepository class, we use the name of the class that provides the new implementation.
Unit testing also becomes much easier with dependency injection, as we can easily swap out dependencies. If this is slightly confusing at the moment, please don't worry. In our upcoming videos, we are going to provide a new implementation for this IEmployeeRepository. That new implementation will retrieve employee details from an underlying SQL Server database. We'll then replace this MockEmployeeRepository with that new implementation. At that point, you will understand the power and flexibility dependency injection provides.
Understanding Service Lifetimes & Conclusion
ASP.NET Core provides three methods to register dependencies with the dependency injection container. The method that we use determines the lifetime of that registered service. At the moment, within our application, we are using the AddSingleton method to register our dependency.
As the name implies, this method creates a singleton service. A singleton service is created when it is first requested. This same instance is then used by all the subsequent requests. So, in general, a singleton service is created only one time per application, and that single instance is used throughout the application lifetime.
Next, we have AddTransient. This method creates a transient service. A new instance of a transient service is created each time it is requested.
Finally, AddScoped. This method creates a scoped service. A new instance of a scoped service is created once per request within the scope. For example, in an MVC application, it creates one instance for each HTTP request but uses the same instance in the other calls within that same web request.
If the difference between these three methods is slightly confusing, please don't worry. We'll be revisiting all these three methods several times in our upcoming videos in the series.
Out-of-the-box, ASP.NET Core has built-in support for dependency injection. Dependency injection allows us to create systems that are loosely coupled and easy to unit test. That's it in this video. Thank you for watching.