The built-in support for dependency injection in ASP.NET Core is great. However, dealing with multiple implementations of an interface when working with dependency injection in ASP.NET Core is a bit tricky. In this article I’ll show you how to dynamically select a service from such an implementation in ASP.NET Core.
To work with the code examples provided in this article, you should have Visual Studio 2019 installed in your system. If you don’t already have a copy, you can download Visual Studio 2019 here.
Create an ASP.NET Core MVC project in Visual Studio
First off, let’s create an ASP.NET Core Web Application MVC project in Visual Studio 2019. Assuming Visual Studio 2019 is installed in your system, follow the steps outlined below to create a new ASP.NET Core MVC project in Visual Studio.
- Launch the Visual Studio IDE.
- Click on “Create new project.”
- In the “Create new project” window, select “ASP.NET Core Web Application” from the list of templates displayed.
- Click Next.
- In the “Configure your new project” window, specify the name and location for the new project.
- Optionally check the “Place solution and project in the same directory” check box, depending on your preferences.
- Click Create.
- In the “Create a New ASP.NET Core Web Application” window shown next, select .NET Core as the runtime and ASP.NET Core 3.1 (or later) from the drop-down list at the top.
- Select “Web Application (Model-View-Controller)” as the project template to create a new ASP.NET Core MVC application.
- Ensure that the check boxes “Enable Docker Support” and “Configure for HTTPS” are unchecked as we won’t be using those features here.
- Ensure that Authentication is set to “No Authentication” as we won’t be using authentication either.
- Click Create.
Following these steps should create a new ASP.NET Core MVC project in Visual Studio 2019. We’ll use this project in the sections below to illustrate how we can register multiple implementations of an interface in ASP.NET Core 3.1.
One problem with the IoC container in ASP.NET Core
The built-in IoC (inversion of control) container in ASP.NET Core may not be as extensive as AutoFac or Unity or some other popular IoC containers, but you should be able to use it to meet your requirements in most cases. However, the built-in IoC container does not allow us to register multiple services and then retrieve a specific service instance at runtime.
There are a few IoC containers that enable you to register concrete types using a unique key that distinguishes the instances of these types. However, the built-in IoC container in ASP.NET Core lacks support for this. Hence registering services that have a common interface and resolving them at runtime isn’t simple. Let’s understand this with an example.
Assume you have an interface called ICustomLogger with the following code:
public interface ICustomLogger
{
public bool Write(string data);
}
The ICustomLogger interface is implemented by the following three classes:
public class FileLogger : ICustomLogger
{
public bool Write(string data)
{
throw new System.NotImplementedException();
}
}
public class DbLogger : ICustomLogger
{
public bool Write(string data)
{
throw new System.NotImplementedException();
}
}
public class EventLogger : ICustomLogger
{
public bool Write(string data)
{
throw new System.NotImplementedException();
}
}
The FileLogger class is used for logging data to a file, the DbLogger class is used for logging data to a database, and the EventLogger class is used to log data to the event log so that the logs can be viewed using the Event Viewer tool. Note that since logging data is not the objective of this article, the Write methods pertaining to each of these classes do not include any implementation.
Now suppose you’ve added instances of these classes as scoped services in the ConfigureServices method of the Startup class as shown below:
services.AddScoped<ICustomLogger, FileLogger>();
services.AddScoped<ICustomLogger, DbLogger>();
services.AddScoped<ICustomLogger, EventLogger>();
The following code snippet illustrates how you can take advantage of dependency injection in the constructor of the HomeController class to use these services.
public class HomeController : Controller
{
public HomeController(ICustomLogger fileLogger, ICustomLogger dbLogger, ICustomLogger eventLogger)
{
var obj = fileLogger; //This code has been written for debugging
}
//Action methods go here
}
When you run this application and the breakpoint is hit, you’ll observe that all three parameters are instances of type EventLogger because EventLogger has been injected last. This is shown in Figure 1 below.
Figure 1.
Using dependency injection with multiple implementations of an interface in ASP.NET Core
How do we overcome this limitation of the built-in IoC container in ASP.NET Core? In the solution outlined below, we will use an IEnumerable collection of services to register the services and a delegate to retrieve a specific service instance.
The illustration below will also include example code for using dependency injection in the constructor of your controller class, resolving the service instance, and returning instances of the three classes (FileLogger, DbLogger, and EventLogger) depending on the service type selected at runtime.
Use an IEnumerable collection of service instances
In the ConfigureServices method write the following code to add scoped services of each of the ICustomLogger implementations.
services.AddScoped<ICustomLogger, FileLogger>();
services.AddScoped<ICustomLogger, DbLogger>();
services.AddScoped<ICustomLogger, EventLogger>();
Next, take advantage of dependency injection and an IEnumerable collection of the ICustomLogger implementations in the constructor of the controller class as shown in the code snippet given below.
public HomeController(IEnumerable<ICustomLogger> loggers)
{
foreach(var logger in loggers)
{
var objType = logger.GetType();
}
}
Use a delegate to retrieve a specific service instance
Consider the following enum that contains integer constants that correspond to the three types FileLogger, DbLogger, and EventLogger.
public enum ServiceType
{
FileLogger,
DbLogger,
EventLogger
}
Next, declare a shared delegate as shown in the code snippet given below.
public delegate ICustomLogger ServiceResolver(ServiceType serviceType);
Now add the following scoped services to the service collection instance as shown in the next code snippet.
services.AddScoped<FileLogger>();
services.AddScoped<DbLogger>();
services.AddScoped<EventLogger>();
Return instances based on service type at runtime
The following code snippet shows how you can return instances of the FileLogger, DbLogger, and EventLogger classes depending on the service type selected at runtime.
services.AddTransient<ServiceResolver>(serviceProvider => serviceTypeName =>
{
switch (serviceTypeName)
{
case ServiceType.FileLogger:
return serviceProvider.GetService<FileLogger>();
case ServiceType.DbLogger:
return serviceProvider.GetService<DbLogger>();
case ServiceType.EventLogger:
return serviceProvider.GetService<EventLogger>();
default:
return null;
}
});
Use dependency injection and resolve the service instance
Lastly, here is how you can use dependency injection in the constructor of your controller class and then resolve the service instance.
public HomeController(Func<ServiceType, ICustomLogger> serviceResolver)
{
var service = serviceResolver(ServiceType.FileLogger);
}
You could also use reflection to resolve types at runtime but it is not a recommended solution. Another possible solution is using a generic type parameter on the interface.
How to do more in ASP.NET Core:
- How to use RecyclableMemoryStream in .NET Core
- How to use IHttpClientFactory in ASP.NET Core
- How to use the ProblemDetails middleware in ASP.NET Core
- How to create route constraints in ASP.NET Core
- How to manage user secrets in ASP.NET Core
- How to build gRPC applications in ASP.NET Core
- How to redirect a request in ASP.NET Core
- How to use attribute routing in ASP.NET Core
- How to pass parameters to action methods in ASP.NET Core MVC
- How to use API Analyzers in ASP.NET Core
- How to use route data tokens in ASP.NET Core
- How to use API versioning in ASP.NET Core
- How to use Data Transfer Objects in ASP.NET Core 3.1
- How to handle 404 errors in ASP.NET Core MVC
- How to use dependency injection in action filters in ASP.NET Core 3.1
- How to use the options pattern in ASP.NET Core
- How to use endpoint routing in ASP.NET Core 3.0 MVC
- How to export data to Excel in ASP.NET Core 3.0
- How to use LoggerMessage in ASP.NET Core 3.0
- How to send emails in ASP.NET Core
- How to log data to SQL Server in ASP.NET Core
- How to schedule jobs using Quartz.NET in ASP.NET Core
- How to return data from ASP.NET Core Web API
- How to format response data in ASP.NET Core
- How to consume an ASP.NET Core Web API using RestSharp
- How to perform async operations using Dapper
- How to use feature flags in ASP.NET Core
- How to use the FromServices attribute in ASP.NET Core
- How to work with cookies in ASP.NET Core
- How to work with static files in ASP.NET Core
- How to use URL Rewriting Middleware in ASP.NET Core
- How to implement rate limiting in ASP.NET Core
- How to use Azure Application Insights in ASP.NET Core
- Using advanced NLog features in ASP.NET Core
- How to handle errors in ASP.NET Web API
- How to implement global exception handling in ASP.NET Core MVC
- How to handle null values in ASP.NET Core MVC
- Advanced versioning in ASP.NET Core Web API
- How to work with worker services in ASP.NET Core
- How to use the Data Protection API in ASP.NET Core
- How to use conditional middleware in ASP.NET Core
- How to work with session state in ASP.NET Core
- How to write efficient controllers in ASP.NET Core