首页 > 解决方案 > 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"

标签: c#.net-coredependency-injection

解决方案


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);

推荐阅读