Conditional dependency injection in ASP.NET Core. Part 1
In the first part of the article, we will analyze the configuration of the IoC container at the application launch stage, with the possibility of choosing one or more of the available implementations. We also consider the implementation process in the context of an HTTP request, based on the data available in it. In the second part, we show how you can expand the capabilities of DI for choosing an implementation based on the text identifier of the service.
Content
Part 1. Conditional service resolution
1. Environment context - conditional service receipt depending on the current Environment setting.
2. Configuration context - conditional receipt of a service based on the application settings file.
3. HTTP request context - conditional receipt of a service based on web request data.
Part 2. Obtaining a service by identifier (Resolving service by ID)
4. Obtaining a service based on an identifier
1. Environment context
ASP.NET Core introduces a mechanism such as Environments .
Environment is an environment variable (ASPNETCORE_ENVIRONMENT) that indicates in which configuration the application will run. By convention, ASP.NET Core supports three predefined configurations: Development, Staging, and Production, but in general, the configuration name can be anything.
Depending on the installed Environment, we can configure the IoC container in the way we need. For example, at the development stage, you need to work with local files, and at the testing and production stage, with files in the cloud service. The container setting in this case will be as follows:
public IHostingEnvironment HostingEnvironment { get; }
public void ConfigureServices(IServiceCollection services)
{
if (this.HostingEnvironment.IsDevelopment())
{
services.AddScoped();
}
else
{
services.AddScoped();
}
}
2. Configuration context
Another innovation in ASP.NET Core was the user settings storage mechanism, which replaced the section
appsettings.json
{
"ApplicationMode": "Cloud" // Cloud | Localhost
}
public void ConfigureServices(IServiceCollection services)
{
var appMode = this.Configuration.GetSection("ApplicationMode").Value;
if (appMode == "Localhost")
{
services.AddScoped();
}
else if (appMode == "Cloud")
{
services.AddScoped();
}
}
Thanks to these approaches, the IoC container is configured at the application launch stage. Let's now see what possibilities we have if it is necessary to choose an implementation at runtime, depending on the request parameters.
3. Request context
First of all, we can get from the IoC container all the objects that implement the required interface:
public interface IService
{
string Name {get; set; }
}
public class LocalController
{
private readonly IService service;
public LocalController(IEnumerable services)
{
// из всех реализаций выбираем необходимую
this.service = services.FirstOrDefault(svc => svc.Name == "local");
}
}
This approach completely solves the problem of choosing an implementation, but it closely resembles the Service Locator, which has been criticized several times ( tyts , tyts ). Fortunately, ASP.NET Core did not leave us alone with this problem: if we look at the set of methods available to configure the IoC container, we will see that we have another way to solve the problem using the delegate:
Func implementationFactory
As you remember, the interface
IServiceProvideris an IoC container that we configure in the ConfigureServicesclass method Startup. In addition, the ASP.NET Core platform also sets up a number of its own services that will be useful to us. As part of the web request
IHttpContextAccessor, the service that provides the object is primarily useful to us HttpContext. With it, we can get comprehensive information about the current request, and based on this data, select the desired implementation:public void ConfigureServices(IServiceCollection services)
{
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped(serviceProvider => {
var httpContext = serviceProvider.GetRequiredService();
return httpContext.IsLocalRequest() // IsLocalRequest() is a custom extension method, not a part of ASP.NET Core
? serviceProvider.GetService()
: serviceProvider.GetService();
});
}
Note that you must explicitly configure the implementation
IHttpContextAccessor. In addition, we do not set the types LocalServiceand CloudServicehow implementation of the interface IService, and simply add them to the container. Thanks to access to
HttpContext, you can use the request headers, query string, form data to analyze and select the desired implementation:$.ajax({
type:"POST",
beforeSend: function (request)
{
request.setRequestHeader("Use-local", "true");
},
url: "UseService",
data: { id = 100 },
});
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped(serviceProvider => {
var httpContext = serviceProvider.GetRequiredService().HttpContext;
if (httpContext == null)
{
// Разрешение сервиса происходит не в рамках HTTP запроса
return null;
}
// Можно использовать любые данные запроса
var queryString = httpContext.Request.Query;
var requestHeaders = httpContext.Request.Headers;
return requestHeaders.ContainsKey("Use-local")
? serviceProvider.GetService() as IService
: serviceProvider.GetService() as IService;
});
}
And finally, we give another example using the service
IActionContextAccessor. Choosing an implementation based on the action name:public void ConfigureServices(IServiceCollection services)
{
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped(serviceProvider => {
var actionName = serviceProvider.GetRequiredService().ActionContext?.ActionDescriptor.Name;
// Если имя экшена отсутствует, значит разрешение сервиса происходит не в рамках веб-запроса, а, например, в классе Startup
if (actionName == null) return ResolveOutOfWebRequest(serviceProvider);
return actionName == "UseLocalService"
? serviceProvider.GetService()
: serviceProvider.GetService();
});
}
So, we examined the basic solutions of a conditional implementation, based on various data from the context in which the application is running. In the next article, we will go a little deeper into the capabilities of DI and see how to extend its functionality.
The source code for the examples can be downloaded at: github.com/nix-user/AspNetCoreDI