Options選項

来源:https://www.cnblogs.com/louzixl/archive/2023/12/03/17872541.html
-Advertisement-
Play Games

選項用來提供對相關設置的強類型訪問,讀取配置首選使用選項模式。選項無法脫離容器使用,依賴容器,實現了選項不同的訪問方式。選項模式使用了泛型包裝器,因此具備瞭如下優點: 不需要顯示註冊選項具體類型,只需要將泛型包裝器註入到容器中; 對於選項實例的評估推遲到獲取IOptions.Value時進行,而不是 ...


選項用來提供對相關設置的強類型訪問,讀取配置首選使用選項模式。選項無法脫離容器使用,依賴容器,實現了選項不同的訪問方式。選項模式使用了泛型包裝器,因此具備瞭如下優點:

  • 不需要顯示註冊選項具體類型,只需要將泛型包裝器註入到容器中;
  • 對於選項實例的評估推遲到獲取IOptions.Value時進行,而不是在註入時進行,這樣就可以獲取不同生命周期的選項;
  • 可以對選項進行泛型約束;

選項註入

選項模式向容器中註入了三種類型的選項泛型包裝器:IOptions<>,IOptionsSnapshot<>,IOptionsMonitor<>。其中IOptionsSnapshot<>被註冊為Scoped。註入了IOptionsFactory<>泛型選項工廠,用來創建選項實例。註入了IOptionsMonitorCache<>,由IOptionsMonitor<>用於緩存泛型實例。

public static IServiceCollection AddOptions(this IServiceCollection services)
{
    if (services == null)
        throw new ArgumentNullException(nameof(services));

    services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(UnnamedOptionsManager<>)));
    services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
    services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
    services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
    services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));
    return services;
}

選項不同生命周期

不同類型的選項介面功能不同:

  • IOptions<>:實現類為UnnamedOptionsManager,其中TOptions欄位保存選項實例(不支持命名選項)。在首次獲取Value時,會調用工廠創建選項實例。因為被註冊為單例,因此無法識別配置修改。
  • IOptionsSnapshot<>:實現類為OptionsManager,其中OptionsCache欄位(私有,非容器獲取)保存選項實例(支持命名選項)。被註冊為範圍,在範圍內首次獲取Value時,會調用工廠創建選項實例,並將其保存到私有的OptionsCache中,在範圍內選項值不變,不同範圍內選項值根據獲取時配置文件的不同而不同。
  • IOptionsMonitor<>:實現類為OptionsMonitor,其中IOptionsMonitorCache欄位,該欄位的值是從容器中解析的,用來緩存選項實例。OptionsMonitor還註入了IOptionsChangeTokenSource列表,可以監聽配置源的修改。當監聽到修改時,調用工廠重新創建選項以刷新選項值。
internal sealed class UnnamedOptionsManager<[DynamicallyAccessedMembers(
    Options.DynamicallyAccessedMembers)] TOptions> : IOptions<TOptions>
    where TOptions : class
{
    private readonly IOptionsFactory<TOptions> _factory;
    private volatile object _syncObj;
    private volatile TOptions _value;

    public UnnamedOptionsManager(IOptionsFactory<TOptions> factory) => _factory = factory;

    public TOptions Value
    {
        get
        {
            if (_value is TOptions value)
                return value;

            lock (_syncObj ?? Interlocked.CompareExchange(ref _syncObj, new object(), null) ?? _syncObj)
            {
                return _value ??= _factory.Create(Options.DefaultName);
            }
        }
    }
}
public class OptionsManager<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions> 
    : IOptions<TOptions>, IOptionsSnapshot<TOptions>
    where TOptions : class
{
    private readonly IOptionsFactory<TOptions> _factory;
    private readonly OptionsCache<TOptions> _cache = new OptionsCache<TOptions>(); 
        
    public OptionsManager(IOptionsFactory<TOptions> factory)
    {
        _factory = factory;
    }

    public TOptions Value => Get(Options.DefaultName);

    public virtual TOptions Get(string name)
    {
        name = name ?? Options.DefaultName;

        if (!_cache.TryGetValue(name, out TOptions options))
        {
            IOptionsFactory<TOptions> localFactory = _factory;
            string localName = name;
            options = _cache.GetOrAdd(name, () => localFactory.Create(localName));
        }

        return options;
    }
}
public class OptionsMonitor<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions> 
    : IOptionsMonitor<TOptions>, IDisposable
    where TOptions : class
{
    private readonly IOptionsMonitorCache<TOptions> _cache;
    private readonly IOptionsFactory<TOptions> _factory;
    private readonly List<IDisposable> _registrations = new List<IDisposable>();
    internal event Action<TOptions, string> _onChange;

    public OptionsMonitor(IOptionsFactory<TOptions> factory,
        IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache)
    {
        _factory = factory;
        _cache = cache;

        void RegisterSource(IOptionsChangeTokenSource<TOptions> source)
        {
            IDisposable registration = ChangeToken.OnChange(
                      () => source.GetChangeToken(),
                      (name) => InvokeChanged(name),
                      source.Name);

            _registrations.Add(registration);
        }
        
        // 此處簡寫
        foreach (IOptionsChangeTokenSource<TOptions> source in sources)
        {
            RegisterSource(source);
        }
    }

    private void InvokeChanged(string name)
    {
        name = name ?? Options.DefaultName;
        _cache.TryRemove(name);
        TOptions options = Get(name);
        if (_onChange != null)
            _onChange.Invoke(options, name);
    }

    public TOptions CurrentValue
    {
        get => Get(Options.DefaultName);
    }

    public virtual TOptions Get(string name)
    {
        name = name ?? Options.DefaultName;
        return _cache.GetOrAdd(name, () => _factory.Create(name));
    }

    public IDisposable OnChange(Action<TOptions, string> listener)
    {
        var disposable = new ChangeTrackerDisposable(this, listener);
        _onChange += disposable.OnChange;
        return disposable;
    }

    public void Dispose()
    {
        foreach (IDisposable registration in _registrations)
        {
            registration.Dispose();
        }

        _registrations.Clear();
    }
}

選項配置

選項模式提供了IConfigureOptions<>、IConfigureNamedOptions<>和IPostConfigureOptions<>介面,用來對選項進行配置。IConfigureNamedOptions<>繼承了IConfigureOptions<>介面,增加了命名選項配置功能。這三個介面中都有一個對選項配置的方法,將介面註入到容器中,當調用工廠創建選項時,會調用介面中的配置方法對選項進行配置。首先會調用IConfigureOptions<>、IConfigureNamedOptions<>介面中的配置方法,然後調用IPostConfigureOptions<>介面中的配置方法。

// OptionsFactory<>
public TOptions Create(string name)
{
    TOptions options = CreateInstance(name);
    foreach (IConfigureOptions<TOptions> setup in _setups)
    {
        if (setup is IConfigureNamedOptions<TOptions> namedSetup)
            namedSetup.Configure(name, options);
        else if (name == Options.DefaultName)
            setup.Configure(options);
    }
    foreach (IPostConfigureOptions<TOptions> post in _postConfigures)
    {
        post.PostConfigure(name, options);
    }

    // 選項驗證...

    return options;
}

protected virtual TOptions CreateInstance(string name)
{
    return Activator.CreateInstance<TOptions>();
}

選項驗證

選項模式提供了IValidateOptions<>介面,包含一個Validate方法對選項進行驗證。將介面註入容器中,當調用工廠創建選項時,會調用介面中的Validate方法對選項進行驗證。

// OptionsFactory<>
public TOptions Create(string name)
{
    TOptions options = CreateInstance(name);
    // 選項配置...

    if (_validations.Length > 0)
    {
        var failures = new List<string>();
        foreach (IValidateOptions<TOptions> validate in _validations)
        {
            ValidateOptionsResult result = validate.Validate(name, options);
            if (result is not null && result.Failed)
                failures.AddRange(result.Failures);
        }
        if (failures.Count > 0)
            throw new OptionsValidationException(name, typeof(TOptions), failures);
    }

    return options;
}

如果需要為選項添加驗證,實現IValidateOptions<>介面並註入到容器即可:

builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton
    <IValidateOptions<SettingsOptions>, ValidateSettingsOptions>());

也可以添加DataAnnotations驗證,調用ValidateDataAnnotations擴展方法即可,該方法定義在 Microsoft.Extensions.Options.DataAnnotations中。需要先調用AddOptions<>擴展方法創建OptionsBuilder<>:

builder.Services.AddOptions<SettingsOptions>()
    .Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
    .ValidateDataAnnotations();

綁定配置

通過如下擴展方法將選項綁定到配置:

builder.Services.Configure<SettingsOptions>(builder.Configuration.GetSection("Settings"));

綁定到配置是通過IConfiguration.Bind()擴展方法實現的,同時,也添加了對配置修改的監聽:

public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, string name, IConfiguration config, Action<BinderOptions> configureBinder) where TOptions : class
{
    if (services == null)
        throw new ArgumentNullException(nameof(services));

    if (config == null)
        throw new ArgumentNullException(nameof(config));

    services.AddOptions();
    services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(
        new ConfigurationChangeTokenSource<TOptions>(name, config));
    return services.AddSingleton<IConfigureOptions<TOptions>>(
        new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder));
}
轉載請註明出處,歡迎交流。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 項目背景: vue 1.創建 backtop.vue 的回到頂部邏輯的組件 <template> <transition name="back-up-fade"> <div class="back-top" :style="{ bottom: bottom + 'px', right: right ...
  • 理解 async/await 的原理和使用方法是理解現代JavaScript非同步編程的關鍵。這裡我會提供一個詳細的實例,涵蓋原理、流程、使用方法以及一些註意事項。代碼註釋會儘量詳盡,確保你理解每個步驟。 實例:使用async/await進行非同步操作 <!DOCTYPE html> <html lan ...
  • 本文檔譯自 www.codeproject.com 的文章 "Calling Conventions Demystified",作者 Nemanja Trifunovic,原文參見此處 引言 - Introduction 在學習 Windows 編程的漫長、艱難而美妙的旅途中,你可能會對函數聲明前出 ...
  • 如何使用mysql實現可重入的分散式鎖 目錄 什麼是分散式鎖? 如何實現分散式鎖? 定義分散式表結構 定義鎖統一介面 使用mysql來實現分散式鎖 ① 生成線程標記ID ② 加鎖 ③ 解鎖 ④ 重置鎖 寫在最後 1. 什麼是分散式鎖? 百度百科:分散式鎖是控制分散式系統之間同步訪問共用資源的一種方式 ...
  • Scikit-learn是一個基於Python的開源機器學習庫,它提供了大量的機器學習演算法和工具,方便用戶進行數據挖掘、分析和預測。 Scikit-learn是基於另外兩個知名的庫 Scipy 和 Numpy的,關於 Scipy 和 Numpy 等庫,之前的系列文章中有介紹: Scipy 基礎系列 ...
  • 第二部分主要涵蓋了 SpringMVC 中作用域處理,介紹了 Request 作用域、Session 作用域和應用作用域的處理方式,以及 @ModelAttribute 註解的使用和 ModelAndView 的使用方法;最後,探討了靜態資源的處理方式,包括使用 DefaultServlet 或者 ... ...
  • acwing week2 基礎演算法3總結 總結點1:雙指針演算法 //常用模版框架 for (int i = 0, j = 0; i < n; i ++ ) { while (j < i && check(i, j)) j ++ ; } 常見問題分類: (1) 對於一個序列,用兩個指針維護一段區間 ( ...
  • 小市值選股策略的核心在於通過綜合分析公司的基本面、行業定位、財務健康狀況以及市場趨勢, 來尋找那些被市場低估但具備顯著成長潛力的股票,同時也要重視風險管理和投資組合的多樣化。 今天來給大家分享下小市值策略代碼如下: # 顯式導入 BigQuant 相關 SDK 模塊 from bigdatasour ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...