Biwen.Settings添加对IConfiguration&IOptions的集成支持
Biwen.Settings 是一个简易的配置项管理模块,主要的作用就是可以校验并持久化配置项,比如将自己的配置存储到数据库中,JSON文件中等
使用上也是很简单,只需要在服务中注入配置,
比如我们有一个GithubSetting的配置项,我们只需要定义好对象然后注入到Service中即可:
[Description("Github配置")]
public class GithubSetting : ValidationSettingBase<GithubSetting>
{
[Description("Github用户名")]
public string? UserName { get; set; } = "vipwan";
[Description("Github仓库")]
public string? Repository { get; set; } = "Biwen.Settings";
[Description("Github Token")]
public string? Token { get; set; } = "";
public GithubSetting()
{
//验证规则
RuleFor(x => x.UserName).NotEmpty().Length(3, 128);
RuleFor(x => x.Repository).NotNull().NotEmpty().Length(3, 128);
RuleFor(x => x.Token).NotNull().NotEmpty().Length(3, 128);
}
}
@inject GithubSetting GithubSetting;//直接对象注入
尽管这样已经足够好用且便捷,但是对于习惯了使用IConfiguration
和IOptions
的朋友来说还是有些不习惯,其实实现对IConfiguration的支持还是很简单的,实现一下IConfigurationProvider
即可,我们来动手实现一个名为BiwenSettingConfigurationProvider
的Provider:
internal class Events
{
/// <summary>
/// Channel队列
/// </summary>
public static readonly Channel<(bool IsChanged, string? SettingName)> ConfigrationChangedChannel = Channel.CreateUnbounded<(bool IsChanged, string? SettingName)>();
}
internal sealed class BiwenSettingConfigurationSource(bool autoRefresh = true) : IConfigurationSource
{
public IConfigurationProvider Build(IConfigurationBuilder builder) => new BiwenSettingConfigurationProvider(autoRefresh);
}
internal class BiwenSettingConfigurationProvider : ConfigurationProvider, IDisposable, IAsyncDisposable
{
public BiwenSettingConfigurationProvider(bool autoRefresh)
{
if (Settings.ServiceRegistration.ServiceProvider is null)
{
throw new BiwenException("必须首先注册Biwen.Setting模块,请调用:services.AddBiwenSettings()");
}
if (autoRefresh)
{
StartAlertAsync(cts.Token);
}
}
private CancellationTokenSource cts = new();
/// <summary>
/// 使用Channel通知配置变更,如果有事件更新则重新加载
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task StartAlertAsync(CancellationToken cancellationToken)
{
_ = Task.Run(async () =>
{
while (!cancellationToken.IsCancellationRequested)
{
_ = await Events.ConfigrationChangedChannel.Reader.ReadAsync(cancellationToken);
Load();
//通知配置变更
OnReload();
}
}, cancellationToken);
return Task.CompletedTask;
}
//从SettingManager中加载配置项
public override void Load()
{
Dictionary<string, string?> dics = [];
using var scope = Settings.ServiceRegistration.ServiceProvider.CreateScope();
var settingManager = scope.ServiceProvider.GetRequiredService<ISettingManager>();
var settings = settingManager.GetAllSettings()!;
foreach (var setting in settings)
{
if (setting.SettingContent is null) continue;
if (JsonNode.Parse(setting.SettingContent) is not JsonObject json) continue;
foreach (var item in json)
{
dics.TryAdd($"{setting.SettingName}:{item.Key}", item.Value?.ToString());
}
}
Data = dics;
}
public void Dispose()
{
cts.Cancel();
Events.ConfigrationChangedChannel.Writer.Complete();
}
public ValueTask DisposeAsync()
{
cts.Cancel();
Events.ConfigrationChangedChannel.Writer.Complete();
return ValueTask.CompletedTask;
}
}
内部通过Channel实现变更通知,
internal class ConfigurationMediratorDoneHandler(ILogger<ConfigurationMediratorDoneHandler> logger) : IMediratorDoneHandler
{
public Task OnPublishedAsync<T>(T @event) where T : ISetting, new()
{ Events.ConfigrationChangedChannel.Writer.TryWrite((true, typeof(T).Name));
logger.LogInformation($"Setting Changed: {typeof(T).Name},并通知Configuration刷新!");
return Task.CompletedTask;
}
}
然后老规矩我们扩展一下IServiceCollection
:
public static class ServiceRegistration
{
internal static IServiceCollection AddBiwenSettingConfiguration(this IServiceCollection services)
{
//ConfigurationMediratorDoneHandler
services.AddSingleton<IMediratorDoneHandler, ConfigurationMediratorDoneHandler>();
return services;
}
/// <summary>
/// 提供对IConfiguration,IOptions的支持
/// </summary>
/// <param name="manager"></param>
/// <param name="autoRefresh"></param>
/// <returns></returns>
public static ConfigurationManager AddBiwenSettingConfiguration(
this ConfigurationManager manager, IServiceCollection serviceDescriptors, bool autoRefresh = true)
{
var sp = Settings.ServiceRegistration.ServiceProvider ?? throw new BiwenException("必须首先注册Biwen.Setting模块,请调用:services.AddBiwenSettings()");
//添加订阅
if (autoRefresh)
{
serviceDescriptors.AddBiwenSettingConfiguration();
}
IConfigurationBuilder configBuilder = manager;
configBuilder.Add(new BiwenSettingConfigurationSource(autoRefresh));
var settings = ASS.InAllRequiredAssemblies.ThatInherit(typeof(ISetting)).Where(x => x.IsClass && !x.IsAbstract).ToList();
//注册ISetting
settings.ForEach(x =>
{
//IOptions DI
manager?.GetSection(x.Name).Bind(GetSetting(x, sp));
});
return manager;
}
static object GetSetting(Type x, IServiceProvider sp)
{
var settingManager = sp.GetRequiredService<ISettingManager>();
var cache = sp.GetRequiredService<IMemoryCache>();
//使用缓存避免重复反射
var md = cache.GetOrCreate($"GenericMethod_{x.FullName}", entry =>
{
MethodInfo methodLoad = settingManager.GetType().GetMethod(nameof(settingManager.Get))!;
MethodInfo generic = methodLoad.MakeGenericMethod(x);
return generic;
});
return md!.Invoke(settingManager, null)!;
}
}
最后在启动时调用AddBiwenSettingConfiguration扩展即可
builder.Configuration.AddBiwenSettingConfiguration(builder.Services, true);
最后按下面的形式注册就可以了:
@inject GithubSetting GithubSetting;//直接对象注入
@inject IOptions<GithubSetting> IOP; //通过IOptions注入
@inject IConfiguration Configuration;//IConfiguration
...
源代码我发布到了GitHub,欢迎star! https://github.com/vipwan/Biwen.Settings
https://github.com/vipwan/Biwen.Settings/tree/master/Biwen.Settings/Extensions/Configuration