.Net Options 選項


DotNet Configuration 源碼:https://github.com/dotnet/runtime/tree/master/src/libraries/,其中以 Microsoft.Extensions.Options 開頭的項目
Options 官方文檔:

Nuget包:Microsoft.Extensions.Options

建議先掌握:

  • DependencyInjection ( 依賴注入 ):
  • Configuration ( 配置 ) :

介紹

Options提供了對配置數據的強類型訪問

Options提供了驗證選項的機制

Configure

首先聲明一個選項類

public class MyOption
{
    public string A { get; set; }

    public string B { get; set; }
}

使用Configure擴展方法對選項類進行配置

var serviceCollection = new ServiceCollection();

// 配置 MyOption
serviceCollection.Configure<MyOption>(n =>
{
    n.A = "A Value";
    n.B = "B Value";
});

var serviceProvider = serviceCollection.BuildServiceProvider();

獲取IOptions<MyOption>選項服務

var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
MyOption myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A);
Console.WriteLine(myOptionValue.B);

在Aps.Net 中使用

配置

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.Configure<MyOption>(option =>
    {
        option.A = "A Value";
        option.B = "A Value";
    });
}

注入Ioption<MyOption>

[Route("Home")]
public class HomeController : ControllerBase
{
    private readonly MyOption _myOption;

    public HomeController(IOptions<MyOption> myOptions)
    {
        _myOption = myOptions.Value;
    }

    [HttpGet]
    public string GetAsync()
    {
        return $"A:{_myOption.A},B:{_myOption.B}";
    }
}

綁定配置,使用IConfiguration來配置選項

Nuget包:Microsoft.Extensions.Options.ConfigurationExtensions

Configure<TOptions>(IConfigureation configuration)不僅僅會綁定配置,還會添加對配置文件的更改通知,通知IOptionsMonitor重新計算選項

實例:

appsettings.json

{
  "MySetting": {
    "A": "A Value",
    "B": "B Value"
  }
}

C#

IConfiguration configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();
var serviceCollection = new ServiceCollection();
serviceCollection.Configure<MyOption>(configuration.GetSection("MySetting"));
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
var myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A);
Console.WriteLine(myOptionValue.B);

PostConfigure

PostConfigure 會在 Configure 之后進行配置

實例:

var serviceCollection = new ServiceCollection();
serviceCollection.PostConfigure<MyOption>(option =>
{
    option.B = "PostConfigure B Value";
});

serviceCollection.Configure<MyOption>(n =>
{
    n.A = "A Value";
    n.B = "B Value";
});

var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
MyOption myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A); // A Value
Console.WriteLine(myOptionValue.B); // PostConfigure B Value

命名選項

可以給選項命名

默認選項名稱為空字符串

IOptions 不支持獲取命名選項,只能獲取默認命名選項

可以使用IOptionsSnapshotIOptionsMonitor等獲取命名選項

IOptionsSnapshot繼承子IOptions並添加了Get(string optionName)方法來獲取命名選項

IOptionsMonitorCurrentValue屬性用於獲取默認選項,Get(string optionName)方法來獲取命名選項

實例:

var serviceCollection = new ServiceCollection();
// 配置 MyOption
serviceCollection.Configure<MyOption>(n =>
{
    n.A = "A Value";
    n.B = "B Value";
});
serviceCollection.Configure<MyOption>("My", n =>
{
    n.A = "My:A Value";
    n.B = "My:B Value";
});
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
MyOption myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A); // A Value
Console.WriteLine(myOptionValue.B); // B Value

// IOptionsSnapshot 獲取命名選項
var myOption2 = serviceProvider.GetRequiredService<IOptionsSnapshot<MyOption>>();
MyOption myOptionValue2 = myOption2.Get("My");
Console.WriteLine(myOptionValue2.A); // My:A Value
Console.WriteLine(myOptionValue2.B); // My:B Value

// IOptionsMonitor 獲取命名選項
var myOptionsMonitor = serviceProvider.GetRequiredService<IOptionsMonitor<MyOption>>();
MyOption myOptionValue3 = myOptionsMonitor.Get("My");
Console.WriteLine(myOptionValue3.A);
Console.WriteLine(myOptionValue3.B);

ConfigureAll、PostConfigureAll

ConfigureAllPostConfigureAll可以配置全部的命名配置

實例:

var serviceCollection = new ServiceCollection();
serviceCollection.ConfigureAll<MyOption>(n =>
{
    n.A = "A Value,Config By ConfigureAll";
});
// 配置 MyOption
serviceCollection.Configure<MyOption>(n =>
{
    n.B = "B Value";
});
serviceCollection.Configure<MyOption>("My", n =>
{
    n.B = "My:B Value";
});

var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
MyOption myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A); // A Value,Config By ConfigureAll
Console.WriteLine(myOptionValue.B); // B Value

var myOption2 = serviceProvider.GetRequiredService<IOptionsSnapshot<MyOption>>();
MyOption myOptionValue2 = myOption2.Get("My");
Console.WriteLine(myOptionValue2.A); // A Value,Config By ConfigureAll
Console.WriteLine(myOptionValue2.B); // My:B Value

IOptions、IOptionsSnapshot、IOptionsMonitor

IOptions<TOptions>

  • 只會計算一次選項
  • 不支持命名選項
  • 單例服務,可以注入到任何服務生存期

IOptionsSnapshot<TOptions>

  • 會計算多次選項,每次請求時應重新計算選項
  • 注冊為范圍內,因此無法注入到單一實例服務
  • 支持命名選項

IOptionsMonitor<TOptions>:

  • 單例服務,可以注入到任何服務生存期
  • 支持更改通知 ( 比如配置文件更改通知 )
  • 支持命名選項
  • 支持重載配置
  • 選擇性選項失效 (IOptionsMonitorCache )

IOptionsFactory<TOptions>負責創建、計算選項實例,IOptions<TOptions>IOptionsSnapshot<TOptions>IOptionsMonitor<TOptions>都使用它來創建選項實例

對比實例:

appsettings.json

{
  "MySetting": {
    "B": "B 1"
  }
}

C# Startup

public class Startup
{
    public IConfiguration Configuration { get; }

    public Startup( IConfiguration configuration)
    {
        Configuration = configuration;
    }
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        services.Configure<MyOption>(option => { option.A = DateTime.Now.ToString(CultureInfo.CurrentCulture); });
        services.Configure<MyOption>(Configuration.GetSection("MySetting"));
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseRouting();
        app.UseEndpoints(options => { options.MapControllers(); });
    }
}

C# HomeController


[Route("Home")]
public class HomeController : ControllerBase
{
    public IOptions<MyOption> MyOptions { get; }
    public IOptionsSnapshot<MyOption> MyOptionsSnapshot { get; }
    public IOptionsMonitor<MyOption> MyOptionsMonitor { get; }
    public IServiceProvider ServiceProvider { get; }
    public IWebHostEnvironment WebHostEnvironment { get; }

    public HomeController(
        IOptions<MyOption> myOptions,
        IOptionsSnapshot<MyOption> myOptionsSnapshot,
        IOptionsMonitor<MyOption> myOptionsMonitor,
        IServiceProvider serviceProvider,
        IWebHostEnvironment webHostEnvironment
    )
    {
        MyOptions = myOptions;
        MyOptionsSnapshot = myOptionsSnapshot;
        MyOptionsMonitor = myOptionsMonitor;
        ServiceProvider = serviceProvider;
        WebHostEnvironment = webHostEnvironment;
    }

    [HttpGet]
    public string GetAsync()
    {
        Console.WriteLine($"MyOption A:{MyOptions.Value.A}");
        Console.WriteLine($"MyOption B:{MyOptions.Value.B}");
        Console.WriteLine($"MyOptionsSnapshot A:{MyOptionsSnapshot.Value.A}");
        Console.WriteLine($"MyOptionsSnapshot B:{MyOptionsSnapshot.Value.B}");
        Console.WriteLine($"MyOptionsMonitor A:{MyOptionsMonitor.CurrentValue.A}");
        Console.WriteLine($"MyOptionsMonitor B:{MyOptionsMonitor.CurrentValue.B}");

        // 更改appsetting配置文件,處罰文件更改通知 ( json文件配置需要開啟更改重載 )
        System.IO.File.WriteAllText(WebHostEnvironment.ContentRootPath + "/appsettings.json",
            JsonSerializer.Serialize(new{
                MySetting = new
                {
                    B = "B 2"
                }
            })
        );
        var timer = new Timer(2000);
        timer.Start();
        timer.Elapsed += (sender, e) =>
        {
            Console.WriteLine($"MyOption2 A:{MyOptions.Value.A}");
            Console.WriteLine($"MyOption2 B:{MyOptions.Value.B}");
            Console.WriteLine($"MyOptionsSnapshot2 A:{MyOptionsSnapshot.Value.A}");
            Console.WriteLine($"MyOptionsSnapshot2 B:{MyOptionsSnapshot.Value.B}");
            Console.WriteLine($"MyOptionsMonitor2 A:{MyOptionsMonitor.CurrentValue.A}");
            Console.WriteLine($"MyOptionsMonitor2 B:{MyOptionsMonitor.CurrentValue.B}");
            timer.Stop();
        };

        return "Hello";
    }
}

控制台打印信息如下:

第一次請求:

MyOption A:10/23/2020 11:48:37 PM

MyOption B:B 1

MyOptionsSnapshot A:10/23/2020 11:48:37 PM

MyOptionsSnapshot B:B 1

MyOptionsMonitor A:10/23/2020 11:48:37 PM

MyOptionsMonitor B:B 1

2秒后

MyOption2 A:10/23/2020 11:48:37 PM // 沒變,因為IOptions只會計算一次

MyOption2 B:B 1 // 沒變,因為IOptions只會計算一次

MyOptionsSnapshot2 A:10/23/2020 11:48:37 PM // 沒變,因為IOptionsSnapshot每次請求才會計算一次

MyOptionsSnapshot2 B:B 1 // 沒變,因為IOptionsSnapshot每次請求才會計算一次

MyOptionsMonitor2 A:10/23/2020 11:48:37 PM // 沒變,因為IOptionsMonitor沒有接受到 A鍵 的相關更改通知

MyOptionsMonitor2 B:B 2 // 變了,因為IOptionsMonitor接受到了文件配置更改通知

第二次請求:

MyOption A:10/23/2020 11:48:37 PM // 沒變,因為IOptions只會計算一次

MyOption B:B 1 // 沒變,因為IOptions只會計算一次

MyOptionsSnapshot A:10/23/2020 11:49:15 PM // 變了,因為IOptionsSnapshot每次請求都會計算一次

MyOptionsSnapshot B:B 2 // 變了,因為IOptionsSnapshot每次請求都會計算一次

OptionBuilder

特點:

  • 簡化了創建命名選項的過程,因為它只是初始 AddOptions<TOptions>(string optionsName) 調用的單個參數,而不會出現在所有后續調用中
  • 能在配置選項過程中使用依賴
  • 能夠驗證選項

不使用OptionsBuilder

var serviceCollection = new ServiceCollection();
serviceCollection.PostConfigure<MyOption>("My",n =>
{
    n.A = n.A + "_Stuff";
    n.B = "B";
});
serviceCollection.Configure<MyOption>("My", n => { n.A = "A"; });

使用OptionsBuilder

通過AddOptions<TOptions>(string optionsName)獲取OptionsBuilder

var serviceCollection = new ServiceCollection();
serviceCollection.AddOptions<MyOption>("My")
    .PostConfigure(n =>
    {
        n.A = n.A + "_Stuff";
        n.B = "B";
    })
    .Configure(n => { n.A = "A"; });

選項依賴

OptionBuilderConfigure最多支持5個泛型參數,他們表示選項的依賴

var serviceCollection = new ServiceCollection();
serviceCollection.AddOptions<MyOption>("My")
    .Configure<IConfiguration>((option, configuration) =>
    {
        option.A = "A";
        option.B = configuration["B"];
    });

選項驗證

OptionBuilderValidate可以對選項進行驗證,將在此選項獲取時進行驗證,如果驗證失敗將拋出異常
Validate的第二個參數可以自定義驗證的錯誤信息

IConfiguration configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(_ => configuration);
serviceCollection.AddOptions<MyOption>()
    .Configure<IConfiguration>((option, configuration) =>
    {
        option.A = "A2";
        option.B = configuration["B"];
    })
    .Validate(options => options.B != null,"B不能為空"); // 第二個參數用於自定義驗證錯誤信息
    .Validate(options => options.A != null,"A不能為空"); // 第二個參數用於自定義驗證錯誤信息
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();  // 如果驗證失敗,將拋出異常

使用特性進行選項驗證

Nuget包:Microsoft.Extensions.Options.DataAnnotations

實例:

appsettings.json

{
  "MySetting": {
    "A": "A Value",
    "B": "B Value"
  }
}

C#

IConfiguration configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();
var serviceCollection = new ServiceCollection();
serviceCollection.Configure<MyOption2>(configuration.GetSection("MySetting"));
serviceCollection.AddOptions<MyOption2>()
    .ValidateDataAnnotations(); // 使用特性進行選項驗證

var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption2>>();
var myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A);
Console.WriteLine(myOptionValue.B);

ConfiguOptions

ConfiguOptions用於在依賴容器中注冊IConfigureOptions<in TOptions>IPostConfigureOptions<in TOptions>IValidateOptions<TOptions>的實現(如果傳入的類型實現了他們)

ConfigurePostConfigure等對選項進行配置,本質就是在依賴容器中注冊IConfigureOptions<in TOptions>的實現
OptionBuilderValidate函數本質也是注冊IValidateOptions<TOptions>的實現

所以我們也可以手動注冊IConfigureOptions<in TOptions>,這樣的一個好處是可以注入依賴

注意: serviceCollection.AddOptions()函數用於注冊IOptionsIOptionsSnapshotIOptionsMonitorIOptionsFactoryIOptionsMonitorCache的實現
使用serviceCollection.Configure等函數會在內部調用serviceCollection.AddOptions(),所以不需要在調用它
這里我們手動注入IConfigureOptions<in TOptions>的實現,所以手動調用它

實例1:

C# 1

public class MyConfigureOption : IConfigureOptions<MyOption>
{
    public IConfiguration Configuration { get; }

    public MyConfigureOption(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public void Configure(MyOption options)
    {
        options.A = "A Value";
        options.B = Configuration["B"];
    }
}

C# 2

/// appsettings.json:{"B": "B Value"}
IConfiguration configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(_ => configuration);
serviceCollection.AddOptions();
serviceCollection.AddTransient<IConfigureOptions<MyOption>, MyConfigureOption>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
Console.WriteLine(myOption.Value.A); // A Value
Console.WriteLine(myOption.Value.B); // B Value

實例2:

可以直接繼承自ConfigureNamedOptions<MyOption>來實現命名配置

C# 1

public class MyConfigureOption : ConfigureNamedOptions<MyOption>
{
    public MyConfigureOption(IConfiguration configuration)
        : base("My", options =>
        {
            options.A = "A Value";
            options.B = configuration["B"];
        })
    {
    }
}

C# 2

/// appsettings.json:{"B": "B Value"}
IConfiguration configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(_ => configuration);
serviceCollection.AddOptions();
serviceCollection.AddTransient<IConfigureOptions<MyOption>, MyConfigureOption>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptionsSnapshot<MyOption>>();
var myOptionValue = myOption.Get("My");
Console.WriteLine(myOptionValue.A); // A Value
Console.WriteLine(myOptionValue.B); // B Value

實例3:

可以實現IValidateOptions<TOptions>來對選項進行驗證

public class MyConfigureOption : ConfigureNamedOptions<MyOption>, IValidateOptions<MyOption>
{
    public MyConfigureOption(IConfiguration configuration)
        : base("My", options =>
        {
            options.A = "A Value";
            options.B = configuration["B"];
        })
    {
    }

    public ValidateOptionsResult Validate(string name, MyOption options)
    {
        if (name != "My")
        {
            return ValidateOptionsResult.Skip;
        }

        if (options.B == null)
        {
            return ValidateOptionsResult.Fail("B 不能為空");
        }
        else
        {
            return ValidateOptionsResult.Success;
        }
    }
}

C# 2

/// appsettings.json:{"B": "B Value"}
IConfiguration configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(_ => configuration);
serviceCollection.AddOptions();
serviceCollection.AddTransient<IConfigureOptions<MyOption>, MyConfigureOption>();
serviceCollection.AddTransient<IValidateOptions<MyOption>, MyConfigureOption>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptionsSnapshot<MyOption>>();
var myOptionValue = myOption.Get("My");
Console.WriteLine(myOptionValue.A);
Console.WriteLine(myOptionValue.B);

使用ConfiguOptions代替手動注冊IConfigureOptions<in TOptions>IPostConfigureOptions<in TOptions>IValidateOptions<TOptions>

在上面實例中需要手動指定要注冊的服務類,在實例3中需要注冊2次,使用ConfiguOptions能幫你自動注冊他們

實例3中的以下代碼

serviceCollection.AddTransient<IConfigureOptions<MyOption>, MyConfigureOption>();
serviceCollection.AddTransient<IValidateOptions<MyOption>, MyConfigureOption>();

可以替換一下代碼

serviceCollection.ConfigureOptions<MyConfigureOption>();


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM