使用 IConfigureOptions 配置注入的依赖项

Using IConfigureOptions to configure an injected dependency

我已经创建了一个 ASP.NET Core 1.0.1 WebApi 项目,并试图在我的控制器中使用它之前使用一些自定义选项初始化注入的依赖项。在网上搜索后,我发现了几篇文章(, here and here 解释了如何使用 IConfigureServices 来做到这一点。看起来很简单!不幸的是,我无法让它工作,我也不知道为什么,我是确定这一定是一个简单的疏忽..

我创建了一个简单的项目,并添加了以下 classes 来说明最基本的场景:

public class Tester
{
    public void Initialize(TestOptions options)
    {
        //do something with options.
    }
}

public class TestConfigurator : IConfigureOptions<TestOptions>
{
    private Tester _tester;

    public TestConfigurator(Tester tester)
    {
        _tester = tester;
    }

    public void Configure(TestOptions options)
    {
        _tester.Initialize(options);
    }
}

public class TestOptions
{
}

'Tester' class 被注入到控制器的构造函数中 class:

[Route("api/[controller]")]
public class ValuesController : Controller
{
    public ValuesController(Tester tester)
    {
        //do something with tester..
    }
    // GET api/values
    [HttpGet]
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }
}

最后,我在Startup的ConfigureServices中添加了如下配置class:

    public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        services.AddApplicationInsightsTelemetry(Configuration);
        services.AddOptions();
        services.AddMvc();
        services.AddSingleton<Tester, Tester>();
        services.AddSingleton<IConfigureOptions<TestOptions>, TestConfigurator>();
    }

当我 运行 项目并调用 'api/values' 调用时,测试器 class 被创建并注入到 ValuesController 中,但 TestConfigurator class 从未构建因此 class 永远不会使用选项 class 进行初始化。我错过了什么?

更新 下面的答案当然对这个简化的例子都是有效的。我现在意识到我有点过于简化了,因为我正在使用的依赖项(此处显示为 Tester)来自第 3 方库,所以我没有可以使用的构造函数。将第 3 方 class 包装在扩展的 class 中就可以解决问题,但是如果有人对在不修改其构造函数的情况下操纵它有其他建议,那么我仍然愿意接受建议,谢谢。

好的,现在我明白了,我觉得所有的编辑都很愚蠢。

你用的 IOptions 错了,这让我很困惑。

实施自定义 IConfigurationOptions<> 使您能够从数据库配置选项,或者只使用不同的 class(而不是 lambda)

您想要做的是根据这些选项实例化一个测试器 class,这很好 - 但这不是 IConfigureOptions<> 的工作。

为了根据 TestOptions 初始化你的 Tester class 你应该在 Tester class 上创建一个像这样接收它的构造函数

public class Tester
{
    public Tester(IOptions<TestOptions> options)
    {
        //do something with options.
    }
}

你正在尝试做的事情会奏效。

摘自 Asp.Net Core Configuration Documentation 并根据您的示例进行了改编

假设

public class TestOptions {
    public string SomeOption { get; set; }
}

Options can be injected into your application using the IOptions<TOptions> accessor service.

您可以尝试抽象 Tester 并将其注册到服务集合。

public interface ITester {
    //tester contract    
}

public class Tester : ITester {
    public Tester(IOptions<TestOptions> options) {
        //do something with test options.
    }
}

To setup the IOptions<TOptions> service you call the AddOptions extension method during startup in your ConfigureServices method. You configure options using the Configure<TOptions> extension method. You can configure options using a delegate or by binding your options to configuration:

public void ConfigureServices(IServiceCollection services) {

    services.AddApplicationInsightsTelemetry(Configuration);

    // Setup options with DI
    services.AddOptions();

    // Configure TestOptions using config by installing Microsoft.Extensions.Options.ConfigurationExtensions
    services.Configure<TestOptions>(Configuration);

    // Configure TestOptions using code
    services.Configure<TestOptions>(testOptions => {
        testOptions.SomeOption = "value1_from_action";
    });

    // Add framework services.
    services.AddMvc();

    // Add your services.
    services.AddSingleton<ITester, Tester>();
}

最后重构控制器以依赖抽象而不是具体。

[Route("api/[controller]")]
public class ValuesController : Controller {
    public ValuesController(ITester tester) {
        //do something with tester..
    }
    // GET api/values
    [HttpGet]
    public IEnumerable<string> Get() {
        return new string[] { "value1", "value2" };
    }
}

TestOptions 应该来自哪里?您是否试图从您的设置文件中自动神奇地映射它?我认为您过度考虑了这应该如何工作,除非出于某种原因您必须使用 initialize 而不是构造函数注入。

您要做的只是让测试人员可以使用一些选项,对吧?

如果是这样,只需使用基本的 IOptions 功能 - 无需使用 IConfigureOptions

进行高级操作
public class Tester
{
    private TestOptions _options;
    public Tester(IOptions<TestOptions> options)
    {
       _options = options.Value;
    }
}
// don't need this anymore
/* public class TestConfigurator : IConfigureOptions<TestOptions>
{
    private Tester _tester;

    public TestConfigurator(Tester tester)
    {
        _tester = tester;
    }

    public void Configure(TestOptions options)
    {
        _tester.Initialize(options);
    }
}
*/
public class TestOptions
{
}

然后使用以下两种方法之一配置选项(取决于它是来自配置还是必须手动构建)。

public void ConfigureServices(IServiceCollection services)
{
        // Add framework services.
        services.AddApplicationInsightsTelemetry(Configuration);
        services.AddOptions();
        services.AddMvc();
        services.AddSingleton<Tester, Tester>();
        // Configure TestOptions using config 
        services.Configure<TestOptions>(Configuration);

        // Configure MyOptions using code
        services.Configure<TestOptions>(testOptions =>
        {
           // initialize them here, e.g. testOptions.Foo = "Bar"
        });
}