选项是配置一个升级版,一般情况下是把一个范围内的配置包装成类型,以供使用,比如下面的RedisSetting,是Redis的配置参数:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"RedisSetting": {
"Host": "127.0.0.1",
"Port": 6379,
"Password": "123",
"ConnectionTimeOut": "10ms"
}
}
可以采用下面的形式把配置类型 实体注入到容器(因为没有说注入这个知识点,这里可以理解为初始化)中。
var builder = WebApplication.CreateBuilder();
builder.Services.Configure<RedisSetting>(builder.Configuration.GetSection("RedisSetting"));
var app = builder.Build();
选项分为三种:普通选项目IOptions,热更新选项IOptionsSnapshot,监控选项IOptionsMonitor,下面分别说明。
IOptions选项
app.MapGet("/appinfo", (IOptions<RedisSetting> options) =>
{
return options.Value;
});
Demo结果
热加载选项:IOptionsSnapshot
app.MapGet("/snapshot", (IOptionsSnapshot<RedisSetting> options) =>
{
return options.Value;
});
结果
更新配置ConnectionTimeOut为15ms,在不重启服务的情况下,再次请求,结果会变成15ms
监控选项:IOptionsMonitor
app.MapGet("/monitorstart", (IOptionsMonitor<RedisSetting> options) =>
{
options.OnChange(redisSetting =>
{
app.Logger.LogInformation(options.CurrentValue.ToString());
});
return "Ok";
});
调用监控
当把ConnectionTimeOut更新成20ms时,OnChange会被触发,日志会打印出来
利用监控特性可以与报警联合起来,当监控到配置有变化时,通知服务相关人知晓,是人为设置,还是被篡改,以便采取措施。
选项命名:
当相同的配置有两组时,选项命名就非常有用了,比如一主一备
"RedisSettings": {
"Main": {
"Host": "127.0.0.1",
"Port": 6379,
"Password": "123",
"ConnectionTimeOut": "10ms"
},
"Prepare": {
"Host": "127.0.0.1",
"Port": 6380,
"Password": "456",
"ConnectionTimeOut": "12ms"
}
}
实体类可以用静态常量区分开来
public record RedisSetting
{
public const string Main = "Main";
public const string Prepare = "Prepare";
public string? Host { get; set; }
public int Port { get; set; }
public string? Password { get; set; }
public string? ConnectionTimeOut { get; set; }
}
分别注入配置即可
builder.Services.Configure<RedisSetting>(RedisSetting.Main,
builder.Configuration.GetSection("RedisSettings:Main"));
builder.Services.Configure<RedisSetting>(RedisSetting.Prepare,
builder.Configuration.GetSection("RedisSettings:Prepare"));
不过只有IOptionsSnapshot和IOptionsMonitor能通过Get方法来获取命名的配置,IOptions没有实现Get方法
app.MapGet("/snapshotredissetting", (IOptionsSnapshot<RedisSetting> options) =>
{
return options.Get(RedisSetting.Main);
});
app.MapGet("/monitorstart", (IOptionsMonitor<RedisSetting> options) =>
{
options.OnChange(redisSetting =>
{
app.Logger.LogInformation(options.Get(RedisSetting.Main).ToString());
app.Logger.LogInformation(options.Get(RedisSetting.Prepare).ToString());
});
return options.CurrentValue;
});
另外,为了提高配置数据的安全性,可以给配置选项增加验证,可以通过在配置实体类上增加DataAnnotations来验证,也可以自定义验证
public record RedisSetting
{
public string? Host { get; set; }
public int Port { get; set; }
public string? Password { get; set; }
[RegularExpression(@"^\d+ms$", ErrorMessage = "格式不正确,必须是ms")]
public string? ConnectionTimeOut { get; set; }
}
上面的ConnectionTimeOut是DataAnnotations方式,下面是自定义验证模式。
builder.Services.AddOptions<RedisSetting>()
.Bind(builder.Configuration.GetSection("RedisSetting"))
.ValidateDataAnnotations()
.Validate(config =>
{
if (config.Port < 1000)
{
return false;
}
return true;
}, "端口不能少于1000");
如果验证更复杂,可以自定义类实现,如下:
public class RedisSettingValidation : IValidateOptions<RedisSetting>
{
public RedisSetting _config { get; init; }
public RedisSettingValidation(IConfiguration config)
{
_config = config.GetSection("RedisSetting")
.Get<RedisSetting>();
}
public ValidateOptionsResult Validate(string name, RedisSetting options)
{
string? vor=null;
var rx = new Regex(@"^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$");
if (options != null&&options.Host!=null)
{
var match = rx.Match(options.Host);
if (string.IsNullOrEmpty(match.Value))
{
vor = $"{options.Host} 格式不正确";
}
if (vor != null)
{
return ValidateOptionsResult.Fail(vor);
}
}
return ValidateOptionsResult.Success;
}
}
添加验证类型
builder.Services.Configure<RedisSetting>(builder.Configuration.GetSection("RedisSetting"));
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions
<RedisSetting>, RedisSettingValidation>());
想要更快更方便的了解相关知识,可以关注微信公众号