.NET 高级开发 | 配置系统原理、实现一个配置中心

张开发
2026/4/10 8:33:50 15 分钟阅读

分享文章

.NET 高级开发 | 配置系统原理、实现一个配置中心
配置和选项ASP.NET Core 模板项目下会有 appsettings.json、appsettings.Development.json 两个配置文件我们可以通过这两个文件配置 Web 应用的启动端口、是否使用 https 等大多数第三方框架也都支持在这两个 json 文件中配置。ASP.NET Core 程序默认支持从 json 文件、xml 文件、环境变量等多种配置源注入到内存中微服务应用一般会使用远程配置中心存储配置以便动态更新到程序中不管是什么类型的配置源只需要提供 IConfigurationBuilder 的扩展方法给开发者即可开发者不必关注配置源本身的实现细节。而 Microsoft.Extensions.Configuration.Abstractions 定义了统一的接口使用者只需要 注入 IConfiguration 服务即可动态获取配置。在实际生产环境中尤其在微服务场景下我们会有实时更新配置的需求要求服务实例使用集中式的配置中心以便修改配置后所有实例同时更新。通过配置中心管理多个服务的配置以及将实例的开发、测试等环境的配置隔离开来。在本章中笔者将会介绍这些 .NET 中的配置和选项在学会使用方法和原理之后还会介绍如何使用 SignalR 开发一个配置中心不管使用的是控制台还是例如 WPF 这类桌面程序都可以达到 ASP.NET Core 中使用配置的效果。在本小节中我们将会做两个实践项目第一个是实现从文件中读取配置并且在文件修改后能够实时更新配置到内存中第二个是实现一个配置中心能够将远程配置更新到本地。配置(Configuration)创建一个控制台程序然后引入 Microsoft.Extensions.Configuration 类库我们可以使用 ConfigurationBuilder 类构建配置提供器然后通过扩展包从各种数据源中导入配置。目前Microsoft 官方提供的导入配置源扩展方法有以下类型内存键值集合配置文件json、xml、yaml 文件等环境变量、命令行参数无论是哪种数据源导入到内存中时均以字符串键值对的形式出现。从 json 文件获取配置需要引入Microsoft.Extensions.Configuration.Json包。在项目根目录下创建一个 json 文件内容如下{ test:配置 }然后从 json 中导入配置。var config new ConfigurationBuilder() .AddJsonFile(test.json) .Build(); string test config[test]; Console.WriteLine(test);如果配置文件不在根目录下则可以使用SetBasePath()来定义路径示例如下var config new ConfigurationBuilder() .SetBasePath(E:\\test\\aaa) .AddJsonFile(test.json) .Build();另外json 扩展默认会监听文件的变化如果文件做出了修改那么就会重新读取配置到内存中。config.AddJsonFile(appsettings.json, optional: true, reloadOnChange: true);而从键值对计划中导入配置示例如下var dic new Dictionarystring, string() { [test] 配置 }; var config new ConfigurationBuilder() .AddInMemoryCollection(dic) .Build(); string test config[test];常用的导入配置的扩展方法有builder.Configuration .AddCommandLine(...) .AddEnvironmentVariables(...) .AddIniFile(...) .AddIniStream(...) .AddInMemoryCollection(...) .AddJsonFile(...) .AddJsonStream(...) .AddKeyPerFile(...) .AddUserSecrets(...) .AddXmlFile(...) .AddXmlStream(...);观察AddInMemoryCollection()的扩展方法可以看到本质是创建了一个 MemoryConfigurationSource 实例添加到 IConfigurationBuilder 中。public static IConfigurationBuilder AddInMemoryCollection(this IConfigurationBuilder configurationBuilder) { ThrowHelper.ThrowIfNull(configurationBuilder); configurationBuilder.Add(new MemoryConfigurationSource()); return configurationBuilder; }如果我们要自定义一个数据源需要实现 IConfigurationSource、IConfigurationProvider 两个接口IConfigurationSource 用于生成配置提供器IConfigurationProvider 用于通过 key 获取配置字符串。public interface IConfigurationSource { IConfigurationProvider Build(IConfigurationBuilder builder); }public interface IConfigurationProvider { bool TryGet(string key, out string? value); void Set(string key, string? value); IChangeToken GetReloadToken(); void Load(); IEnumerablestring GetChildKeys(IEnumerablestring earlierKeys, string? parentPath); }读取配置在 ASP.NET Core 项目中都会有个 appsettings.json 文件其默认内容如下{ Logging: { LogLevel: { Default: Information, Microsoft: Warning, Microsoft.Hosting.Lifetime: Information } } }因为配置在内存中是以键值对出现的我们可以使用:符号获取下一层子项的配置。var config new ConfigurationBuilder() .AddJsonFile(appsettings.json) .Build(); string test config[Logging:LogLevel:Default];查找Logging:LogLevel:Default时并不需要先定位Logging再往下查找LogLevel而是直接使用字符串Logging:LogLevel:Default作为 Key 从配置字典中查询对应的 Value。Logging:LogLevel:Default Information Logging:LogLevel:Microsoft Warning通过 json 配置文件我们可以很方便地构建层级结构的配置如果想在字典中存储可以使用{k1}:{k2}这种形式存。例如var dic new Dictionarystring, string() { [testParent:Child1] 6, [testParent:Child2] 666 }; var config new ConfigurationBuilder() .AddInMemoryCollection(dic) .Build().GetSection(testParent); string test config[Child1];如果你只想 获取 json 文件中LogLevel部分的配置可以使用GetSection()方法获取 IConfigurationSection 对象这样可以筛选出以当前字符串开头的所有配置那么我们下次使用时就不必提供完整的 Key 了。// json: { Logging: { LogLevel: { Default: Information, Microsoft.AspNetCore: Warning } }, AllowedHosts: * } // C# varconfig new ConfigurationBuilder() .AddJsonFile(appsettings.json) .Build(); IConfigurationSection section config.GetSection(Logging:LogLevel); string test section[Default];但是这样读取起来很不方便我们可以使用 Microsoft.Extensions.Configuration.Binder 类库里面有大量的扩展方法可以帮助我们将配置字符串转换为强类型。// json: { test: { Index: 1 } } // C#: public class Test { public int Index { get; set; } } var config new ConfigurationBuilder() .AddJsonFile(test.json) .Build(); var section config.GetSection(test); var o section.GetTest();即使源数据没有层次结构我们可以也可以使用Get()方法将配置映射为对象。public class TestOptions { public string A { get; set; } public string B { get; set; } public string C { get; set; } }var dic new Dictionarystring, string() { [A] 6, [B] 66, [C] 666 }; TestOptions config new ConfigurationBuilder() .AddInMemoryCollection(dic) .Build().GetTestOptions();配置拦截有时从数据源中导入的配置是第三方扩展提供的有些配置无法直接修改因为所有的配置都会以键值对的形式存储在内存中那么我们可以尝试通过增加配置键值对来解决这个问题。比如 Serilog 的配置Serilog 可以在配置文件中设置打印文件日志我们需要在程序运行时确定日志存放到哪里。public static void DynamicLog(this IServiceCollection services, string customPath) { var configuration services!.BuildServiceProvider().GetRequiredServiceIConfiguration(); // 查找节点 var fileName configuration.AsEnumerable() .Where(x x.Key.StartsWith(Serilog:WriteTo) x.Key.EndsWith(Name) x.Value!.Equals(File)).FirstOrDefault(); // Serilog:WriteTo:0:Name if (!string.IsNullOrEmpty(fileName.Value)) { var key fileName.Key.Replace(Name, Args:path); var path Path.Combine(customPath, log.txt); configuration[key] path; } }配置优先级在 ASP.NET Core 中开发时会使用到appsettings.json、appsettings.Development.json配置文件这两个配置文件都有自己的 IConfigurationSource、IConfigurationProvider。运行时appsettings.Development.json 中的配置会替换 appsettings.json 的配置。其实在于配置的注入顺序例如我们可以手动注入多个 json 配置文件var configuration new ConfigurationBuilder() .AddJsonFile(path: appsettings.json) .AddJsonFile(path: appsettings.Development.json)这个 Configuration 会存在两个配置源// appsettings.json JsonConfigurationSource // appsettings.Development.json JsonConfigurationSource当查找配置时会从 Providers 中倒序查找会首先从 appsettings.Development.json 中查找配置当查找完成后立即返回。因此当我们需要使用自定义配置提供器时可以在最后才加上我们的提供器这样我们自定义的提供器优先级最高。选项(Options)在 ASP.NET Core 中很多中间件的配置是通过选项传递的。比如设置表单上传文件最大为 30MB。// 表单配置 builder.Services.ConfigureFormOptions(options { // 上传的文件最大为 30mb options.MultipartBodyLengthLimit 31_457_280; });这样做的好处是我们使用配置时可以直接使用强类型而不需要关注如何从 IConfiguration 中取出配置。如果我们要获取 TestOptions是通过IOptionsTestOptions来获取的不能直接获取 TestOptions 服务。private readonly TestModel _options; public TestController(IOptionsFormOptions options) { _options options.Value; }

更多文章