添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
  • 使用 IOptions 來註冊 & 注入
  • 補充:AddOptions & 對 IOptions 加入驗證
  • 補充:IOptions、IOptionsMonitor、IOptionsSnapshot
  • IOptions
  • IOptionsMonitor
  • IOptionsSnapshot
  • 附錄:試一下 IOptionsMonitor
  • 認識 Appsettings.json

    首先簡單介紹一下 appsettings.json 是在幹嘛的:

    我們開發的時候,常常會需要先設定好一些服務參數、組態設定之類的設定值,再用這些設定值用來控制我們程式的某些行為。例如:

  • 「某功能的上限值是 10
  • 「某項開關在測試環境是 false
  • 「某服務信件的發送者要用 noreply9527
  • 另外常見的還有連線字串、寫 Log 時的 logging level 等等。

    這些設定值會和程式碼拆開,放在設定檔集中管理 ,再讓程式碼從設定檔讀取相關的設定值來用就好,藉此來把設定值的管理和使用做個關注點分離。

    有了設定檔,要調整修改也比較方便:要增加或是修改設定值,都只要先往設定檔衝就行。並且因為兩邊拆開了,我們也就可以簡單地替換這些設定值來應對不同狀況(例如正式環境和測試環境套用兩組不同的設定檔,或是在自己電腦測試的時候快速改個值之類的),彈性可說是 UPUP!

    更重要的是,這樣我們就不需要把一大堆東西寫死在程式碼的各個地方,也就不會要改個值還要先搜尋整個專案再一個一個挖出來改了。 我按 Shift Ctrl F 已經按到哭

    把設定值抽出去丟到設定檔之後,我們就得到了:集中管理設定值、方便修改和替換、減少程式碼中又重複又寫死的臭東西等等好處。

    在 .Net Core 的世界裡,這個設定檔就是 appsettings.json
    前面提到的像是日誌等級、功能旗標之類的這些設定值,就會放在 appsettings.json 裡面。

    而當我們想要從 appsettings.json 把這些設定值給讀出來的時候,
    就可以使用我們的 IOptions 啦!

    使用 IOptions 來註冊 & 注入

    提醒:這篇的示範會用到一些些 .Net 依賴注入(DI)相關的操作。沒接觸過的朋友可以考慮先閱讀 菜雞新訓記:依賴注入

    假設我們有個專案,叫做大漢防禦管理系統。專案內的 appsettings.json 有以下內容:

    "StrongholdInfo" : { "Index" : 49 , "Name" : "劍閣" , "Enabled" : true , "General" : [ "姜維" , "廖化" , "張翼" ,

    我們正好在開發一個新功能,需要抓到這段設定值。現在就來示範一下:

    首先,讓我們建立一個類別,等等用來放設定值內容(通常後綴會用 Options)

    /// <summary>
    /// 關隘資訊
    /// </summary>
    public class StrongholdInfoOptions
        /// <summary>
        /// 關隘編號
        /// </summary>
        public int Index { get; set; }
        /// <summary>
        /// 關隘名稱
        /// </summary>
        public string Name { get; set; } = string.Empty;
        /// <summary>
        /// 關隘啟用狀態
        /// </summary>
        public bool Enabled { get; set; }
        /// <summary>
        /// 駐守人員
        /// </summary>
        public string[]? General { get; set; }
    

    接著使用 Configure<T> 來註冊,並且用 Configuration.GetSection 來指定這段設定值在 appsettings.json 裡的位置:

    // 原本就有的一些註冊...
    builder.Services.AddControllers();
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
    // 使用 Configure 註冊 Option
    builder.Services.Configure<StrongholdInfoOptions>(
        builder.Configuration.GetSection("StrongholdInfo"));
    

    註冊好了之後,就可以使用 IOptions<T> 注入到我們要用的地方囉

    [ApiController] [Route("[controller] ")] public class DemoController : ControllerBase private readonly StrongholdInfoOptions _info; // 使用 IOptions<T> 注入,並用 .Value 取得內容 public DemoController( IOptions<StrongholdInfoOptions> info) _info = info.Value; [HttpGet("IOption")] public object Get() return _info;

    收工,搞定。就是這麼簡單。

    補充:AddOptions & 對 IOptions 加入驗證

    前面我們在註冊時,是使用 Configure

    // 使用 Configure 註冊 Option
    builder.Services.Configure<StrongholdInfoOptions>(
        builder.Configuration.GetSection("StrongholdInfo"));
    

    其實也可以使用 AddOptions

    // 使用 Configure 註冊 Option
    builder.Services.Configure<StrongholdInfoOptions>(
        builder.Configuration.GetSection("StrongholdInfo"));
    // 或是使用 AddOptions,這兩個做法最後都會呼叫 Configure<StrongholdInfoOptions>
    // see: https://github.com/dotnet/extensions/issues/514
    // ps: `BindConfiguration` 是比較新的語法,以前會使用 `Bind() + builder.Configuration.GetSection()`
    builder.Services
        .AddOptions<StrongholdInfoOptions>()
        .BindConfiguration(StrongholdInfoOptions.SectionName);
    

    這兩個語法最終會做同樣的事情,因為 Bind() 會去呼叫 Configure()

  • c# - 在 ASP.NET Core 中加載配置時,services.Configure() 和 services.AddOptions ().Bind() 之間有什麼區別?- Stack Overflow
  • Question: AddOptions () vs. Multiple Configure (…) · Issue #514 · dotnet/extensions (github.com)
  • 只是 AddOptions 比較晚出現,並且後來又加入了更多自定義,用起來比較靈活。

    例如我們可以用 ValidateDataAnnotations 來啟用屬性驗證:
    (可參考 Microsoft Learn 選項模式 的「選項驗證」小節)

    [Required]
    [RegularExpression(@"^[\u4e00-\u9fa5]{1,10}$")] // 限定 1~10 個中文字
    public string Name { get; set; }
    
    builder.Services
        .AddOptions<SettingsOptions>()
        .Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
        .ValidateDataAnnotations()  // 會在呼叫 .value 的時候進行驗證
     // .Validate(config => {})     // 也可以自訂驗證邏輯
     // .ValidateOnStart()          // 也可以要求在啟動時就驗證
    
    // 假如加了 ValidateDataAnnotations 的話,取值驗證失敗會噴 OptionsValidationException
        SettingsOptions options = _config.Value;
    catch (OptionsValidationException ex)
        foreach (string failure in ex.Failures) // 畢竟可能一堆東西沒通過驗證嘛
            _logger.LogError("Validation error: {FailureMessage}", failure);
    

    如果需要自訂驗證器,可以搜尋 IValidateOptions ,可以自己實作 Validate() ,但我個人還沒遇到這麼複雜的狀況,這邊就不詳述。

    Configure 直接又簡單,但 AddOptions 比較靈活。
    我個人比較喜歡粗暴直接的做法,所以目前是都直接呼叫 Configure 而已。供各位參考

    補充:IOptions、IOptionsMonitor、IOptionsSnapshot

    提了 IOptions 就不能不提他的兩位哥哥:IOptionsMonitor、IOptionsSnapshot

    [ApiController]
    public class OptionsMonitorDemoController : ControllerBase
        // 我們有三種 IOptions 相關的介面來取得設定檔內容
        private readonly IOptions<StrongholdInfoOptions> _options;
        private readonly IOptionsMonitor<StrongholdInfoOptions> _optionsMonitor;
        private readonly IOptionsSnapshot<StrongholdInfoOptions> _optionsSnapshot;
    

    這邊也迅速筆記一下。有興趣的朋友可以直接閱讀相關文章:

  • IOptions、IOptionsMonitor 以及 IOptionsSnapshot - wenhx - 博客园
  • ASP.NET Core 中的選項模式 | Microsoft Learn 的「選項介面」小節
  • IOptions

    用起來最簡單方便,個人推👍

    IOptions 會註冊為 Singleton,所以大家都會用同一組。也只有第一次建立的時候會抓設定檔的內容。後面就算跑去偷改檔案,也不會被影響(想更新?重啟站台吧)

    如果設定檔不太常改動的話,直接用 IOptions 簡單做一做是最方便的,也省資源。

    IOptionsMonitor

    IOptionsMonitor 同樣也會註冊為 Singleton,但是它會去偷聽設定檔有沒有更新。
    當設定檔有更新的時候 IOptionsMonitor 也會一起更新,所以能夠隨時取得目前版本的設定值

    取值的方法名稱也很明確表達這點,大家都是 _options.Value
    但 IOptionsMonitor 的是 _options.CurrentValue

    _options = _options.Value,
    _optionsMonitor = _optionsMonitor.CurrentValue,
    

    如果我們的功能非常依賴設定值,而且又希望隨時更新(像留言區 Cash 大補充的 Hot Reload)的時候,就可以考慮使用 IOptionsMonitor。

    但要小心如果 API 正在處理 Request,然後又剛好正在修改設定檔的話,可能會有一些靈異現象(?)

    弄這篇筆記的時候也動手試了一下 IOptionsMonitor,但有點小佔版面,就放在最後的附錄(讓我之後可以回來抄)了。

    IOptionsSnapshot

    IOptionsSnapshot 會註冊為 Scope,所以每個請求進來的時候,都會各自去拿一次目前的設定檔內容,並且就用這一份設定檔內容處理這一次請求

    我個人感覺最中規中矩。吃得到設定檔的變動,但也不會像 IOptionsMonitor
    搞到前一秒還是 true 下一秒就是 false🤔

    如果有改動 Config 的需求,又能接受下一組 Request 進來才吃到的時候(或是希望不要發生靈異現象),就可以考慮使用 IOptionsSnapshot。

    大概這樣。但我個人平常都還是 IOptions 優先,
    如果真的有需要即時反應設定檔的變動時,再把另外兩個拿出來討論吧。

    前面介紹了 .Net Core 裡 appsettings.json IOptions 的基本操作,也順便補充了一些簡單介紹。其他相關的操作,就放在延伸閱讀這邊,有興趣的朋朋們可以看看。

    如果想根據不同環境(Dev, Prd 之類的)切換不同 appsettings.json:
    ASP.NET Core 依環境載入不同 appsetting.json 設定 - 黑暗執行緒

    如果你正在搞功能旗標(Feature Flag/Feature Toggle)然後看到這篇的話,
    也可以嘗試看看 FeatureManagement
    .Net: 使用 FeatureManagement 套件來實作 Feature Flag 功能切換吧

    如果覺得到處都是 IOptions,想要降低對 IOptions 的依賴的話,可以綁到強型別裡:
    [NETCore] ASP.NET Core 使用強型別取代 IOption 注入配置 ~ m@rcus 學習筆記

  • ASP.NET Core 的設定 | Microsoft Learn
  • ASP.NET Core 中的選項模式 | Microsoft Learn
  • [.NETCore] 如何取得 appsettings.json 組態設定 ~ m@rcus 學習筆記 (marcus116.blogspot.com)
  • IOptions、IOptionsMonitor以及IOptionsSnapshot - wenhx - 博客园 (cnblogs.com)
  • .NET Configuration with IOptions, IOptionsMonitor, and IOptionsSnapshot | by Ludmal De Silva | Medium
  • 附錄:試一下 IOptionsMonitor

    簡單比較一下修改 appsettings.json 後,IOptions 和 IOptionsMonitor 的資料差異,方便我以後需要複製貼上,或是哪天需要甩給朋友時使用。

    public OptionsMonitorDemoController(
        IOptions<StrongholdInfoOptions> options, 
        IOptionsMonitor<StrongholdInfoOptions> optionsMonitor)
        _options = options;
        _optionsMonitor = optionsMonitor;
    [HttpGet("api/Demo/GetWithMonitor")]
    public IEnumerable<object> GetWithMonitor()
        var before = new
            OptionsName = _options.Value.Name,
            OptionsMonitorName = _optionsMonitor.CurrentValue.Name,
        // 在這裡下中斷點,打開 appsettings.json 手動改資料
        // 把 "Name": "劍閣" 改成 "Name": "羅馬"
        System.Threading.Thread.Sleep(1000);
        var after = new
            OptionsName = _options.Value.Name,
            OptionsMonitorName = _optionsMonitor.CurrentValue.Name,
        return new object[] { before, after };
        //   {
        //     "optionsName": "劍閣",
        //     "optionsMonitorName": "劍閣"
        //   },
        //   {
        //     "optionsName": "劍閣",
        //     "optionsMonitorName": "羅馬"
        //   }
        
  • 使用 IOptions 來註冊 & 注入
  • 補充:AddOptions & 對 IOptions 加入驗證
  • 補充:IOptions、IOptionsMonitor、IOptionsSnapshot
  • IOptions
  • IOptionsMonitor
  • IOptionsSnapshot
  • 附錄:試一下 IOptionsMonitor
  •