簡體   English   中英

IOptions 注入

[英]IOptions Injection

在我看來,讓域服務需要一個IOptions<T>實例來傳遞它的配置是一個壞主意。 現在我必須將額外的(不必要的?)依賴項拉入庫中。 我在網上看到了很多注入IOptions的例子,但我沒有看到它的額外好處。

為什么不直接將實際的 POCO 注入到服務中呢?

    services.AddTransient<IConnectionResolver>(x =>
    {
        var appSettings = x.GetService<IOptions<AppSettings>>();

        return new ConnectionResolver(appSettings.Value);
    });

或者甚至使用這種機制:

        AppSettings appSettings = new AppSettings();

        Configuration.GetSection("AppSettings").Bind(appSettings);

        services.AddTransient<IConnectionResolver>(x =>
        {      
             return new ConnectionResolver(appSettings.SomeValue);
        });

設置的使用:

public class MyConnectionResolver 
{
     // Why this?
     public MyConnectionResolver(IOptions<AppSettings> appSettings)
     {
           ... 
     }

     // Why not this?
     public MyConnectionResolver(AppSettings appSettings)
     {
           ... 
     }
     
     // Or this
     public MyConnectionResolver(IAppSettings appSettings)
     {
           ... 
     }
}

為什么需要額外的依賴? IOptions給我買了什么,而不是老派的注射方式?

從技術上講,沒有什么可以阻止您使用 ASP.NET Core 的依賴注入注冊 POCO 類或創建包裝類並IOption<T>.Value返回IOption<T>.Value

但是您將失去 Options 包的高級功能,即在源更改時自動更新它們,如您在此處的源中所見。

正如您在該代碼示例中看到的,如果您通過services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));注冊您的選項services.Configure<AppSettings>(Configuration.GetSection("AppSettings")); 它將讀取 appsettings.json 中的設置並將其綁定到模型中,並另外跟蹤它的更改。 當appsettings.json編輯,並重新綁定模型與新值看到這里

當然,您需要自己決定,是否要將一些基礎結構泄漏到您的域中或傳遞Microsoft.Extensions.Options包提供的額外功能。 它是一個非常小的包,與 ASP.NET Core 無關,因此可以獨立於它使用。

Microsoft.Extensions.Options包足夠小,它只包含抽象和具體的services.Configure重載,用於IConfiguration (與獲取配置的方式、命令行、json、環境、azure 密鑰庫等更緊密相關)。 ) 是一個單獨的包。

總而言之,它對“基礎設施”的依賴非常有限。

雖然使用IOption是官方的做事方式,但我似乎無法IOption這樣一個事實,即我們的外部庫不需要了解關於 DI 容器或其實現方式的任何信息。 IOption似乎違反了這個概念,因為我們現在告訴我們的類庫一些關於 DI 容器將注入設置的方式 - 我們應該只注入由該類定義的 POCO 或接口。

這讓我非常惱火,以至於我編寫了一個實用程序將 POCO 注入到我的類庫中,該類庫填充了來自 appSettings.json 部分的值。 將以下類添加到您的應用程序項目中:

public static class ConfigurationHelper
{
    public static T GetObjectFromConfigSection<T>(
        this IConfigurationRoot configurationRoot,
        string configSection) where T : new()
    {
        var result = new T();

        foreach (var propInfo in typeof(T).GetProperties())
        {
            var propertyType = propInfo.PropertyType;
            if (propInfo?.CanWrite ?? false)
            {
                var value = Convert.ChangeType(configurationRoot.GetValue<string>($"{configSection}:{propInfo.Name}"), propInfo.PropertyType);
                propInfo.SetValue(result, value, null);
            }
        }

        return result;

    }
}

可能可以進行一些改進,但是當我使用簡單的字符串和整數值對其進行測試時,它運行良好。 這是我在應用程序項目的 Startup.cs -> ConfigureServices 方法中為名為DataStoreConfiguration的設置類和同名的 appSettings.json 部分使用它的示例:

services.AddSingleton<DataStoreConfiguration>((_) =>
    Configuration.GetObjectFromConfigSection<DataStoreConfiguration>("DataStoreConfiguration"));

appSettings.json 配置如下所示:

{
  "DataStoreConfiguration": {
    "ConnectionString": "Server=Server-goes-here;Database=My-database-name;Trusted_Connection=True;MultipleActiveResultSets=true",
    "MeaningOfLifeInt" : "42"
  },
 "AnotherSection" : {
   "Prop1" : "etc."
  }
}

DataStoreConfiguration類在我的庫項目中定義,如下所示:

namespace MyLibrary.DataAccessors
{
    public class DataStoreConfiguration
    {
        public string ConnectionString { get; set; }
        public int MeaningOfLifeInt { get; set; }
    }
}

通過此應用程序和庫配置,我能夠使用構造函數注入直接將 DataStoreConfiguration 的具體實例注入到我的庫中,而無需IOption包裝器:

using System.Data.SqlClient;

namespace MyLibrary.DataAccessors
{
    public class DatabaseConnectionFactory : IDatabaseConnectionFactory
    {

        private readonly DataStoreConfiguration dataStoreConfiguration;

        public DatabaseConnectionFactory(
            DataStoreConfiguration dataStoreConfiguration)
        {
            // Here we inject a concrete instance of DataStoreConfiguration
            // without the `IOption` wrapper.
            this.dataStoreConfiguration = dataStoreConfiguration;
        }

        public SqlConnection NewConnection()
        {
            return new SqlConnection(dataStoreConfiguration.ConnectionString);
        }
    }
}

解耦是 DI 的一個重要考慮因素,所以我不知道為什么微軟要讓用戶將他們的類庫耦合到像IOptions這樣的外部依賴IOptions ,無論它看起來多么微不足道,或者它應該提供什么好處。 我還建議IOptions一些好處看起來像是過度設計。 例如,它允許我動態更改配置並跟蹤更改 - 我已經使用了其他三個包含此功能的 DI 容器,但我從未使用過一次......同時,我幾乎可以向您保證團隊會想要將 POCO 類或接口注入庫中以替換ConfigurationManager ,經驗豐富的開發人員不會對無關的包裝器接口感到高興。 我希望在 ASP.NET Core 的未來版本中包含一個類似於我在此處描述的實用程序,或者有人為我提供了一個令人信服的論據,說明我為什么錯了。

信用

有了這個兩個簡單的線條在startup.csConfigureServices ,你可以注入IOptions值,如:

public void ConfigureServices(IServiceCollection services)
{
    //...
    services.Configure<AppSettingsConfig>(Configuration.GetSection("AppSettings"));
    services.AddScoped(cfg => cfg.GetService<IOptions<AppSettingsConfig>>().Value);
}

然后使用:

 public MyConnectionResolver(AppSettings appSettings)
 {
       ... 
 }

我也無法忍受 IOptions 的建議。 將這一點強加給開發人員是一種蹩腳的設計。 IOptions 應該清楚地記錄為可選,哦,諷刺的是。

這就是我為我的配置值所做的

var mySettings = new MySettings();
Configuration.GetSection("Key").Bind(mySettings);

services.AddTransient(p => new MyService(mySettings));

您保留強類型並且不需要在您的服務/庫中使用 IOptions。

你可以這樣做:

services.AddTransient(
    o => ConfigurationBinder.Get<AppSettings>(Configuration.GetSection("AppSettings") 
);

使用 Net.Core v.2.2,它對我有用。

或者,使用IOption<T>.Value

它看起來像這樣

services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM