.Net 5 Options 选项
DotNet Configuration 源码:https://github.com/dotnet/runtime/tree/master/src/libraries/,其中以 Microsoft.Extensions.Options 开头的项目
Options 官方文档:
- https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-5.0
- https://docs.microsoft.com/en-us/dotnet/core/extensions/options
Nuget包:Microsoft.Extensions.Options
建议先掌握:
- DependencyInjection ( 依赖注入 ):
- Configuration ( 配置 ) :
介绍
Options
提供了对配置数据的强类型访问
Options
提供了验证选项的机制
Configure
首先声明一个选项类
public class MyOption
{
public string A { get; set; }
public string B { get; set; }
}
使用Configure
扩展方法对选项类进行配置
var serviceCollection = new ServiceCollection();
// 配置 MyOption
serviceCollection.Configure<MyOption>(n =>
{
n.A = "A Value";
n.B = "B Value";
});
var serviceProvider = serviceCollection.BuildServiceProvider();
获取IOptions<MyOption>
选项服务
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
MyOption myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A);
Console.WriteLine(myOptionValue.B);
在http://Aps.Net 中使用
配置
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.Configure<MyOption>(option =>
{
option.A = "A Value";
option.B = "A Value";
});
}
注入Ioption<MyOption>
[Route("Home")]
public class HomeController : ControllerBase
{
private readonly MyOption _myOption;
public HomeController(IOptions<MyOption> myOptions)
{
_myOption = myOptions.Value;
}
[HttpGet]
public string GetAsync()
{
return $"A:{_myOption.A},B:{_myOption.B}";
}
}
绑定配置,使用IConfiguration
来配置选项
Nuget包:Microsoft.Extensions.Options.ConfigurationExtensions
Configure<TOptions>(IConfigureation configuration)
不仅仅会绑定配置,还会添加对配置文件的更改通知,通知IOptionsMonitor
重新计算选项
实例:
appsettings.json
{
"MySetting": {
"A": "A Value",
"B": "B Value"
}
}
C#
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var serviceCollection = new ServiceCollection();
serviceCollection.Configure<MyOption>(configuration.GetSection("MySetting"));
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
var myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A);
Console.WriteLine(myOptionValue.B);
PostConfigure
PostConfigure
会在 Configure
之后进行配置
实例:
var serviceCollection = new ServiceCollection();
serviceCollection.PostConfigure<MyOption>(option =>
{
option.B = "PostConfigure B Value";
});
serviceCollection.Configure<MyOption>(n =>
{
n.A = "A Value";
n.B = "B Value";
});
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
MyOption myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A); // A Value
Console.WriteLine(myOptionValue.B); // PostConfigure B Value
命名选项
可以给选项命名
默认选项名称为空字符串
IOptions
不支持获取命名选项,只能获取默认命名选项
可以使用IOptionsSnapshot
、IOptionsMonitor
等获取命名选项
IOptionsSnapshot
继承子IOptions
并添加了Get(string optionName)
方法来获取命名选项
IOptionsMonitor
的CurrentValue
属性用于获取默认选项,Get(string optionName)
方法来获取命名选项
实例:
var serviceCollection = new ServiceCollection();
// 配置 MyOption
serviceCollection.Configure<MyOption>(n =>
{
n.A = "A Value";
n.B = "B Value";
});
serviceCollection.Configure<MyOption>("My", n =>
{
n.A = "My:A Value";
n.B = "My:B Value";
});
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
MyOption myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A); // A Value
Console.WriteLine(myOptionValue.B); // B Value
// IOptionsSnapshot 获取命名选项
var myOption2 = serviceProvider.GetRequiredService<IOptionsSnapshot<MyOption>>();
MyOption myOptionValue2 = myOption2.Get("My");
Console.WriteLine(myOptionValue2.A); // My:A Value
Console.WriteLine(myOptionValue2.B); // My:B Value
// IOptionsMonitor 获取命名选项
var myOptionsMonitor = serviceProvider.GetRequiredService<IOptionsMonitor<MyOption>>();
MyOption myOptionValue3 = myOptionsMonitor.Get("My");
Console.WriteLine(myOptionValue3.A);
Console.WriteLine(myOptionValue3.B);
ConfigureAll、PostConfigureAll
ConfigureAll
、PostConfigureAll
可以配置全部的命名配置
实例:
var serviceCollection = new ServiceCollection();
serviceCollection.ConfigureAll<MyOption>(n =>
{
n.A = "A Value,Config By ConfigureAll";
});
// 配置 MyOption
serviceCollection.Configure<MyOption>(n =>
{
n.B = "B Value";
});
serviceCollection.Configure<MyOption>("My", n =>
{
n.B = "My:B Value";
});
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
MyOption myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A); // A Value,Config By ConfigureAll
Console.WriteLine(myOptionValue.B); // B Value
var myOption2 = serviceProvider.GetRequiredService<IOptionsSnapshot<MyOption>>();
MyOption myOptionValue2 = myOption2.Get("My");
Console.WriteLine(myOptionValue2.A); // A Value,Config By ConfigureAll
Console.WriteLine(myOptionValue2.B); // My:B Value
IOptions、IOptionsSnapshot、IOptionsMonitor
IOptions<TOptions>
- 只会计算一次选项
- 不支持命名选项
- 单例服务,可以注入到任何服务生存期
IOptionsSnapshot<TOptions>
- 会计算多次选项,每次请求时应重新计算选项
- 注册为范围内,因此无法注入到单一实例服务
- 支持命名选项
IOptionsMonitor<TOptions>
:
- 单例服务,可以注入到任何服务生存期
- 支持更改通知 ( 比如配置文件更改通知 )
- 支持命名选项
- 支持重载配置
- 选择性选项失效 (IOptionsMonitorCache)
IOptionsFactory<TOptions>
负责创建、计算选项实例,IOptions<TOptions>
、IOptionsSnapshot<TOptions>
、IOptionsMonitor<TOptions>
都使用它来创建选项实例
对比实例:
appsettings.json
{
"MySetting": {
"B": "B 1"
}
}
C# Startup
public class Startup
{
public IConfiguration Configuration { get; }
public Startup( IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.Configure<MyOption>(option => { option.A = DateTime.Now.ToString(CultureInfo.CurrentCulture); });
services.Configure<MyOption>(Configuration.GetSection("MySetting"));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints(options => { options.MapControllers(); });
}
}
C# HomeController
[Route("Home")]
public class HomeController : ControllerBase
{
public IOptions<MyOption> MyOptions { get; }
public IOptionsSnapshot<MyOption> MyOptionsSnapshot { get; }
public IOptionsMonitor<MyOption> MyOptionsMonitor { get; }
public IServiceProvider ServiceProvider { get; }
public IWebHostEnvironment WebHostEnvironment { get; }
public HomeController(
IOptions<MyOption> myOptions,
IOptionsSnapshot<MyOption> myOptionsSnapshot,
IOptionsMonitor<MyOption> myOptionsMonitor,
IServiceProvider serviceProvider,
IWebHostEnvironment webHostEnvironment
)
{
MyOptions = myOptions;
MyOptionsSnapshot = myOptionsSnapshot;
MyOptionsMonitor = myOptionsMonitor;
ServiceProvider = serviceProvider;
WebHostEnvironment = webHostEnvironment;
}
[HttpGet]
public string GetAsync()
{
Console.WriteLine($"MyOption A:{MyOptions.Value.A}");
Console.WriteLine($"MyOption B:{MyOptions.Value.B}");
Console.WriteLine($"MyOptionsSnapshot A:{MyOptionsSnapshot.Value.A}");
Console.WriteLine($"MyOptionsSnapshot B:{MyOptionsSnapshot.Value.B}");
Console.WriteLine($"MyOptionsMonitor A:{MyOptionsMonitor.CurrentValue.A}");
Console.WriteLine($"MyOptionsMonitor B:{MyOptionsMonitor.CurrentValue.B}");
// 更改appsetting配置文件,处罚文件更改通知 ( json文件配置需要开启更改重载 )
System.IO.File.WriteAllText(WebHostEnvironment.ContentRootPath + "/appsettings.json",
JsonSerializer.Serialize(new{
MySetting = new
{
B = "B 2"
}
})
);
var timer = new Timer(2000);
timer.Start();
timer.Elapsed += (sender, e) =>
{
Console.WriteLine($"MyOption2 A:{MyOptions.Value.A}");
Console.WriteLine($"MyOption2 B:{MyOptions.Value.B}");
Console.WriteLine($"MyOptionsSnapshot2 A:{MyOptionsSnapshot.Value.A}");
Console.WriteLine($"MyOptionsSnapshot2 B:{MyOptionsSnapshot.Value.B}");
Console.WriteLine($"MyOptionsMonitor2 A:{MyOptionsMonitor.CurrentValue.A}");
Console.WriteLine($"MyOptionsMonitor2 B:{MyOptionsMonitor.CurrentValue.B}");
timer.Stop();
};
return "Hello";
}
}
控制台打印信息如下:
第一次请求:
MyOption A:10/23/2020 11:48:37 PM
MyOption B:B 1
MyOptionsSnapshot A:10/23/2020 11:48:37 PM
MyOptionsSnapshot B:B 1
MyOptionsMonitor A:10/23/2020 11:48:37 PM
MyOptionsMonitor B:B 1
2秒后
MyOption2 A:10/23/2020 11:48:37 PM // 没变,因为IOptions只会计算一次
MyOption2 B:B 1 // 没变,因为IOptions只会计算一次
MyOptionsSnapshot2 A:10/23/2020 11:48:37 PM // 没变,因为IOptionsSnapshot每次请求才会计算一次
MyOptionsSnapshot2 B:B 1 // 没变,因为IOptionsSnapshot每次请求才会计算一次
MyOptionsMonitor2 A:10/23/2020 11:48:37 PM // 没变,因为IOptionsMonitor没有接受到 A键 的相关更改通知
MyOptionsMonitor2 B:B 2 // 变了,因为IOptionsMonitor接受到了文件配置更改通知
第二次请求:
MyOption A:10/23/2020 11:48:37 PM // 没变,因为IOptions只会计算一次
MyOption B:B 1 // 没变,因为IOptions只会计算一次
MyOptionsSnapshot A:10/23/2020 11:49:15 PM // 变了,因为IOptionsSnapshot每次请求都会计算一次
MyOptionsSnapshot B:B 2 // 变了,因为IOptionsSnapshot每次请求都会计算一次
OptionBuilder
特点:
- 简化了创建命名选项的过程,因为它只是初始
AddOptions<TOptions>(string optionsName)
调用的单个参数,而不会出现在所有后续调用中 - 能在配置选项过程中使用依赖
- 能够验证选项
不使用OptionsBuilder
var serviceCollection = new ServiceCollection();
serviceCollection.PostConfigure<MyOption>("My",n =>
{
n.A = n.A + "_Stuff";
n.B = "B";
});
serviceCollection.Configure<MyOption>("My", n => { n.A = "A"; });
使用OptionsBuilder
通过AddOptions<TOptions>(string optionsName)
获取OptionsBuilder
var serviceCollection = new ServiceCollection();
serviceCollection.AddOptions<MyOption>("My")
.PostConfigure(n =>
{
n.A = n.A + "_Stuff";
n.B = "B";
})
.Configure(n => { n.A = "A"; });
选项依赖
OptionBuilder
的Configure
最多支持5个泛型参数,他们表示选项的依赖
var serviceCollection = new ServiceCollection();
serviceCollection.AddOptions<MyOption>("My")
.Configure<IConfiguration>((option, configuration) =>
{
option.A = "A";
option.B = configuration["B"];
});
选项验证
OptionBuilder
的Validate
可以对选项进行验证,将在此选项获取时进行验证,如果验证失败将抛出异常 Validate
的第二个参数可以自定义验证的错误信息
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(_ => configuration);
serviceCollection.AddOptions<MyOption>()
.Configure<IConfiguration>((option, configuration) =>
{
option.A = "A2";
option.B = configuration["B"];
})
.Validate(options => options.B != null,"B不能为空"); // 第二个参数用于自定义验证错误信息
.Validate(options => options.A != null,"A不能为空"); // 第二个参数用于自定义验证错误信息
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>(); // 如果验证失败,将抛出异常
使用特性进行选项验证
Nuget包:Microsoft.Extensions.Options.DataAnnotations
实例:
appsettings.json
{
"MySetting": {
"A": "A Value",
"B": "B Value"
}
}
C#
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var serviceCollection = new ServiceCollection();
serviceCollection.Configure<MyOption2>(configuration.GetSection("MySetting"));
serviceCollection.AddOptions<MyOption2>()
.ValidateDataAnnotations(); // 使用特性进行选项验证
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption2>>();
var myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A);
Console.WriteLine(myOptionValue.B);
ConfiguOptions
ConfiguOptions
用于在依赖容器中注册IConfigureOptions<in TOptions>
、IPostConfigureOptions<in TOptions>
和IValidateOptions<TOptions>
的实现(如果传入的类型实现了他们)
Configure
、PostConfigure
等对选项进行配置,本质就是在依赖容器中注册IConfigureOptions<in TOptions>
的实现 OptionBuilder
的Validate
函数本质也是注册IValidateOptions<TOptions>
的实现
所以我们也可以手动注册IConfigureOptions<in TOptions>
,这样的一个好处是可以注入依赖
注意:serviceCollection.AddOptions()
函数用于注册IOptions
、IOptionsSnapshot
、IOptionsMonitor
、IOptionsFactory
、IOptionsMonitorCache
的实现 使用serviceCollection.Configure
等函数会在内部调用serviceCollection.AddOptions()
,所以不需要在调用它 这里我们手动注入IConfigureOptions<in TOptions>
的实现,所以手动调用它
实例1:
C# 1
public class MyConfigureOption : IConfigureOptions<MyOption>
{
public IConfiguration Configuration { get; }
public MyConfigureOption(IConfiguration configuration)
{
Configuration = configuration;
}
public void Configure(MyOption options)
{
options.A = "A Value";
options.B = Configuration["B"];
}
}
C# 2
/// appsettings.json:{"B": "B Value"}
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(_ => configuration);
serviceCollection.AddOptions();
serviceCollection.AddTransient<IConfigureOptions<MyOption>, MyConfigureOption>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
Console.WriteLine(myOption.Value.A); // A Value
Console.WriteLine(myOption.Value.B); // B Value
实例2:
可以直接继承自ConfigureNamedOptions<MyOption>
来实现命名配置
C# 1
public class MyConfigureOption : ConfigureNamedOptions<MyOption>
{
public MyConfigureOption(IConfiguration configuration)
: base("My", options =>
{
options.A = "A Value";
options.B = configuration["B"];
})
{
}
}
C# 2
/// appsettings.json:{"B": "B Value"}
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(_ => configuration);
serviceCollection.AddOptions();
serviceCollection.AddTransient<IConfigureOptions<MyOption>, MyConfigureOption>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptionsSnapshot<MyOption>>();
var myOptionValue = myOption.Get("My");
Console.WriteLine(myOptionValue.A); // A Value
Console.WriteLine(myOptionValue.B); // B Value
实例3:
可以实现IValidateOptions<TOptions>
来对选项进行验证
public class MyConfigureOption : ConfigureNamedOptions<MyOption>, IValidateOptions<MyOption>
{
public MyConfigureOption(IConfiguration configuration)
: base("My", options =>
{
options.A = "A Value";
options.B = configuration["B"];
})
{
}
public ValidateOptionsResult Validate(string name, MyOption options)
{
if (name != "My")
{
return ValidateOptionsResult.Skip;
}
if (options.B == null)
{
return ValidateOptionsResult.Fail("B 不能为空");
}
else
{
return ValidateOptionsResult.Success;
}
}
}
C# 2
/// appsettings.json:{"B": "B Value"}
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(_ => configuration);
serviceCollection.AddOptions();
serviceCollection.AddTransient<IConfigureOptions<MyOption>, MyConfigureOption>();
serviceCollection.AddTransient<IValidateOptions<MyOption>, MyConfigureOption>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var myOption = serviceProvider.GetRequiredService<IOptionsSnapshot<MyOption>>();
var myOptionValue = myOption.Get("My");
Console.WriteLine(myOptionValue.A);
Console.WriteLine(myOptionValue.B);
使用ConfiguOptions
代替手动注册IConfigureOptions<in TOptions>
、IPostConfigureOptions<in TOptions>
和IValidateOptions<TOptions>
在上面实例中需要手动指定要注册的服务类,在实例3中需要注册2次,使用ConfiguOptions
能帮你自动注册他们
实例3中的以下代码
serviceCollection.AddTransient<IConfigureOptions<MyOption>, MyConfigureOption>();
serviceCollection.AddTransient<IValidateOptions<MyOption>, MyConfigureOption>();
可以替换一下代码
serviceCollection.ConfigureOptions<MyConfigureOption>();
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)