- A+
所属分类:.NET技术
实现自定义配置源至少需要添加如下成员:
- 实现IConfigurationSource接口的配置源;
- 实现IConfigurationProvider接口或虚基类ConfigurationProvider的配置提供程序;
- 添加配置源的IConfigurationBuilder扩展方法;
如自定义一个TXT文本文件配置源:
添加配置源
配置源负责创建配置提供程序,以及监听文件修改。监听文件修改可以使用FileSystemWatcher,通过监听Changed事件监听配置文件的修改。使用ConfigurationReloadToken作为IChangeToken,当监听到文件修改时调用取消令牌的取消操作,进而通知订阅者文件已更改。
public class TxtConfigurationSource : IConfigurationSource, IDisposable { private FileSystemWatcher? _fileWatcher; private ConfigurationReloadToken _reloadToken; public TxtConfigurationSource(string path, bool reloadOnChange = true) { FilePath = path; ReloadOnChange = reloadOnChange; _fileWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory()); _fileWatcher.Filter = "*.txt"; _fileWatcher.EnableRaisingEvents = true; _fileWatcher.Changed += _fileWatcher_Changed; _reloadToken = new ConfigurationReloadToken(); } private void _fileWatcher_Changed(object sender, FileSystemEventArgs e) { if (e.FullPath != FilePath) return; if (_reloadToken.HasChanged) return; // 触发事件 ConfigurationReloadToken previousToken = Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken()); previousToken.OnReload(); } public bool ReloadOnChange { get; set; } public string FilePath { get; set; } public IConfigurationProvider Build(IConfigurationBuilder builder) { return new TxtConfigurationProvider(this); } public IChangeToken GetChangeToken() => _reloadToken; public void Dispose() => Dispose(true); protected virtual void Dispose(bool disposing) { _fileWatcher?.Dispose(); } }
添加配置提供程序
配置提供程序负责加载配置文件,并订阅配置源中的配置修改事件。通过ChangeToken.OnChange()方法进行事件订阅,当监听到文件改变时,重新加载文件:
public class TxtConfigurationProvider : ConfigurationProvider, IDisposable { private readonly IDisposable _changeTokenRegistration; public TxtConfigurationProvider(TxtConfigurationSource source) :base() { Source = source ?? throw new ArgumentNullException(nameof(source)); if (Source.ReloadOnChange) { _changeTokenRegistration = ChangeToken.OnChange( () => Source.GetChangeToken(), () => { Thread.Sleep(300); Load(); }); } } public TxtConfigurationSource Source { get; } public override void Load() { if (!File.Exists(Source.FilePath)) return; else { Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var lines = File.ReadAllLines(Source.FilePath); foreach (var line in lines) { var array = line.Replace(":", ":").Split(':'); if (array.Length < 2) continue; Data.Add(line.Substring(0, line.LastIndexOf(':')), array.Last()); } } OnReload(); } public void Dispose() => Dispose(true); protected virtual void Dispose(bool disposing) { _changeTokenRegistration?.Dispose(); } }
添加配置源扩展方法
添加IConfigurationBuilder扩展方法,方便将自定义配置源添加到IConfigurationBuilder中:
public static IConfigurationBuilder AddTxtFile(this IConfigurationBuilder builder, string path, bool reloadOnChange) { if (builder == null) throw new ArgumentNullException(nameof(builder)); if (string.IsNullOrEmpty(path)) throw new ArgumentException($"文件不能为空:{nameof(path)}"); return builder.Add(new TxtConfigurationSource(path, reloadOnChange)); }
使用
首先通过扩展方法添加配置源,调用IConfigurationBuilder.Build()方法后即可通过IConfiguration获取配置项。通过ChangeToken.OnChange()方法订阅配置修改事件。
using ConfigurationTest; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Primitives; IConfigurationBuilder configurationBuilder = new ConfigurationBuilder() .AddTxtFile(Path.Combine(Directory.GetCurrentDirectory(), "config.txt"), reloadOnChange: true); // 通过IConfiguration直接读取指定配置 IConfiguration configuration = configurationBuilder.Build(); Console.WriteLine($"FileProvider:Source:{configuration.GetSection("FileProvider:Source").Value}"); Console.WriteLine($"Provider:{configuration["FileProvider:Provider"]}"); Console.WriteLine(); // 通过绑定选项获取配置 FileProviderOptions fileProviderOptions = new FileProviderOptions(); configuration.GetSection("FileProvider").Bind(fileProviderOptions); Console.WriteLine($"FileProvider.Source = {fileProviderOptions.Source}"); Console.WriteLine($"FileProvider.Provider = {fileProviderOptions.Provider}"); Console.WriteLine(); // 监听配置修改 var disable = ChangeToken.OnChange(() => configuration.GetReloadToken(), () => { foreach (var section in configuration.GetChildren()) { PrintAllConfig(section); } Console.WriteLine(); Console.WriteLine("按“q”退出"); }); Console.WriteLine("按“q”退出"); while (Console.ReadLine() != "q") { } disable.Dispose(); void PrintAllConfig(IConfigurationSection config) { var sections = config.GetChildren(); if(sections == null || sections.Count() == 0) Console.WriteLine($"{config.Key} : {config.Value}"); else { foreach (var section in sections) { PrintAllConfig(section); } } } class FileProviderOptions { public string Source { get; set; } public string Provider { get; set; } }
// config.txt FileProvider:Source:TxtSource FileProvider:Provider:TxtProvider