简化 IOptions<T> 的方法

10

我正在尝试将一个.NET Framework类库与一个使用内置DI机制的ASP.NET Core 2.1应用程序对齐。现在,我创建了一个配置类,并向appsettings.json添加了适当的部分:

services.Configure<MyConfig>(Configuration.GetSection("MyConfiguration"));
services.AddScoped<MyService>();

在类库中:

public class MyService 
{
    private readonly MyConfig _config;

    public MyService(IOptions<MyConfig> config)
    {
        _config = config.Value;
    }
}

然而,为了构建这个类库,我必须添加Microsoft.Extensions.Options NuGet包。问题是该包携带了大量的依赖项,这些依赖项似乎过于冗余,仅仅为了一个接口而添加这么多依赖项有些不值得。

enter image description here

所以,最终的问题是,“我是否可以采用其他方法来配置位于.NET Framework类库中的依赖较少的DI服务?”

1
不失去 MS 配置的作用域/可重新加载特性吗?不行。如果您满意静态单例选项,可以将选项类注册到 IoC 容器中。但是当配置更改时,您将无法获得新的配置,并且它也不会被作用域限定(如果另一个请求修改了它,则可能在中间请求中进行更改)。 - Tseng
6
依赖本身并不是什么坏东西。问题在于.NET Core和.NET Standard的设计方式。之前,你会有一个带有所有程序集的大型框架,但在.NET Core/.NET Standard中,它被分成了多个包,这就是你看到的原因。此外,System.Memory包含了重要的优化功能,可以减少内存分配和碎片化,所以这是一件好事情。 :P - Tseng
1
好的,这种语气不会让你有任何进展。就像我说的,如果您不关心作用域选项保证和重新加载机制,您可以将MyConfig注入到您的服务中,而不是IOptions<MyConfig>,并在ConfigureServices中进行额外的注册:services.AddScope<MyConfig>(provider => provider.GetRequiredService<IOptions<MyConfig>>().Value);。但是,.NET Core和.NET Standard现在已经非常成熟,平台无关,并且可以在许多企业场景中使用(除非您需要基于WCF或System.Web的内容)。 - Tseng
1
如果你使用.NET Framework,那么所有这些依赖项都会被自动引入。在这里,它只是为了清晰起见而被放在了自己的包中。但是,无论是.NET Core还是.NET Framework,框架本身都会自带这些东西... - Structed
1
你不必依赖于 IOptions<T>,而且我认为你不应该依赖于 IOptions<T>。这是一个无用的抽象层。请参见此处我的理由 - Steven
显示剩余11条评论
3个回答

7

请查看Filip Wojcieszyn撰写的这篇文章。

https://www.strathweb.com/2016/09/strongly-typed-configuration-in-asp-net-core-without-ioptionst/

您可以添加扩展方法:

public static class ServiceCollectionExtensions
{
    public static TConfig ConfigurePOCO<TConfig>(this IServiceCollection services, IConfiguration configuration) where TConfig : class, new()
    {
        if (services == null) throw new ArgumentNullException(nameof(services));
        if (configuration == null) throw new ArgumentNullException(nameof(configuration));

        var config = new TConfig();
        configuration.Bind(config);
        services.AddSingleton(config);
        return config;
    }
}

将其应用于配置:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.ConfigurePOCO<MySettings>(Configuration.GetSection("MySettings"));
}

然后使用它:

public class DummyService
{
    public DummyService(MySettings settings)
    {
        //do stuff
    }
}

1
实际上,我通过在“ConfigureServices”中应用“Bind”解决了这个问题,但我认为将其作为“IServiceCollection”的扩展具有吸引力和清晰度,我实际上会使用这种方法进行重构,所以我也接受你的答案。 - mmix
这段代码实际上是没有必要的。IServiceCollection会处理配置节的转换,就像我在下面的答案中所述一样。传统上,服务的空值检查是在使用者级别(即构造函数)完成的,因此我不认为此处增加了价值。 - pim

1
我之前遇到了这个问题,如果你能称之为问题的话。当我们看到依赖列表时,我们都会感到有点震惊。但正如@Tseng所提到的那样,包含一堆额外的微小程序集并不是什么大问题(它们将已经通过另一个项目的引用在bin中被包含)。但我承认,为了选项界面而不得不包含它们确实很烦人。

我解决它的方法是在startup.cs中解析服务依赖关系,并相应地调整服务的构造函数:

services.AddTransient<MyService>(Configuration.GetConfiguration("MyConfiguration"));

我遇到的问题并不是因为太过震惊,而是这些库不仅仅是ASP.NET库,它们还被WPF项目和许多传统WinForms所共享。将这些依赖项放入业务类中对我们来说毫无意义,因为它们将成为其他项目的依赖项。 - mmix
我理解你的想法并完全赞同。我喜欢构建具有尽可能低的依赖性的服务。因此,我喜欢直接注入MyConfiguration而不是IOptions<MyConfiguration> - pim

0

如果您不关心 IOptions 提供的任何内容,为什么不直接将 IConfiguration 注入到您的服务中呢?

public class MyService
{
    private readonly IConfiguration _config;

    public MyService(IConfiguration config)
    {
        _config = config;
    }

    public void DoSomething()
    {
        var value = _config["SomeKey"];

        // doing something
    }
}

6
我认为,注入IConfiguration比注入IOption<T>还要糟糕。它相当于使用服务定位器模式来获取配置值,因为它完全隐藏了所使用的配置值,而不是在构造函数中明确地声明它们。这使得类需要负责读取和转换这些值,而不是应用程序启动路径。我甚至认为注入IConfiguration(或者任何生存在组合根之外的类)都算是一种反模式。 - Steven
我同意这条评论。此外,IConfiguration 也是一个不必要的依赖项,它带有其他东西。 - mmix
除了将 IConfiguration 注入和额外的逻辑放入类中之外,问题在于配置键。 对于简单结构,您可以使用 var value = _config["SomeKey"];,但在更复杂的配置中,您可能会得到 var value = _config["ParentKey:ChildKey:ChildKey"]; - Slawomir Brys

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接