其他分享
首页 > 其他分享> > NetCore 入门 (五) : Options 模式

NetCore 入门 (五) : Options 模式

作者:互联网

1. QuickStart

Options模式可以说是Configuration的增强功能,Options模式存在的目的就是为了简化Configuration属性的读取和使用。但是从设计上讲,Options模式是完全独立的,有自己的完整的业务逻辑,并不依赖于Configuration。Options模式对Configuration功能的增强,是通过扩展的方式实现的。

Options模式具有如下特性:

1.1 NuGet包

Microsoft.Extensions.Options // options 定义
Microsoft.Extensions.Options.ConfigurationExtensions // 使Options模式支持Configuration数据源
Microsoft.Extensions.Options.DataAnnotations // DataAnnotations对数据验证的扩展

1.2 示例1 - 基本用法

使用Options模式一般需要经过3步:

  1. 启用Options模式
  2. 配置Options对象
  3. 获取Options对象
public class Profile // 定义Options对象
{
    public string Gender { get; set; }

    public int Age { get; set; }

    public Contact ContactInfo { get; set; }
}

public class Contact
{
    public string Email { get; set; }

    public string Phone { get; set; }
}
var services = new ServiceCollection();

services.AddOptions()                       // step1 启用Options模式
    .Configure<Profile>(it =>               // step2 配置Options对象
    {
        it.Age = 19;
        it.Gender = "男";
        it.ContactInfo = new Contact()
        {
            Email = "123@qq.com",
            Phone = "123456789"
        };
    });

var serviceProvider = services.BuildServiceProvider();

IOptions<Profile> options = serviceProvider
    .GetRequiredService<IOptions<Profile>>(); // step3 获取Options对象

Profile profile = options.Value;              // 读取Options对象的值

Debug.Assert(profile.Age == 19);
Debug.Assert(profile.Gender == "男");
Debug.Assert("123@qq.com".Equals(profile.ContactInfo.Email));
Debug.Assert("123456789".Equals(profile.ContactInfo.Phone));

1.3 示例2 - 具名Options对象

IOptionsSnapshot可以给每一个Options对象赋予一个名称。这种方式可以用来定义不同环境下的配置信息。

var services = new ServiceCollection();

services.AddOptions()
    .Configure<Profile>("foo", it =>  // 根据名称配置Options对象
    {
        it.Age = 18;
        it.Gender = "Male";
        it.ContactInfo = new Contact()
        {
            Email = "foo@126.com",
            Phone = "1234567890"
        };
    })
    .Configure<Profile>("bar", it =>
    {
        it.Age = 19;
        it.Gender = "Female";
        it.ContactInfo = new Contact()
        {
            Email = "bar@126.com",
            Phone = "12355"
        };
    });

var serviceProvider = services.BuildServiceProvider();

IOptionsSnapshot<Profile> options = serviceProvider
        .GetRequiredService<IOptionsSnapshot<Profile>>();

Profile profile = options.Get("foo"); // 根据名称获取Options对象

Debug.Assert(profile.Age == 18);
Debug.Assert(profile.Gender == "Male");
Debug.Assert("foo@126.com".Equals(profile.ContactInfo.Email));
Debug.Assert("1234567890".Equals(profile.ContactInfo.Phone));

1.4 示例3 - Configuration数据源

Configuration是Options模式最主要的数据源,使用之前,请添加扩展包Microsoft.Extensions.Options.ConfigurationExtensions

var config = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();

var services = new ServiceCollection();

services.AddOptions()
    //.Configure<Profile>(config)                           // 方式1
    //.Configure<Profile>("foo", config)                    // 方式2
    .Configure<Profile>("bar", config.GetSection("bar"));   // 方式3

ConfigureJsonValue

将Options对象直接与Json的Key绑定。

public static IServiceCollection ConfigureJsonValue<TOptions>(this IServiceCollection services, string key) where TOptions : class
{
    services.AddOptions();

    var config = services.BuildServiceProvider().GetService<IConfiguration>();

    var section = config.GetSection(key);
    if (section != null)
    {
        services.Configure<TOptions>(section);
    }

    return services;
}

使用示例:

services.ConfigureJsonValue<Profile>("Profile");
services.ConfigureJsonValue<ContactInfo>("Profile:ContactInfo");// 支持多级

1.5 示例4 - 热更新

Options模式 支持对配置源的监控,在检测到更新后及时加载最新的数据,并通过IChangeToken对象对外发送通知。

var config = new ConfigurationBuilder()
    .AddJsonFile(
        path: "changedSettings.json",
        optional: true,
        reloadOnChange: true) // 1. 开启配置热更新
    .Build();

var serviceProvider = new ServiceCollection()
    .AddOptions()
    .Configure<Profile>(config)
    .BuildServiceProvider();

IOptionsMonitor<Profile> options = serviceProvider.GetRequiredService<IOptionsMonitor<Profile>>(); // 2. 获取监控对象

options.OnChange(prf => // 3. 注册回调函数
{
    Console.WriteLine($"Gender:{prf.Gender}");
    Console.WriteLine($"Age:{prf.Age}");
    Console.WriteLine($"Email:{prf.ContactInfo.Email}");
    Console.WriteLine($"Phone:{prf.ContactInfo.Phone}");
});

var profile = options.CurrentValue; // 获取当前值

1.6 示例5 - 数据验证

Validate

var services = new ServiceCollection();

OptionsBuilder<Profile> builder = services
       .AddOptions<Profile>()
       .Configure(it =>
       {
           it.Age = 19;
           it.Gender = "男";
           it.ContactInfo = new Contact()
           {
               Email = "123@qq.com",
               Phone = "123456789"
           };
       })
       .Validate(it =>          // 添加验证函数
       {
           return it.Age > 20;
       }, "年龄必须大于20");


var serviceProvider = services.BuildServiceProvider();

try
{
    var options = serviceProvider.GetRequiredService<IOptions<Profile>>();

    var profile = options.Value; // 如果验证不通过,读取Value属性时抛出异常
}

catch (Exception ex)
{
    Console.WriteLine(ex.Message); // 输出:年龄必须大于20
}

数据注解

可以使用数据注解的方式进行Options对象的验证。使用之前请添加扩展包Microsoft.Extensions.Options.DataAnnotations

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

    [Required]
    public string Name { get; set; }
}
var services = new ServiceCollection();

OptionsBuilder<Student> builder = services
       .AddOptions<Student>()
       .Configure(it =>
       {
           it.Age = 19;
       })
       .ValidateDataAnnotations(); // 使用数据注解进行验证

var serviceProvider = services.BuildServiceProvider();

try
{
    var options = serviceProvider.GetRequiredService<IOptions<Student>>();

    var student = options.Value; // 如果验证不通过,读取Value属性时抛出异常
}

catch (Exception ex)
{
    //输出: DataAnnotation validation failed for members: 'Name' with the error: 'The Name field is required.'.
    Console.WriteLine(ex.Message); 
}

1.7 示例6 - 依赖其他对象

Options模式支持在配置Options对象的过程中,使用依赖注入服务。

public class ProfileService
{
    public void Print(Profile profile)
    {
        Console.WriteLine($"Gender:{profile.Gender}");
        Console.WriteLine($"Age:{profile.Age}");
        Console.WriteLine($"Email:{profile.ContactInfo.Email}");
        Console.WriteLine($"Phone:{profile.ContactInfo.Phone}");
    }
}
var services = new ServiceCollection()
    .AddSingleton<ProfileService>();  // 服务注册

OptionsBuilder<Profile> builder = services
       .AddOptions<Profile>()
       .Configure<ProfileService>((it, service) => // 添加对ProfileService的依赖
       {
           it.Age = 19;
           it.Gender = "男";
           it.ContactInfo = new Contact()
           {
               Email = "123@qq.com",
               Phone = "123456789"
           };
           Console.WriteLine("========Print Profile in Configure========");
           service.Print(it);
       })
       .PostConfigure<ProfileService>((it, service) => // 添加对ProfileService的依赖
       {
           Console.WriteLine("========Print Profile in PostConfigure========");
           service.Print(it);
       })
       .Validate<ProfileService>((it, service) => // 添加对ProfileService的依赖
       {
           Console.WriteLine("========Print Profile in Validate========");
           service.Print(it);
           return true;
       });


var serviceProvider = services.BuildServiceProvider();

var profile = serviceProvider.GetRequiredService<IOptions<Profile>>().Value;

结果输出

========Print Profile in Configure========
Gender:男
Age:19
Email:123@qq.com
Phone:123456789
========Print Profile in PostConfigure========
Gender:男
Age:19
Email:123@qq.com
Phone:123456789
========Print Profile in Validate========
Gender:男
Age:19
Email:123@qq.com
Phone:123456789

2. 模型解析

2.1 开启Options模式

通过AddOptions方法开启Options模式。

var services = new ServiceCollection();

services.AddOptions();

我们来看看AddOptions方法的实现:

public static class OptionsServiceCollectionExtensions
{
    public static IServiceCollection AddOptions(this IServiceCollection 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;
    }
}

AddOptions方法注册了5个服务,这5个服务是实现Options模式的核心。

::: tip
要注意每个服务的生命周期,不同的生命周期对Options对象的获取会有一定的影响。
:::

2.2 配置Options对象

配置Options对象一般需要经过以下3个步骤,每个步骤实现特定的功能,且都有与之对应的一个接口:

  1. 配置IConfigureOptions,为Options对象赋值
  2. 后续配置:[可选] IPostConfigureOptions,做些额外的功能,比如日志记录、数据审计等
  3. 验证:[可选] IValidateOptions,验证Options对象的有效性

2.2.1 IServiceCollection扩展

Options对象的配置有2种方式:一种是IServiceCollection接口的扩展方法,另一种是OptionBuilder类。

先来介绍IServiceCollection扩展。这种方式只能进行配置后续配置

配置
public static class OptionsServiceCollectionExtensions
{
    public static IServiceCollection Configure<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) 
        where TOptions : class
    {
        return services.Configure(Options.DefaultName, configureOptions);
    }

    public static IServiceCollection Configure<TOptions>(this IServiceCollection services, 
        string name, Action<TOptions> configureOptions) 
        where TOptions : class
    {
        services.AddOptions();
        services.AddSingleton(new ConfigureNamedOptions<TOptions>(name, configureOptions)); // 实现的核心
        return services;
    }

    public static IServiceCollection ConfigureAll<TOptions>(this IServiceCollection services, 
        Action<TOptions> configureOptions) 
        where TOptions : class
    {
        return services.Configure(null, configureOptions);
    }
}

这一步骤其实是注册了一个名为ConfigureNamedOptions的单例服务。至于为什么要这样做,请参考IOptionsFactory

后续配置
public static class OptionsServiceCollectionExtensions
{
    public static IServiceCollection PostConfigure<TOptions>(this IServiceCollection services, 
        Action<TOptions> configureOptions) 
        where TOptions : class
    {
        return services.PostConfigure(Options.DefaultName, configureOptions);
    }

    public static IServiceCollection PostConfigure<TOptions>(this IServiceCollection services, 
        string name, Action<TOptions> configureOptions) 
        where TOptions : class
    {
        services.AddOptions();
        services.AddSingleton(new PostConfigureOptions<TOptions>(name, configureOptions)); // 实现的核心
        return services;
    }

    public static IServiceCollection PostConfigureAll<TOptions>(this IServiceCollection services, 
        Action<TOptions> configureOptions) 
        where TOptions : class
    {
        return services.PostConfigure(null, configureOptions);
    }
}

2.2.2 OptionBuilder

OptionBuilder这种方式完全涵盖了配置后续配置验证这三个步骤。在此基础上,允许在每个步骤的配置过程中,使用其他的依赖服务。

OptionsBuilder的定义
public class OptionsBuilder<TOptions> where TOptions : class
{
    public string Name { get; }
    public IServiceCollection Services { get; }

    public OptionsBuilder(IServiceCollection services, string name)
    {
        this.Services = services;
        this.Name = (name ?? Options.DefaultName);
    }
}
启用OptionBuilder
public static class OptionsServiceCollectionExtensions
{
    public static OptionsBuilder<TOptions> AddOptions<TOptions>(this IServiceCollection services) 
        where TOptions : class
    {
        return services.AddOptions(Options.DefaultName);
    }

    public static OptionsBuilder<TOptions> AddOptions<TOptions>(this IServiceCollection services, string name) 
        where TOptions : class
    {
        services.AddOptions();
        return new OptionsBuilder<TOptions>(services, name);
    }
}
配置
public class OptionsBuilder<TOptions> where TOptions : class
{
    public virtual OptionsBuilder<TOptions> Configure(Action<TOptions> configureOptions)
    {
        this.Services.AddSingleton(new ConfigureNamedOptions<TOptions>(this.Name, configureOptions));
        return this;
    }

    // 依赖外部服务TDep
    public virtual OptionsBuilder<TOptions> Configure<TDep>(Action<TOptions, TDep> configureOptions) 
        where TDep : class
    {
        this.Services.AddTransient((IServiceProvider sp) => new ConfigureNamedOptions<TOptions, TDep>(
                this.Name, 
                sp.GetRequiredService<TDep>(), 
                configureOptions)
            );
        return this;
    }

    // 依赖外部服务TDep1, TDep2, TDep3, TDep4, TDep5
    public virtual OptionsBuilder<TOptions> Configure<TDep1, TDep2>(Action<TOptions, TDep1, TDep2> configureOptions);
    public virtual OptionsBuilder<TOptions> Configure<TDep1, TDep2, TDep3>(...);
    public virtual OptionsBuilder<TOptions> Configure<TDep1, TDep2, TDep3, TDep4>(...);
    public virtual OptionsBuilder<TOptions> Configure<TDep1, TDep2, TDep3, TDep4, TDep5>(...);
}

这种方式的实现也是注册了一个名为ConfigureNamedOptions的单例服务。

后续配置
public class OptionsBuilder<TOptions> where TOptions : class
{
    public virtual OptionsBuilder<TOptions> PostConfigure(Action<TOptions> configureOptions)
    {
        this.Services.AddSingleton(new PostConfigureOptions<TOptions>(this.Name, configureOptions));
        return this;
    }

    // 依赖外部服务TDep
    public virtual OptionsBuilder<TOptions> PostConfigure<TDep>(Action<TOptions, TDep> configureOptions) 
        where TDep : class
    {
        this.Services.AddTransient((IServiceProvider sp) => new PostConfigureOptions<TOptions, TDep>(
                this.Name, 
                sp.GetRequiredService<TDep>(), 
                configureOptions)
            );
        return this;
    }

    // 依赖外部服务TDep1, TDep2, TDep3, TDep4, TDep5
    public virtual OptionsBuilder<TOptions> PostConfigure<TDep1, TDep2>(Action<TOptions, TDep1, TDep2> configureOptions);
    public virtual OptionsBuilder<TOptions> PostConfigure<TDep1, TDep2, TDep3>(...);
    public virtual OptionsBuilder<TOptions> PostConfigure<TDep1, TDep2, TDep3, TDep4>(...);
    public virtual OptionsBuilder<TOptions> PostConfigure<TDep1, TDep2, TDep3, TDep4, TDep5>(...);
}
验证
public class OptionsBuilder<TOptions> where TOptions : class
{
    public virtual OptionsBuilder<TOptions> Validate(Func<TOptions, bool> validation)
    {
        return this.Validate(validation, "A validation error has occured.");
    }

    public virtual OptionsBuilder<TOptions> Validate(Func<TOptions, bool> validation, string failureMessage)
    {
        this.Services.AddSingleton(new ValidateOptions<TOptions>(this.Name, validation, failureMessage));
        return this;
    }


    // 依赖外部服务
    public virtual OptionsBuilder<TOptions> Validate<TDep>(Func<TOptions, TDep, bool> validation)
    {
        return this.Validate<TDep>(validation, "A validation error has occured.");
    }

    public virtual OptionsBuilder<TOptions> Validate<TDep>(Func<TOptions, TDep, bool> validation, string failureMessage)
    {
        this.Services.AddTransient((IServiceProvider sp) => new ValidateOptions<TOptions, TDep>(
                this.Name, 
                sp.GetRequiredService<TDep>(), 
                validation, 
                failureMessage)
            );
        return this;
    }
    
    // 依赖外部服务TDep1, TDep2, TDep3, TDep4, TDep5
    public virtual OptionsBuilder<TOptions> Validate<TDep1, TDep2>(...);
    public virtual OptionsBuilder<TOptions> Validate<TDep1, TDep2, TDep3>(...);
    public virtual OptionsBuilder<TOptions> Validate<TDep1, TDep2, TDep3, TDep4>(...);
    public virtual OptionsBuilder<TOptions> Validate<TDep1, TDep2, TDep3, TDep4, TDep5>(...);
}

2.3 获取Options对象

2.3.1 IOptions & IOptionsSnapshot

OptionManager

IOptions
public interface IOptions<out TOptions> where TOptions : class, new()
{
    TOptions Value { get; }
}
IOptionsSnapshot

IOptionsSnapshot继承IOptions接口,可以根据名称获取对应的Options对象。

public interface IOptionsSnapshot<out TOptions> : IOptions<TOptions> where TOptions : class, new()
{
    TOptions Get(string name);
}
OptionsManager

OptionsManager是接口IOptions和接口IOptionsSnapshot的默认实现。

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)
    {
        this._factory = factory;
    }

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

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

public static class Options
{
    public static readonly string DefaultName = string.Empty; // 空字符串 即 ''
}
  1. 通过属性Value我们可以发现,默认Option对象也是通过Get方法获得的,只不过名称为空字符串('')。

2.3.2 IOptionsMonitor

该接口旨在实现针对承载Options对象的原始数据源的监控,检测到数据更新后及时通知外部做相应处理。本节只介绍Options对象的获取部分。

public interface IOptionsMonitor<out TOptions>
{
	TOptions CurrentValue { get; }
	TOptions Get(string name);
	IDisposable OnChange(Action<TOptions, string> listener);
}
OptionsMonitor

OptionsMonitorIOptionsMonitor接口的默认实现。

public class OptionsMonitor<TOptions> : IOptionsMonitor<TOptions>, IDisposable 
    where TOptions : class, new()
{
    private readonly IOptionsMonitorCache<TOptions> _cache;

    public OptionsMonitor(IOptionsMonitorCache<TOptions> cache) // 通过依赖注入引用IOptionsMonitorCache
    {
        this._cache = cache;
    }

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

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

3. Options对象的创建

核心类

配置类

后续配置类

验证类

3.1 IOptionsFactory

IOptionsFactory接口负责Option对象的创建。

public interface IOptionsFactory<TOptions> where TOptions : class, new()
{
    TOptions Create(string name);
}

一般来说,Option对象的创建包括3个步骤,每个步骤都对应一个接口:

  1. 配置 --> IConfigureOptions
  2. 后续配置 --> IPostConfigureOptions
  3. 验证 --> IValidateOptions

OptionsFactory

OptionsFactoryIOptionsFactory接口的默认实现。

public class OptionsFactory<TOptions> : IOptionsFactory<TOptions> where TOptions : class, new()
{
    public OptionsFactory(
        IEnumerable<IConfigureOptions<TOptions>> setups, 
        IEnumerable<IPostConfigureOptions<TOptions>> postConfigures, 
        IEnumerable<IValidateOptions<TOptions>> validations)
    {
        this._setups = setups;
        this._postConfigures = postConfigures;
        this._validations = validations;
    }

    private readonly IEnumerable<IConfigureOptions<TOptions>> _setups;
    private readonly IEnumerable<IPostConfigureOptions<TOptions>> _postConfigures;
    private readonly IEnumerable<IValidateOptions<TOptions>> _validations;
}

通过构造函数可以看出,OptionsFactory通过依赖注入服务依赖于接口IConfigureOptionsIPostConfigureOptionsIValidateOptions

Create方法

public class OptionsFactory<TOptions> : IOptionsFactory<TOptions> where TOptions : class, new()
{
    public TOptions Create(string name)
    {
        // 实例化TOptions
        TOptions toptions = Activator.CreateInstance<TOptions>();

        // 步骤1:配置
        foreach (IConfigureOptions<TOptions> configureOptions in this._setups)
        {
            IConfigureNamedOptions<TOptions> configureNamedOptions = configureOptions as IConfigureNamedOptions<TOptions>;
            if (configureNamedOptions != null)
            {
                configureNamedOptions.Configure(name, toptions);
            }
            else if (name == Options.DefaultName)
            {
                configureOptions.Configure(toptions);
            }
        }

        // 步骤2:后续配置
        foreach (IPostConfigureOptions<TOptions> postConfigureOptions in this._postConfigures)
        {
            postConfigureOptions.PostConfigure(name, toptions);
        }

        // 步骤3:验证
        if (this._validations != null)
        {
            List<string> list = new List<string>();
            foreach (IValidateOptions<TOptions> validateOptions in this._validations)
            {
                ValidateOptionsResult validateOptionsResult = validateOptions.Validate(name, toptions);
                if (validateOptionsResult.Failed)
                {
                    list.AddRange(validateOptionsResult.Failures);
                }
            }
            if (list.Count > 0)
            {
                throw new OptionsValidationException(name, typeof(TOptions), list);
            }
        }
        return toptions;
    }
}

3.2 配置

3.2.1 IConfigureOptions

IConfigureOptions接口用来定义配置Option对象的回调函数。

public interface IConfigureOptions<in TOptions> where TOptions : class
{
    void Configure(TOptions options);
}

3.2.2 IConfigureNamedOptions

具名Option对象的配置回调函数。

public interface IConfigureNamedOptions<in TOptions> : IConfigureOptions<TOptions> where TOptions : class
{
    void Configure(string name, TOptions options);
}
ConfigureNamedOptions

ConfigureNamedOptions实现了接口IConfigureOptionsIConfigureNamedOptions

public class ConfigureNamedOptions<TOptions> : IConfigureNamedOptions<TOptions>, IConfigureOptions<TOptions> 
    where TOptions : class
{
    public ConfigureNamedOptions(string name, Action<TOptions> action)
    {
        this.Name = name;
        this.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("options");
        }
        if (this.Name == null || name == this.Name) // Name的取值决定了Action是否执行
        {
            Action<TOptions> action = this.Action;
            if (action == null)
            {
                return;
            }
            action(options);
        }
    }

    public void Configure(TOptions options)
    {
        this.Configure(Options.DefaultName, options);
    }
}

从高亮行可以看出,属性Name的取值决定了回调函数Action是否执行:

3.2.3 对DI的支持

为了提高配置函数的灵活性,ConfigureNamedOptions增加了对依赖注入服务的支持,定义了一系列泛型对象。

ConfigureNamedOptions<TOptions, TDep>
public class ConfigureNamedOptions<TOptions, TDep> : IConfigureNamedOptions<TOptions>, IConfigureOptions<TOptions> 
    where TOptions : class 
    where TDep : class
{
	public ConfigureNamedOptions(string name, TDep dependency, Action<TOptions, TDep> action)
	{
        ...
		this.Dependency = dependency;
	}

    public TDep Dependency { get; }
}
ConfigureNamedOptions<TOptions, TDep1, TDep2>
public class ConfigureNamedOptions<TOptions, TDep1, TDep2> : IConfigureNamedOptions<TOptions>, IConfigureOptions<TOptions> 
    where TOptions : class 
    where TDep1 : class 
    where TDep2 : class
{
    public ConfigureNamedOptions(string name, TDep1 dependency, TDep2 dependency2, Action<TOptions, TDep1, TDep2> action)
    {
        ...
        this.Dependency1 = dependency;
        this.Dependency2 = dependency2;
    }

    public TDep1 Dependency1 { get; }

    public TDep2 Dependency2 { get; }
}

3.3 后续配置

3.3.1 IPostConfigureOptions

IPostConfigureOptions接口用来定义后续配置Option对象的回调函数。

public interface IPostConfigureOptions<in TOptions> where TOptions : class
{
    void PostConfigure(string name, TOptions options);
}

3.3.2 PostConfigureOptions

PostConfigureOptions是接口IPostConfigureOptions的默认实现。

public class PostConfigureOptions<TOptions> : IPostConfigureOptions<TOptions> where TOptions : class
{
    public PostConfigureOptions(string name, Action<TOptions> action)
    {
        this.Name = name;
        this.Action = action;
    }

    public string Name { get; }

    public Action<TOptions> Action { get; }

    public virtual void PostConfigure(string name, TOptions options)
    {
        if (options == null)
        {
            throw new ArgumentNullException("options");
        }
        if (this.Name == null || name == this.Name) // Name的取值决定了Action是否执行
        {
            Action<TOptions> action = this.Action;
            if (action == null)
            {
                return;
            }
            action(options);
        }
    }
}

3.3.3 对DI的支持

PostConfigureOptions同样定义了一系列泛型对象。

3.4 验证

3.4.1 IValidateOptions

public interface IValidateOptions<TOptions> where TOptions : class
{
    ValidateOptionsResult Validate(string name, TOptions options);
}

3.4.2 ValidateOptions

public class ValidateOptions<TOptions> : IValidateOptions<TOptions> where TOptions : class
{
    public ValidateOptions(string name, Func<TOptions, bool> validation, string failureMessage)
    {
        this.Name = name;
        this.Validation = validation;
        this.FailureMessage = failureMessage;
    }

    public string Name { get; }

    public Func<TOptions, bool> Validation { get; }

    public string FailureMessage { get; }

    public ValidateOptionsResult Validate(string name, TOptions options)
    {
        if (this.Name != null && !(name == this.Name)) //当名称不一致时,返回Skipped
        {
            return ValidateOptionsResult.Skip;
        }
        Func<TOptions, bool> validation = this.Validation;
        if (((validation != null) ? new bool?(validation(options)) : null).Value)
        {
            return ValidateOptionsResult.Success;
        }
        return ValidateOptionsResult.Fail(this.FailureMessage);
    }
}

3.4.3 ValidateOptionsResult

public class ValidateOptionsResult
{
    public bool Succeeded { get; protected set; }
    public bool Skipped { get; protected set; }
    public bool Failed { get; protected set; }

    public string FailureMessage { get; protected set; }
    public IEnumerable<string> Failures { get; protected set; }
}
public class ValidateOptionsResult
{
    public static ValidateOptionsResult Fail(string failureMessage);
    public static ValidateOptionsResult Fail(IEnumerable<string> failures);

    public static readonly ValidateOptionsResult Skip;
    public static readonly ValidateOptionsResult Success;
}

Option对象的验证结果分为3类:

ValidateOptions对象的名称与Option对象的名称不一致时,返回Skipped

3.4.4 对DI的支持

ValidateOptions也定义了一系列泛型对象。

4. Options对象的缓存

OptionCache

IOptionsMonitorCache

public interface IOptionsMonitorCache<TOptions> where TOptions : class
{
    void Clear();
    TOptions GetOrAdd(string name, Func<TOptions> createOptions);
    bool TryAdd(string name, TOptions options);
    bool TryRemove(string name);
}

OptionsCache

public class OptionsCache<TOptions> : IOptionsMonitorCache<TOptions> where TOptions : class
{
    private readonly ConcurrentDictionary<string, Lazy<TOptions>> _cache = 
        new ConcurrentDictionary<string, Lazy<TOptions>>(StringComparer.Ordinal);

    public void Clear()
    {
        this._cache.Clear();
    }

    public virtual TOptions GetOrAdd(string name, Func<TOptions> createOptions)
    {
        name = (name ?? Options.DefaultName);
        return this._cache.GetOrAdd(name, new Lazy<TOptions>(createOptions)).Value;
    }

    public virtual bool TryAdd(string name, TOptions options)
    {
        name = (name ?? Options.DefaultName);
        return this._cache.TryAdd(name, new Lazy<TOptions>(() => options));
    }

    public virtual bool TryRemove(string name)
    {
        name = (name ?? Options.DefaultName);
        Lazy<TOptions> lazy;
        return this._cache.TryRemove(name, out lazy);
    }
}

5. Options对象的监控

OptionMonitor

5.1 IOptionsMonitor

该接口旨在实现针对承载Options对象的原始数据源的监控,检测到数据更新后及时通知外部做相应处理。

public interface IOptionsMonitor<out TOptions>
{
	TOptions CurrentValue { get; }
	TOptions Get(string name);
	IDisposable OnChange(Action<TOptions, string> listener);
}

5.2 IOptionsChangeTokenSource

检测到数据变化后,通过IChangeToken对象向外发送通知。

public interface IOptionsChangeTokenSource<out TOptions>
{
	IChangeToken GetChangeToken();
	string Name { get; }
}

5.3 OptionsMonitor

5.3.1 监听数据源

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

    public OptionsMonitor(IOptionsFactory<TOptions> factory, 
        IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, 
        IOptionsMonitorCache<TOptions> cache)
    {
        this._factory = factory;
        this._sources = sources;
        this._cache = cache;
        using (IEnumerator<IOptionsChangeTokenSource<TOptions>> enumerator = this._sources.GetEnumerator())
        {
            while (enumerator.MoveNext())
            {
                IOptionsChangeTokenSource<TOptions> source = enumerator.Current;
                ChangeToken.OnChange<string>(() => source.GetChangeToken(), delegate(string name)
                {
                    this.InvokeChanged(name);
                }, source.Name);
            }
        }
    }
}

5.3.2 获取Option对象

public class OptionsMonitor<TOptions> : IOptionsMonitor<TOptions>, IDisposable 
    where TOptions : class, new()
{
    public TOptions CurrentValue
    {
        get
        {
            return this.Get(Options.DefaultName);
        }
    }

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

5.3.3 回调函数的注册

public class OptionsMonitor<TOptions> : IOptionsMonitor<TOptions>, IDisposable 
    where TOptions : class, new()
{
    internal event Action<TOptions, string> _onChange;

    public IDisposable OnChange(Action<TOptions, string> listener)
    {
        ChangeTrackerDisposable changeTrackerDisposable = new ChangeTrackerDisposable(this, listener);
        this._onChange += changeTrackerDisposable.OnChange;
        return changeTrackerDisposable;
    }

内部类ChangeTrackerDisposable

public class OptionsMonitor<TOptions> : IOptionsMonitor<TOptions>, IDisposable 
    where TOptions : class, new()
{
    internal class ChangeTrackerDisposable : IDisposable
    {
        private readonly Action<TOptions, string> _listener;
        private readonly OptionsMonitor<TOptions> _monitor;

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

        public void OnChange(TOptions options, string name)
        {
            this._listener(options, name);
        }

        public void Dispose()
        {
            this._monitor._onChange -= this.OnChange;
        }
    }
}
  1. OnChange方法将所有的回调函数注册到事件_onChange上,通过该事件触发回调函数。
  2. OnChange方法返回ChangeTrackerDisposable对象,该对象实现了IDisposable接口。通过ChangeTrackerDisposable对象的释放,可以解除回调函数的注册。

5.3.4 触发回调函数

public class OptionsMonitor<TOptions> : IOptionsMonitor<TOptions>, IDisposable 
    where TOptions : class, new()
{
    private void InvokeChanged(string name)
    {
        name = (name ?? Options.DefaultName);
        this._cache.TryRemove(name);
        TOptions arg = this.Get(name);
        if (this._onChange != null)
        {
            this._onChange(arg, name);
        }
    }
}

经历了下面3个步骤:

  1. 清除缓存
  2. 获取最新的Option对象
  3. 调用回调函数

6. 扩展

6.1 Configuration数据源

NuGet包

Microsoft.Extensions.Options.ConfigurationExtensions

IServiceCollection扩展

public static class OptionsConfigurationServiceCollectionExtensions
{
    public static IServiceCollection Configure<TOptions>(this IServiceCollection services, IConfiguration config) 
        where TOptions : class;
    public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, 
        IConfiguration config) 
        where TOptions : class;
    public static IServiceCollection Configure<TOptions>(this IServiceCollection services, 
        IConfiguration config, 
        Action<BinderOptions> configureBinder) 
        where TOptions : class;

    
    public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config, 
        Action<BinderOptions> configureBinder) 
        where TOptions : class
    {
        services.AddOptions();
        services.AddSingleton(new ConfigurationChangeTokenSource<TOptions>(name, config));
        return services.AddSingleton(new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder));
    }
}

NamedConfigureFromConfigurationOptions

定义一个IConfigureNamedOptions接口的实现。

public class NamedConfigureFromConfigurationOptions<TOptions> : ConfigureNamedOptions<TOptions> where TOptions : class
{
    public NamedConfigureFromConfigurationOptions(string name, IConfiguration config, Action<BinderOptions> configureBinder) 
        : base(name, delegate(TOptions options)
            {
                config.Bind(options, configureBinder);
            })
    {

    }
}

ConfigurationChangeTokenSource

定义一个IOptionsChangeTokenSource接口的实现,用来监控配置源的变化。

public class ConfigurationChangeTokenSource<TOptions> : IOptionsChangeTokenSource<TOptions>
{
    private IConfiguration _config;

    public ConfigurationChangeTokenSource(string name, IConfiguration config)
    {
        this._config = config;
        this.Name = (name ?? Options.DefaultName);
    }

    public string Name { get; }

    public IChangeToken GetChangeToken()
    {
        return this._config.GetReloadToken();
    }
}

6.2 DataAnnotation验证

NuGet包

Microsoft.Extensions.Options.DataAnnotations

OptionsBuilder扩展

public static class OptionsBuilderDataAnnotationsExtensions
{
    public static OptionsBuilder<TOptions> ValidateDataAnnotations<TOptions>(this OptionsBuilder<TOptions> optionsBuilder) 
        where TOptions : class
    {
        optionsBuilder.Services.AddSingleton(new DataAnnotationValidateOptions<TOptions>(optionsBuilder.Name));
        return optionsBuilder;
    }
}

DataAnnotationValidateOptions
定义一个IValidateOptions接口的实现。

public class DataAnnotationValidateOptions<TOptions> : IValidateOptions<TOptions> where TOptions : class
{
    public DataAnnotationValidateOptions(string name){}

    public ValidateOptionsResult Validate(string name, TOptions options)
    {
        if (this.Name != null && !(name == this.Name))
        {
            return ValidateOptionsResult.Skip;
        }
        List<ValidationResult> list = new List<ValidationResult>();
        if (Validator.TryValidateObject(options, new ValidationContext(options, null, null), list, true))
        {
            return ValidateOptionsResult.Success;
        }

        ...
    }
}

调用Validator.TryValidateObject进行基于 数据注解 的模型验证,验证通过返回true。

标签:Options,入门,NetCore,TOptions,class,services,public,name
来源: https://www.cnblogs.com/renzhsh/p/16630614.html