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步:
- 启用Options模式
- 配置Options对象
- 获取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 = "[email protected]",
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("[email protected]".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 = "[email protected]",
Phone = "1234567890"
};
})
.Configure<Profile>("bar", it =>
{
it.Age = 19;
it.Gender = "Female";
it.ContactInfo = new Contact()
{
Email = "[email protected]",
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("[email protected]".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 = "[email protected]",
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 = "[email protected]",
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:[email protected]
Phone:123456789
========Print Profile in PostConfigure========
Gender:男
Age:19
Email:[email protected]
Phone:123456789
========Print Profile in Validate========
Gender:男
Age:19
Email:[email protected]
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模式的核心。
- IOptions 和 IOptionsSnapshot:获取Options对象
- IOptionsFactory:创建Options对象
- IOptionsMonitorCache:对Options对象提供缓存功能
- IOptionsMonitor:Options对象的热加载
::: tip
要注意每个服务的生命周期,不同的生命周期对Options对象的获取会有一定的影响。
:::
2.2 配置Options对象
配置Options对象一般需要经过以下3个步骤,每个步骤实现特定的功能,且都有与之对应的一个接口:
- 配置:
IConfigureOptions
,为Options对象赋值 - 后续配置:[可选]
IPostConfigureOptions
,做些额外的功能,比如日志记录、数据审计等 - 验证:[可选]
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
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; // 空字符串 即 ''
}
- 通过属性
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
OptionsMonitor
是IOptionsMonitor
接口的默认实现。
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个步骤,每个步骤都对应一个接口:
- 配置 -->
IConfigureOptions
- 后续配置 -->
IPostConfigureOptions
- 验证 -->
IValidateOptions
OptionsFactory
OptionsFactory
是IOptionsFactory
接口的默认实现。
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
通过依赖注入服务依赖于接口IConfigureOptions
、IPostConfigureOptions
和IValidateOptions
。
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
实现了接口IConfigureOptions
和IConfigureNamedOptions
。
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对象的缓存
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对象的监控
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;
}
}
}
OnChange
方法将所有的回调函数注册到事件_onChange
上,通过该事件触发回调函数。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个步骤:
- 清除缓存
- 获取最新的Option对象
- 调用回调函数
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。