. net options options

Time:2020-11-24

Dotnet configuration source code:https://github.com/dotnet/runtime/tree/master/src/libraries/In which Microsoft.Extensions.Options Opening item
Options official document:

Nuget package:Microsoft.Extensions.Options

It is suggested to master the following:

  • Dependency injection:
  • Configuration:

introduce

OptionsProvides strongly typed access to configuration data

OptionsProvides a mechanism for validating options

Configure

First declare an option class

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

    public string B { get; set; }
}

useConfigureThe extension method configures the option class

var serviceCollection = new ServiceCollection();

//Configure myoption
serviceCollection.Configure(n =>
{
    n.A = "A Value";
    n.B = "B Value";
});

var serviceProvider = serviceCollection.BuildServiceProvider();

obtainIOptionsOption service

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

stay Aps.Net Use in

to configure

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

injectionIoption

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

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

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

Binding configuration, usingIConfigurationTo configure options

Nuget package:Microsoft.Extensions.Options.ConfigurationExtensions

Configure(IConfigureation configuration)Not only the configuration will be bound, but also the notification of changes to the configuration file will be addedIOptionsMonitorRecalculation options

example:

appsettings.json

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

C#

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

PostConfigure

PostConfigureWill be inConfigureThen configure it

example:

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

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

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

Naming options

You can name options

The default option name is an empty string

IOptionsGetting naming options is not supported, only default naming options can be obtained

have access toIOptionsSnapshotIOptionsMonitorFor naming options

IOptionsSnapshotHeirsIOptionsAnd addedGet(string optionName)Method to get the naming options

IOptionsMonitorOfCurrentValueProperty is used to get the default options,Get(string optionName)Method to get the naming options

example:

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

//Ioptionsnapshot get naming options
var myOption2 = serviceProvider.GetRequiredService>();
MyOption myOptionValue2 = myOption2.Get("My");
Console.WriteLine(myOptionValue2.A); // My:A Value
Console.WriteLine(myOptionValue2.B); // My:B Value

//Ioptionsmonitor gets naming options
var myOptionsMonitor = serviceProvider.GetRequiredService>();
MyOption myOptionValue3 = myOptionsMonitor.Get("My");
Console.WriteLine(myOptionValue3.A);
Console.WriteLine(myOptionValue3.B);

ConfigureAll、PostConfigureAll

ConfigureAllPostConfigureAllAll named configurations can be configured

example:

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

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

var myOption2 = serviceProvider.GetRequiredService>();
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

  • Options are evaluated only once
  • Naming options are not supported
  • Singleton services can be injected into any service lifetime

IOptionsSnapshot

  • Multiple options are counted and should be recalculated on each request
  • Registered as in scope and therefore cannot be injected into a single instance service
  • Support naming options

IOptionsMonitor:

  • Singleton services can be injected into any service lifetime
  • Support change notification (such as profile change notification)
  • Support naming options
  • Support overload configuration
  • Selective option invalidation (ioptionsmonitorcache))

IOptionsFactoryResponsible for creating and calculating option instances,IOptionsIOptionsSnapshotIOptionsMonitorAll use it to create option instances

Contrast example:

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(option => { option.A = DateTime.Now.ToString(CultureInfo.CurrentCulture); });
        services.Configure(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 MyOptions { get; }
    public IOptionsSnapshot MyOptionsSnapshot { get; }
    public IOptionsMonitor MyOptionsMonitor { get; }
    public IServiceProvider ServiceProvider { get; }
    public IWebHostEnvironment WebHostEnvironment { get; }

    public HomeController(
        IOptions myOptions,
        IOptionsSnapshot myOptionsSnapshot,
        IOptionsMonitor 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}");

        //Change appsetting configuration file, penalty file change notification (JSON file configuration needs to enable change overload)
        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";
    }
}

The console prints the following information:

First request:

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

Two seconds later

Myoption2 A: 10 / 23 / 2020 11:48:37 PM / / unchanged, because ioptions are only calculated once

Myoption2 B: B 1 / / does not change, because ioptions are only calculated once

Myoptions snapshot 2 A: 10 / 23 / 2020 11:48:37 PM / / unchanged, because ioptionsnapshot is calculated once per request

Myoptions snapshot 2 B: B 1 / / does not change, because ioptionsnapshot is calculated once per request

Myoptionsmonitor2 A: 10 / 23 / 2020 11:48:37 PM / / no change, because ioptionsmonitor did not receive the change notification of key a

Myoptionsmonitor2 B: B 2 / / changed because ioptionsmonitor received notification of file configuration changes

Second request:

Myoption A: 10 / 23 / 2020 11:48:37 PM / / unchanged, because ioptions will only be calculated once

Myoption B: B 1 / / doesn’t change, because ioptions are only calculated once

Myoptionssnapshot A: 10 / 23 / 2020 11:49:15 PM / / changed, because ioptionsnapshot is calculated every time

Myoptionssnapshot B: B 2 / / changed, because ioptionsnapshot is calculated every time it is requested

OptionBuilder

characteristic:

  • Simplifies the process of creating named options because it is only initialAddOptions(string optionsName)A single parameter of the call, which does not appear in all subsequent calls
  • Can use dependencies during configuration options
  • Ability to validate options

Not usedOptionsBuilder

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

useOptionsBuilder

adoptAddOptions(string optionsName)obtainOptionsBuilder

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

Option dependency

OptionBuilderOfConfigureUp to five generic parameters are supported, which represent the dependency of options

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

Option validation

OptionBuilderOfValidateThe option can be validated, which will be verified when the option is obtained. If the validation fails, an exception will be thrown
ValidateThe second parameter of can customize the validation error message

IConfiguration configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(_ => configuration);
serviceCollection.AddOptions()
    .Configure((option, configuration) =>
    {
        option.A = "A2";
        option.B = configuration["B"];
    })
    . validate (options = > options. B! = null, "B cannot be empty"); // the second parameter is used to customize the validation error information
    . validate (options = > options. A! = null, "a cannot be empty"); // the second parameter is used to customize the validation error message
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption =  serviceProvider.GetRequiredService >(); // if the validation fails, an exception will be thrown

Option validation using properties

Nuget package:Microsoft.Extensions.Options.DataAnnotations

example:

appsettings.json

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

C#

IConfiguration configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();
var serviceCollection = new ServiceCollection();
serviceCollection.Configure(configuration.GetSection("MySetting"));
serviceCollection.AddOptions()
    . validatedata annotations(); // use attributes for option validation

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

ConfiguOptions

ConfiguOptionsUsed to register in a dependency containerIConfigureOptionsIPostConfigureOptionsandIValidateOptionsImplementation of (if the incoming type implements them)

ConfigurePostConfigureThe essence of configuring options is to register in the dependency containerIConfigureOptionsImplementation of
OptionBuilderOfValidateThe essence of functions is registrationIValidateOptionsImplementation of

So we can also register manuallyIConfigureOptionsOne advantage of this is that dependency can be injected

be careful:serviceCollection.AddOptions()Function is used to registerIOptionsIOptionsSnapshotIOptionsMonitorIOptionsFactoryIOptionsMonitorCacheImplementation of
useserviceCollection.ConfigureFunctions such asserviceCollection.AddOptions(), so you don’t need to call it
Here we inject it manuallyIConfigureOptionsSo call it manually

Example 1:

C# 1

public class MyConfigureOption : IConfigureOptions
{
    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, MyConfigureOption>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService>();
Console.WriteLine(myOption.Value.A); // A Value
Console.WriteLine(myOption.Value.B); // B Value

Example 2:

Can be directly inherited fromConfigureNamedOptionsTo implement named configuration

C# 1

public class MyConfigureOption : ConfigureNamedOptions
{
    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, MyConfigureOption>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService>();
var myOptionValue = myOption.Get("My");
Console.WriteLine(myOptionValue.A); // A Value
Console.WriteLine(myOptionValue.B); // B Value

Example 3:

It can be realizedIValidateOptionsTo verify the options

public class MyConfigureOption : ConfigureNamedOptions, IValidateOptions
{
    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 V alidateOptionsResult.Fail ("B cannot be empty");
        }
        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, MyConfigureOption>();
serviceCollection.AddTransient, MyConfigureOption>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService>();
var myOptionValue = myOption.Get("My");
Console.WriteLine(myOptionValue.A);
Console.WriteLine(myOptionValue.B);

useConfiguOptionsReplace manual registrationIConfigureOptionsIPostConfigureOptionsandIValidateOptions

In the above instance, you need to manually specify the service class to be registered. In instance 3, you need to register twice, and use theConfiguOptionsCan help you automatically register them

The following code in example 3

serviceCollection.AddTransient, MyConfigureOption>();
serviceCollection.AddTransient, MyConfigureOption>();

You can replace the code

serviceCollection.ConfigureOptions();