ASP.NET Core notes - options mode

  • Option interface
  • Differences between ioptionsnapshot and IOptionsMonitor
  • Post configuration of options
  • Validation of options

In ASP.NET Core note (3) - configuration, various configuration providers and configuration reading methods are introduced. However, in practice, it is not recommended to read applications directly from a heap of configurations, but to use strong type binding to bind configurations to classes belonging to different services by groups. In this way, the configuration scheme can follow two important software engineering principles:

  • Interface separation policy (ISP) or encapsulation - a scheme (class) that depends on configuration settings only depends on the configuration settings it uses.
  • Separation of concerns – the settings of the different parts of the application are not dependent or coupled with each other.

Option interface

The commonly used interfaces of ASP.NET Core option mode are

  • IOptions
  • IOptionsSnapshot
  • IOptionsMonitor

IOptions cannot correspond to the value of the changed option when the configuration is changed, only the application can be restarted. IOptions snapshot and IOptions monitor have this capability.

Options can be injected as services when they are used. The following code simulates the use scenario of options. OrderServiceOptions are injected as options of OrderService, and OrderService is injected in the controller:

public interface IOrderService
{
    int ShowMaxOrderCount();
}

public class OrderService : IOrderService
{
    IOptionsSnapshot<OrderServiceOptions> _options;
    public OrderService(IOptionsSnapshot<OrderServiceOptions> options)
    {
        _options = options;
    }

    public int ShowMaxOrderCount()
    {
        return _options.Value.MaxOrderCount;
    }
}

public class OrderServiceOptions
{
    public int MaxOrderCount { get; set; };
}

Inject OrderService into the controller:

[HttpGet]
public string Get([FromServices]IOrderService orderService)
{
    var res = $"orderService.ShowMaxOrderCount:{orderService.ShowMaxOrderCount()},time={orderService.ShowTime()}";
    Console.WriteLine(res);
    return res;
}

Configure the injection of OrderServiceOptions and OrderService in ConfigService:

services.Configure<OrderServiceOptions>(Configuration.GetSection("OrderService"));
services.AddScoped<IOrderService, OrderService>();

Differences between ioptionsnapshot and IOptionsMonitor

The AddScoped scope is used for injection because the ioptionsnapshot interface is used. The life cycle of ioptions snapshot is scope, and the configuration and update options should be read again every time it is requested. So after modifying the configuration, re request the API, and you can see the latest configuration value. If you try to select Add singleton, because the life cycle of OrderService singleton is longer than ioptionsnapshot, the runtime exception will be thrown directly.

What to do if you encounter a scenario that requires both a single life cycle and change detection? You need the IOptionsMonitor. The differences between IOptionsMonitor and ioptionsnapshot are as follows:

  • IOptionsMonitor is a singleton service that can monitor configuration changes.
  • IOptionsSnapshot is a scope service that provides a snapshot of options when constructing IOptionsSnapshot < T > objects.

IOptionsMonitor uses the same as ioptionsnapshot, but the value changes to CurrentValue. If you modify the configuration source, the OnChange method is triggered:

_options.OnChange(option =>
{
    Console.WriteLine($"The configuration has been updated. The latest value is:{_options.CurrentValue.MaxOrderCount}");
});

IOptionsMonitor can also combine the method of custom data source described in ASP.NET Core note (3) - configuration. When IConfigurationProvider triggers the OnReload() event, the OnChange here will also be triggered.

Post configuration of options

Use PostConfigure to post configure options:

services.PostConfigure<OrderServiceOptions>(options =>
{
    options.MaxOrderCount += 20;
});

Validation of options

To prevent the application from reading the wrong configuration, you can add validation for the option. There are three ways to verify options:

  • Register validation function directly
  • DataAnnotations
  • Implementing the IValidateOptions interface

Register validation function directly

Add option needs to be replaced with AddOptions

//services.Configure<OrderServiceOptions>(configuration);
services.AddOptions<OrderServiceOptions>().Bind(configuration).Configure(options =>
{
configuration.Bind(options);
})
.Validate(options => options.MaxOrderCount <= 100, "MaxOrderCount Cannot be greater than 100");

DataAnnotations

Call ValidateDataAnnotations:

services.AddOptions<OrderServiceOptions>().Bind(configuration).Configure(options =>
{
configuration.Bind(options);
})
.ValidateDataAnnotations<OrderServiceOptions>();

Add Annotation for option model class:

public class OrderServiceOptions
{
    [Range(0, 100)]
    public int MaxOrderCount { get; set; };
}

Implementing the IValidateOptions interface

Registration verification service:

services.AddOptions<OrderServiceOptions>().Bind(configuration).Configure(options =>
{
configuration.Bind(options);
})
.Services.AddSingleton<IValidateOptions<OrderServiceOptions>,OrderServiceValidateOptions> ();

To implement IValidateOptions:

public class OrderServiceValidateOptions : IValidateOptions<OrderServiceOptions>
{
    public ValidateOptionsResult Validate(string name, OrderServiceOptions options)
    {
        if (options.MaxOrderCount > 100)
        {
            return ValidateOptionsResult.Fail("MaxOrderCount Cannot be greater than 100");
        }
        else
        {
            return ValidateOptionsResult.Success;
        }
    }
}

Keywords: Programming snapshot

Added by wdsmith on Wed, 15 Apr 2020 17:10:35 +0300