看看.NET Core幾個Options的簡單使用

前言

配置,對咱們的程序來講是十分重要的一部分。或多或少都會寫一部份內容到配置文件中去。json

由其是在配置中心(Apollo等)作起來以前,配置文件必定會是咱們的首選。api

在.NET Core中,習慣的是用json文件當配置文件。讀取的方法是很多,這裏主要介紹的是用基於Options的方法來讀,能夠認爲這是一種強類型的形式。app

本文會介紹一些常見的用法,簡單的單元測試示例,若是想探討內部實現,請移步至雨夜朦朧的博客post

先來看看IOptions。單元測試

IOptions

先寫好配置文件測試

{
    "Demo": {
        "Age": 18,
        "Name": "catcher"
    },
    //others ... 
}

而後定義對應的實體類this

public class DemoOptions
{
    public int Age { get; set; }

    public string Name { get; set; }
}

而後只須要在ConfigureServices方法添加一行代碼就能夠正常使用了。3d

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<DemoOptions>(Configuration.GetSection("Demo"));
    
    //others..
}

最後就是在想要讀配置內容的地方使用IOptions去注入就行了。code

private readonly DemoOptions _normal;

public ValuesController(IOptions<DemoOptions> normalAcc)
{
    this._normal = normalAcc.Value;
}

// GET api/values
[HttpGet]
public string Get()
{
    var age = $"normal-[{_normal.Age}];";
    var name = $"normal-[{_normal.Name}];";

    return $"age:{age} \nname:{name}";
}

這個時候的結果,就會大體以下了:orm

這個時候可能會冒出這樣的一個想法,若是某天,要修改某個配置項的值,它能及時生效嗎?

口說無憑,來個動圖見證一下。

事實證實,使用IOptions的時候,修改配置文件的值,並不會馬上生效!!

既然IOptions不行,那麼咱們就換一個!

下面來看看IOptionsSnapshot。

IOptionsSnapshot

對於Options家族,在Startup註冊的時候都是一個樣的,區別在於使用它們的時候。

private readonly DemoOptions _normal;
private readonly DemoOptions _snapshot;

public ValuesController(IOptions<DemoOptions> normalAcc,
        IOptionsSnapshot<DemoOptions> snapshotAcc)
{
    this._normal = normalAcc.Value;
    this._snapshot = snapshotAcc.Value;
}

// GET api/values
[HttpGet]
public string Get()
{
    var age = $"normal-[{_normal.Age}];snapshot-[{_snapshot.Age}];";
    var name = $"normal-[{_normal.Name}];snapshot-[{_snapshot.Name}];";

    return $"age:{age} \nname:{name}";
}

這個時候修改配置項的值以後,就會立馬更新了。

本質上,IOptions和IOptionsSnapshot是同樣的,只是他們註冊的生命週期不同,從而就有不一樣的表現結果。

固然,還有一個更強大的Options的存在,IOptionsMonitor。

它的用法和IOptionsSnapshot沒有區別,不一樣的時,它多了一個配置文件發生改變以後事件處理。

下面來看看。

IOptionsMonitor

private readonly DemoOptions _normal;
private readonly DemoOptions _snapshot;
private readonly DemoOptions _monitor;

public ValuesController(IOptions<DemoOptions> normalAcc, IOptionsSnapshot<DemoOptions> snapshotAcc, IOptionsMonitor<DemoOptions> monitorAcc)
{
    this._normal = normalAcc.Value;
    this._snapshot = snapshotAcc.Value;
    this._monitor = monitorAcc.CurrentValue;
    monitorAcc.OnChange(ChangeListener);
}

private void ChangeListener(DemoOptions options, string name)
{
    Console.WriteLine(name);
}

// GET api/values
[HttpGet]
public string Get()
{
    var age = $"normal-[{_normal.Age}];snapshot-[{_snapshot.Age}];monitor-[{_monitor.Age}];";
    var name = $"normal-[{_normal.Name}];snapshot-[{_snapshot.Name}];monitor-[{_monitor.Name}];";

    return $"age:{age} \nname:{name}";
}

效果和上面同樣的,不一樣的是,當保存appsettings.json的時候,會觸發一次ChangeListener

雖然說Snapshot和Monitor可讓咱們及時獲取到最新的配置項。

可是咱們也能夠經過PostConfigurePostConfigureAll來進行調整。

PostConfigure/PostConfigureAll

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<DemoOptions>(Configuration.GetSection("Demo"));

    services.PostConfigureAll<DemoOptions>(x =>
    {
        x.Age = 100;
    });

    services.AddMvc();
}

若是咱們的代碼是這樣寫的,那麼,最終的結果就會是,不管咱們怎麼修改配置文件,最終展現的Age會一直是100。

你們也能夠思考一下這個能夠用在什麼場景。

隨便給你們看一段Steeltoe服務發現客戶端的代碼

private static void AddDiscoveryServices(IServiceCollection services, IConfiguration config, IDiscoveryLifecycle lifecycle)
{
    var clientConfigsection = config.GetSection(EUREKA_PREFIX);
    int childCount = clientConfigsection.GetChildren().Count();
    if (childCount > 0)
    {
        var clientSection = config.GetSection(EurekaClientOptions.EUREKA_CLIENT_CONFIGURATION_PREFIX);
        services.Configure<EurekaClientOptions>(clientSection);

        var instSection = config.GetSection(EurekaInstanceOptions.EUREKA_INSTANCE_CONFIGURATION_PREFIX);
        services.Configure<EurekaInstanceOptions>(instSection);
        services.PostConfigure<EurekaInstanceOptions>((options) =>
        {
            EurekaPostConfigurer.UpdateConfiguration(config, options);
        });
        AddEurekaServices(services, lifecycle);
    }
    else
    {
        throw new ArgumentException("Discovery client type UNKNOWN, check configuration");
    }
}

最後就是單元測試遇到Options要怎麼處理的問題了。

單元測試

單元測試,這裏用了NSubstitute來做示例。

先簡單定義一些類

public class MyClass
{
    private readonly MyOptions _options;

    public MyClass(IOptions<MyOptions> optionsAcc)
    {
        this._options = optionsAcc.Value;
    }

    public string Greet()
    {
        return $"Hello,{_options.Name}";
    }
}

public class MyOptions
{
    public string Name { get; set; }        
}

編寫測試類

public class MyClassTest
{
    private readonly MyClass myClass;

    public MyClassTest()
    {
        var options = new MyOptions {  Name = "catcher"};

        var fake = Substitute.For<IOptions<MyOptions>>();

        fake.Value.Returns(options);

        myClass = new MyClass(fake);
    }

    [Fact]
    public void GreetTest()
    {
        var res = myClass.Greet();

        Assert.Equal("Hello,catcher", res);
    }
}

重點在於fake了一下Options(這裏只以IOptions爲例),而後是告訴測試,若是有用到Value屬性的時候,就用返回定義好的Options。

也是比較簡單的作法,測試的結果天然也是符合預期的。

總結

這幾個Options使用起來仍是比較順手的,至少什麼時候採用那種Options,就得根據場景來定了。

相關文章
相關標籤/搜索