ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

NetCore 入门 (五) : Options 模式

2022-08-27 15:33:52  阅读:212  来源: 互联网

标签:Options 入门 NetCore TOptions class services public name


1. QuickStart

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

Options模式具有如下特性:

  • 依赖注入:以依赖注入的方式使用Options对象;
  • 强类型:Options对象以POCO对象的方式进行定义,且支持对象验证;
  • 热更新:在数据源发生变更后能及时获得通知。

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扩展。这种方式只能进行配置后续配置

配置
  • Configure(configureOptions):配置无名称的Option对象
  • Configure(name, configureOptions):配置名为name的Option对象
  • ConfigureAll(configureOptions):配置所有类型为TOptions的对象
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

后续配置
  • PostConfigure(configureOptions):配置无名称的Option对象
  • PostConfigure(name, configureOptions):配置名为name的Option对象
  • PostConfigureAll(configureOptions):配置所有类型为TOptions的对象
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; }
}
  • Value:获取默认的Option对象
IOptionsSnapshot

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

public interface IOptionsSnapshot<out TOptions> : IOptions<TOptions> where TOptions : class, new()
{
    TOptions Get(string name);
}
  • Get方法:根据name获取Option对象
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);
}
  • CurrentValue: 获取Option对象的当前值
  • Get: 根据名称获取Option对象
  • OnChange: 注册数据变更的回调函数
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是否执行:

  • null: 对于所有的Option对象都执行。
  • 空字符串: 默认的Option对象可执行。
  • 不为空:只有名称为Name属性值的Option对象才可执行。

3.2.3 对DI的支持

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

  • ConfigureNamedOptions<TOptions, TDep>
  • ConfigureNamedOptions<TOptions, TDep1, TDep2>
  • ConfigureNamedOptions<TOptions, TDep1, TDep2, TDep3>
  • ConfigureNamedOptions<TOptions, TDep1, TDep2, TDep3, TDep4>
  • ConfigureNamedOptions<TOptions, TDep1, TDep2, TDep3, TDep4, TDep5>
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同样定义了一系列泛型对象。

  • PostConfigureOptions<TOptions, TDep>
  • PostConfigureOptions<TOptions, TDep1, TDep2>
  • PostConfigureOptions<TOptions, TDep1, TDep2, TDep3>
  • PostConfigureOptions<TOptions, TDep1, TDep2, TDep3, TDep4>
  • PostConfigureOptions<TOptions, TDep1, TDep2, TDep3, TDep4, TDep5>

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类:

  • Succeeded
  • Failed
  • Skipped

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

3.4.4 对DI的支持

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

  • ValidateOptions<TOptions, TDep>
  • ValidateOptions<TOptions, TDep1, TDep2>
  • ValidateOptions<TOptions, TDep1, TDep2, TDep3>
  • ValidateOptions<TOptions, TDep1, TDep2, TDep3, TDep4>
  • ValidateOptions<TOptions, TDep1, TDep2, TDep3, TDep4, TDep5>

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);
}
  • CurrentValue: 获取默认的Option对象
  • Get: 根据名称获取Option对象
  • OnChange: 注册数据变更的回调函数

5.2 IOptionsChangeTokenSource

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

public interface IOptionsChangeTokenSource<out TOptions>
{
	IChangeToken GetChangeToken();
	string Name { get; }
}
  • Name: Option对象的名称。

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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有