c# - MS DI how to configure services using information known only at runtime
问题描述
I'm using Microsoft.Extensions.DependencyInjection 2.1.1 and have services that use the Options pattern to get their configuration. I want to be able to select a concrete implementation of a service using information known only at runtime (e.g. read from configuration).
If I know all the possible concrete service implementations and their options at compile-time, I can do something like the following to select and configure an implementation:
if (useImplementation1)
{
services.Configure<MyServiceImplementation1Options>(config.GetSection("MyServiceImplementation1"));
services.AddSingleton<IMyService, MyServiceImplementation1>();
}
else
{
services.Configure<MyServiceImplementation2Options>(config.GetSection("MyServiceImplementation2"));
services.AddSingleton<IMyService, MyServiceImplementation2>();
}
Is there a way to configure this service and its options using only information known at runtime, e.g.:
Type myServiceOptionsType = ... from configuration, e.g. typeof(MyServiceImplementation1Options)
Type myServiceImplementationType = ... from configuration, e.g. typeof(MyServiceImplementation1)
string myServiceConfigSection = ... from configuration, e.g. "MyServiceImplementation1"
??? what do I do next?
UPDATE
Which I hope will clarify what I'm looking for. Here are sample classes: assume Implementation1 gets data from an XML file, Implementation2 gets data from an SQL database.
Implementation1 code (in assembly MyAssembly):
public class MyServiceImplementation1Options
{
public Uri MyXmlUrl {get; set;}
}
public class MyServiceImplementation1 : IMyService
{
public MyServiceImplementation1(IOptions<MyServiceImplementation1Options> options)
{
...
}
... Implement IMyService ...
}
Implementation2 code (in assembly OtherAssembly):
public class MyServiceImplementation2Options
{
public string ConnectionString {get; set;}
public string ProviderName {get; set;}
}
public class MyServiceImplementation2 : IMyService
{
public MyServiceImplementation2(IOptions<MyServiceImplementation2Options> options)
{
...
}
... Implement IMyService ...
}
Now I'd like to choose between these two implementations without necessarily having compile-time access to the assemblies (MyAssembly and OtherAssembly) that contain the implementations. At runtime I'd read data from a configuration file, that might look something like (in the following, think of Keys and Values as a Dictionary passed to a MemoryConfigurationProvider
- hierarchical configuration is represented using colon separators. It could also be configured using appsettings.json
with hierarchy represented using nesting):
Implementation1 configuration:
Key="MyServiceConcreteType" Value="MyServiceImplementation1,MyAssembly"
Key="MyServiceOptionsConcreteType" Value="MyServiceImplementation1Options,MyAssembly"
Key="MyServiceOptionsConfigSection" Value="MyServiceImplementation1"
Key="MyServiceImplementation1:MyXmlUrl" Value="c:\MyPath\File.xml"
Implementation2 configuration:
Key="MyServiceConcreteType" Value="MyServiceImplementation2,OtherAssembly"
Key="MyServiceOptionsConcreteType" Value="MyServiceImplementation2Options,OtherAssembly"
Key="MyServiceOptionsConfigSection" Value="MyServiceImplementation2"
Key="MyServiceImplementation2:ConnectionString" Value="Server=...etc..."
Key="MyServiceImplementation2:ProviderName" Value="System.Data.SqlClient"
解决方案
OK, now I see where the confusion is. Because the Configure
method doesn't have non-generic versions, so you didn't know how to pass type known at runtime to the method?
In this case, I would use ConfigureOptions
method which allows you to pass a configurator type as parameter. The type must implement IConfigureOptions<T>
which defines Configure(T)
method to configure an options of T.
For example, this type configures MyServiceImplementation1Options
using IConfiguration
:
class ConfigureMyServiceImplementation1 :
IConfigureOptions<MyServiceImplementation1Options>
{
public ConfigureMyServiceImplementation1(IConfiguration config)
{
}
public void Configure(MyServiceImplementation1Options options)
{
// Configure MyServiceImplementation1Options as per configuration section
}
}
MyServiceImplementation1Options.Configure
method is invoked while resolving IOptions<MyServiceImplementation1Options>
, and you could inject IConfiguration
to the type to read configuration from specified section.
And you could use the type like this in Startup:
// Assume you read this from configuration
var optionsType = typeof(MyServiceImplementation1Options);
// Assume you read this type from configuration
// Or somehow could find this type by options type, via reflection etc
var configureOptionsType = typeof(ConfigureMyServiceImplementation1);
// Assume you read this type from configuration
var implementationType = typeof(MyServiceImplementation1);
// Configure options using ConfigureOptions instead of Configure
// By doing this, options is configure by calling
// e.g. ConfigureMyServiceImplementation1.Configure
services.ConfigureOptions(configureOptionsType);
In terms of service registration, there are non-generic versions of Add*
methods. For example, the code below registers type known at runtime as IMyService
:
// Register service
services.AddSingleton(typeof(IMyService), implementationType);
推荐阅读
- c - 为什么我会收到错误消息:'awk: read error (Is a directory)'
- php - Laravel 8 实现 PHP 的 DOM XML 函数输出 XML
- javascript - 更改 Material UI 中的步骤时,垂直步进器应滚动到顶部反应
- css - 第二行和后续行没有在网格中指定全高
- python - 在一张图中绘制更多垂直密度图
- python-3.x - Python matplotlib 无法在椭圆曲线 y^2+x^3+x^2=0 上绘制 Acnode(孤立点)
- node.js - expressjs 返回具有相同标签的项目列表
- c# - C# Interop.Excel 找不到 dll
- php - Authorize.Net API 限制 1000 个结果。如何克服它?
- java - 进行同时 WebClient 调用并仅获取第一个已完成的调用