ASP.NET Core 3.0:. Twenty-four configuration mode Options

The last chapter talked about the usage and configuration of internal processing mechanism for configuration, ASP.NET Core also provides an Options mode. ( ASP.NET Core Series Catalog )

A, Options of use

Binding the last chapter has a configuration of an example, it can be configured to bind a Theme instance. That is, when using the corresponding configuration, the need for a binding operation. Options mode and provides a more direct way, and may provide the read dependency configured by way of injection. Each hereinafter referred Options configured Option.

1. A simple way is not named as Option

Still using this example, there is disposed in the appsettings.json:

{
  "Theme": {
    "Name": "Blue",
    "Color": "#0921DC"
  }
}

Modify ValueController, the code is as follows:

public class ValuesController : Controller
{
    private IOptions<Theme> _options = null;
    public ValuesController(IOptions<Theme> options)
    {
        _options = options;
    }

    public ContentResult GetOptions()
    {
        return new ContentResult() { Content = $"options:{ _options.Value.Name}" };
    }
}

Sign up still needs to be done in the Startup file:

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

        services.AddControllersWithViews();  //3.0中启用的新方法
    }

 

Request this Action, to get the results:

options:Blue

This can be used by injection-dependent manner the need to use the configuration But there is a doubt, there will be "Theme" type bound to such a configuration, but if more than one such configuration? Like this configuration when the following:

  "Themes": [
    {
      "Name": "Blue",
      "Color": "#0921DC"
    },
    {
      "Name": "Red",
      "Color": "#FF4500"
    }
  ]

In such a case, the presence of a plurality Theme, so for this dependency before injection way to die. Then the system provides methods for naming injected Options.

2. Option naming way

First need to register in the Startup folder in time for its name, add the following two registration code:

services.Configure<Theme>("ThemeBlue", Configuration.GetSection("Themes:0"));
services.Configure<Theme>("ThemeRed" , Configuration.GetSection("Themes:1"));

Modify the code ValueController added IOptionsMonitor <Theme> and IOptionsSnapshot <Theme> two new injection as follows:

        private IOptions<Theme> _options = null;
        private IOptionsMonitor<Theme> _optionsMonitor = null;
        private IOptionsSnapshot<Theme> _optionsSnapshot = null;
        public ValuesController(IOptions<Theme> options, IOptionsMonitor<Theme> optionsMonitor, IOptionsSnapshot<Theme> optionsSnapshot)
        {
            _options = options;
            _optionsMonitor = optionsMonitor;
            _optionsSnapshot = optionsSnapshot;
        }

        public ContentResult GetOptions()
        {
            return new ContentResult() { Content = $"options:{_options.Value.Name}," +
                $"optionsSnapshot:{ _optionsSnapshot.Get("ThemeBlue").Name }," +
                $"optionsMonitor:{_optionsMonitor.Get("ThemeRed").Name}" };
        }

Request this Action, to get the results:

options:Blue,optionsSnapshot:Red,optionsMonitor:Gray

Two inject new way to get to by name in the corresponding Options Options. Why are the two of it? What's the difference? I do not know if readers think reload function in the previous chapter configuration. In the configuration register when there is a reloadOnChange option, if it is set to true, when the corresponding data source changes, it will be reloaded. The Options how can less so characteristic of it.

3.Option automatic updates and life cycle

To verify the characteristics of the three Options read mode, modifications Theme class, adding a Guid field, and its assignment in the constructor, as follows:

public class Theme
{
    public Theme()
    {
        Guid = Guid.NewGuid();
    }
    public Guid Guid { get; set; }
    public string Name { get; set; }
    public string Color { get; set; }
}

Example modifications of the named GetOptions Action code is as follows:

public ContentResult GetOptions()
{
    return new ContentResult()
    {
        Content = $"options:{_options.Value.Name}|{_options.Value.Guid}," +
        $"optionsSnapshot:{ _optionsSnapshot.Get("ThemeBlue").Name }|{_optionsSnapshot.Get("ThemeBlue").Guid}," +
        $"optionsMonitor:{_optionsMonitor.Get("ThemeRed").Name}|{_optionsMonitor.Get("ThemeRed").Guid}"
    };
}

This request Action, returns the following results:

options:Blue|ad328f15-254f-4505-a79f-4f27db4a393e,optionsSnapshot:Red|dba5f550-29ca-4779-9a02-781dd17f595a,optionsMonitor:Gray|a799fa41-9444-45dd-b51b-fcd15049f98f

Refresh the page and return the results as follows:

options:Blue|ad328f15-254f-4505-a79f-4f27db4a393e,optionsSnapshot:Red|a2350cb3-c156-4f71-bb2d-25890fe08bec,optionsMonitor:Gray|a799fa41-9444-45dd-b51b-fcd15049f98f

Visible IOptions and IOptionsMonitor two ways to get to the Name value and Guid values ​​were not changed, and acquired by way IOptionsSnapshot Name value has not changed, but the Guid value has changed, will change each time you refresh the page. This is similar to the test for the previous examples do dependency injection speaking, now guess Guid IOptions unchanged and is employed in two ways IOptionsMonitor Singleton pattern, and IOptionsSnapshot Guid embodiment is the use of altered or Scoped Transient mode. If the value of using several IOptionsSnapshot read _optionsSnapshot.Get ( "ThemeBlue"). Guid Action In this, the same request will find the value is the same, different values ​​between different requests, i.e. manner IOptionsSnapshot Scoped mode so employed (this verification example is simple, self-modifying code reader authentication).

In such cases, the modified three kinds of ways to obtain the corresponding Name value of the configuration item, such as were revised to "Blue1", "Red1" and "Gray1", again repeatedly refresh the page to see the return value, you will find the following:

IOptions way: Name and Guid value remained unchanged. Name value is still Blue.

IOptionsSnapshot way: Name Red1 value becomes, the same value Guid single request, the difference between each refresh.

IOptionsMonitor way: only after modifying the configuration value for the first time refresh when the Name value becomes the Gray1, Guid unchanged. After repeatedly refresh, neither of these two values ​​do change.

Summary: IOptions and IOptionsMonitor Singleton pattern employed in two ways, but with the difference that the IOptionsMonitor monitors changes corresponding to the data source, the changed configuration values ​​if the instance is updated, but does not re-offered new instance. IOptionsSnapshot Scoped embodiment uses a model instance for each request using the same, the next time the acquired request is a new instance, if the data source has changed, the new value will be read. Probably remember what the first case, in the following analysis IOptions mechanism of internal processing time will understand why this is so.

4. Data update alerts

IOptionsMonitor approach also provides a method OnChange, when the data source changes will trigger it, so if you want to do something at this time, you can use this method to achieve. Sample code:

_optionsMonitor.OnChange((theme,name)=> { Console.WriteLine(theme.Name +"-"+ name); });

The embodiment is not used as a data source arranged Configuration

The above examples are based on the configuration of the read mode, and in fact, the last chapter Configuration Options pattern disposed apart manner, the configuration is only a read mode Options implementations, for example, may not be used in Configuration data, registration codes directly by:

services.Configure<Theme>("ThemeBlack", theme => {
    theme.Color = "#000000";
    theme.Name = "Black";
}); 

6.ConfigureAll method

ConfigureAll system provides a method, all instances corresponding to a unified set. For example the following code:

services.ConfigureAll<Theme>(theme => {
     theme.Color = "#000000";
     theme.Name = "Black2";
});

At this time, by whatever name to acquire Theme examples, including the name of the corresponding set is not present, the values ​​are acquired by the current value setting ConfigureAll.

7.PostConfigure method and PostConfigureAll

These two methods and Configure, ConfigureAll similar method, except that they will be executed after Configure, ConfigureAll.

8. The order of execution of the plurality Configure, ConfigureAll, PostConfigure and the PostConfigureAll

Can be understood, it is to modify a Configure each variable named name provided, the following code as an example:

services.Configure<Theme>("ThemeBlack", theme => {
    theme.Color = "#000000";
    theme.Name = "Black";
}); 

This setting is to modify (Note to modify rather than replace) a "ThemeBlack" Theme of the type of variable called, if the variable does not exist, create and assign a Theme instance. This creates a number of variables, "variables (just for example, ignore the empty string as the name is not legitimate concerns)" called "empty string," ThemeBlue "," ThemeBlack.

Sequentially executed in accordance with the code sequence, which occurs when the same name if the code behind Configure, the value of the variable name corresponding modifications. If ConfigureAll method, the value of type Theme modify all of the variables.

And then after PostConfigure and PostConfigureAll Configure and ConfigureAll execution, even after the code is written in the Configure PostConfigure as well.

As to why this is such a rule, the next section will explain in detail.

Second, the mechanism to resolve internal processing

1. System start-up phase, dependency injection

On an example related to the three interfaces IOptions, IOptionsSnapshot and IOptionsMonitor, then to start from the three interfaces. Since Options mode by way of a generic interface to the three implantation served, then the system before they need to be injected to achieve the corresponding dependency injection vessel. This occurs when the system start-up created IHost, this time called a services.AddOptions () method Build method of HostBuilder, this method is defined in OptionsServiceCollectionExtensions, the code is as follows:

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

            services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
            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;
        }

        public static IServiceCollection Configure<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class
            => services.Configure(Options.Options.DefaultName, configureOptions);

        public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, Action<TOptions> configureOptions)
            where TOptions : class
        {
            //省略非空验证代码

            services.AddOptions();
            services.AddSingleton<IConfigureOptions<TOptions>>(new ConfigureNamedOptions<TOptions>(name, configureOptions));
            return services;
        }

        public static IServiceCollection ConfigureAll<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class
            => services.Configure(name: null, configureOptions: configureOptions);
//省略部分代码
    }

This shows that the effect of the method is to be served AddOptions injection, IOptions <>, IOptionsSnapshot <> corresponding implementation is OptionsManager <>, Singleton and were used only two kinds Scoped life cycle mode, IOptionsMonitor <> corresponding implementation is OptionsMonitor <>, likewise Singleton mode, which also verified on an example of conjecture. In addition to the above-mentioned three interfaces, as well as IOptionsFactory <> and IOptionsMonitorCache <> two interfaces, which is a very important part of two Options mode, the following contents will be used.

Configure two additional approach is the one used in the example of the specific method of Theme registered in the Options. The difference between the two is whether the option configuration naming, and the first method to Configure unnamed method, it can be seen by the above code is actually passed in a default Options.Options.DefaultName as the name, the default value is an empty string, that is, corresponds to unnamed Option is designated as an empty string. It is ultimately processed in a manner that is named second Configure method. There is also a ConfigureAll method, which is passed as a null name Option, is handed over to the second Configure process.

In the second method Configure still called once AddOptions method, and then is injected into the specific types. AddOptions methods are used in methods TryAdd injection, has not been injected is injected again. Next, a registered IConfigureOptions <TOptions> interface, the corresponding implementation is ConfigureNamedOptions <TOptions> (name, configureOptions), which code is as follows:

public class ConfigureNamedOptions<TOptions> : IConfigureNamedOptions<TOptions> where TOptions : class
{
    public ConfigureNamedOptions(string name, Action<TOptions> action)
    {
        Name = name;
        Action = action;
}

    public string Name { get; }
    public Action<TOptions> Action { get; }

    public virtual void Configure(string name, TOptions options)
    {
        if (options == null)
        {
            throw new ArgumentNullException(nameof(options));
        }

        // Null name is used to configure all named options.
        if (Name == null || name == Name)
        {
            Action?.Invoke(options);
        }
    }

    public void Configure(TOptions options) => Configure(Options.DefaultName, options);
}

It stores the name of the configuration of (Name) and a creation method (Action) in the constructor, it Configure two methods for performing the corresponding Action Options to get the value of the time to create an instance (e.g. Theme example). It will not be executed at this time. Therefore, in this type of ConfigureNamedOptions 3 will, respectively Name value specific value, the value of an empty string Name Name and null value. This corresponds to a method for the Configure Option named in the first example, the method is not Configure Option named, and ConfigureAll method.

Used herein ConfigureNamedOptions OptionsServiceCollectionExtensions and corresponding code directly registered by Option manner, for example, in the first example in the following manner:

services.Configure<Theme>("ThemeBlack", theme => { new Theme { Color = "#000000", Name = "Black" }; });

If the data source is a Configuration manner, for example, the following code

services.Configure<Theme>("ThemeBlue", Configuration.GetSection("Themes:0"));

OptionsServiceCollectionExtensions ConfigureNamedOptions is used and two subclasses, respectively, and NamedConfigureFromConfigurationOptions OptionsConfigurationServiceCollectionExtensions two classes, by their names may be known specifically for use as a data source for Configuration, similar to the code, but more than one IOptionsChangeTokenSource dependent on the injection, the role of the associated data source change Configuration on Options monitor the up and when the data source changes may update the value of the Options main Configure method code as follows:

public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config, Action<BinderOptions> configureBinder)
    where TOptions : class
{
    //省略验证代码

    services.AddOptions();
    services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, config));
    return services.AddSingleton<IConfigureOptions<TOptions>>(new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder));
}

Also there PostConfigure PostConfigureAll and methods, and Configure, ConfigureAll similar method, except that injection of type IPostConfigureOptions <TOptions>.

2. Options to get the value of

Option acquired value is acquired in the process vessel from the corresponding implementation dependency injection. Via dependency injection stage, already we know IOptions <> and IOptionsSnapshot <> corresponding implementation is OptionsManager <>, Take OptionsManager <>, for example look at the services provided after dependency injection process. OptionsManager <> code is as follows:

public class OptionsManager<TOptions> : IOptions<TOptions>, IOptionsSnapshot<TOptions> where TOptions : class, new()
{
    private readonly IOptionsFactory<TOptions> _factory;
private readonly OptionsCache<TOptions> _cache = new OptionsCache<TOptions>();

    public OptionsManager(IOptionsFactory<TOptions> factory)
    {
        _factory = factory;
    }

    public TOptions Value
    {
        get
        {
            return Get(Options.DefaultName);
        }
    }

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

It has <TOptions> and <TOptions> two important members of the OptionsCache IOptionsFactory. If direct access Value value, is actually called another Get (string name) method, passing an empty string as name value. Therefore, the final value is acquired in the read cache, the code here is _cache.GetOrAdd (name, () => _factory.Create (name)), i.e. if the corresponding value is present in the cache, then returns, if there is no , by _factory to create. OptionsFactory <TOptions> code is as follows:

public class OptionsFactory<TOptions> : IOptionsFactory<TOptions> where TOptions : class, new()
{
    private readonly IEnumerable<IConfigureOptions<TOptions>> _setups;
    private readonly IEnumerable<IPostConfigureOptions<TOptions>> _postConfigures;
    private readonly IEnumerable<IValidateOptions<TOptions>> _validations;

    public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures) : this(setups, postConfigures, validations: null)
    { }

    public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures, IEnumerable<IValidateOptions<TOptions>> validations)
    {
        _setups = setups;
        _postConfigures = postConfigures;
        _validations = validations;
}

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

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

        return options;
    }
}

Mainly to see its TOptions Create (string name) method. Here will traverse its _setups collection, this collection of type IEnumerable <IConfigureOptions <TOptions >>, when speaking Options depend on the mode of injection already know, every Configure, ConfigureAll actually registered to the dependency injection container, a IConfigureOptions <TOptions>, but the name may be different. And the PostConfigure PostConfigureAll registration method is IPostConfigureOptions <TOptions> type corresponds _postConfigures is set.

First _setups will traverse the collection calling IConfigureOptions <TOptions> Configure the method, this method is the main code:

 if (Name == null || name == Name)
 {
      Action?.Invoke(options);
 }

If the Name value is null, i.e. it corresponds ConfigureAll method, the Action is performed. Name value or values ​​to be read and the same, the Action is performed.

After _setups traverse the set, the same mechanism to traverse _postConfigures collection. This is the last one on the implementation of the order to verify Configure, ConfigureAll, PostConfigure and PostConfigureAll's.

Examples and eventually return the corresponding write cache. This is IOptions and processing mechanism IOptionsSnapshot two modes, let's look at IOptionsMonitor model, and it corresponds OptionsMonitor. code show as below:

public class OptionsMonitor<TOptions> : IOptionsMonitor<TOptions> where TOptions : class, new()
{
    private readonly IOptionsMonitorCache<TOptions> _cache;
    private readonly IOptionsFactory<TOptions> _factory;
    private readonly IEnumerable<IOptionsChangeTokenSource<TOptions>> _sources;
    internal event Action<TOptions, string> _onChange;

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

        foreach (var source in _sources)
        {
                var registration = ChangeToken.OnChange(
                      () => source.GetChangeToken(),
                      (name) => InvokeChanged(name),
                      source.Name);

                _registrations.Add(registration);        
}
    }

    private void InvokeChanged(string name)
    {
        name = name ?? Options.DefaultName;
        _cache.TryRemove(name);
        var 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;
    }

    internal class ChangeTrackerDisposable : IDisposable
    {
        private readonly Action<TOptions, string> _listener;
        private readonly OptionsMonitor<TOptions> _monitor;

        public ChangeTrackerDisposable(OptionsMonitor<TOptions> monitor, Action<TOptions, string> listener)
        {
            _listener = listener;
            _monitor = monitor;
        }

        public void OnChange(TOptions options, string name) => _listener.Invoke(options, name);

        public void Dispose() => _monitor._onChange -= OnChange;
    }
}

Most functions and OptionsManager similar, but because it is the use of the Singleton pattern, so it is the use of monitor and update the data source to change the mode. When the Configuration Option register as the data source of more than one IOptionsChangeTokenSource dependency injection. When the data source is changed and triggers update data OnChange (Action <TOptions, string> listener), related to the first example of the alert update data.

 

Guess you like

Origin www.cnblogs.com/FlyLolo/p/ASPNETCore_24.html
Recommended