.NET 高级开发 | 配置系统

张开发
2026/4/5 2:44:52 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)选项(Options)实现自定义配置提供器实现配置中心读取配置配置拦截配置优先级配置(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 newDictionarystring,string() { [test]配置 }; var config newConfigurationBuilder() .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 interfaceIConfigurationProvider { boolTryGet(string key,outstring?value); voidSet(string key,string?value); IChangeTokenGetReloadToken(); voidLoad(); IEnumerablestringGetChildKeys(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];image-20230807190141946查找Logging:LogLevel:Default时并不需要先定位Logging再往下查找LogLevel而是直接使用字符串Logging:LogLevel:Default作为 Key 从配置字典中查询对应的 Value。Logging:LogLevel:Default Information Logging:LogLevel:Microsoft Warning通过 json 配置文件我们可以很方便地构建层级结构的配置如果想在字典中存储可以使用{k1}:{k2}这种形式存。例如var dic newDictionarystring,string() { [testParent:Child1]6, [testParent:Child2]666 }; var config newConfigurationBuilder() .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 newConfigurationBuilder() .AddJsonFile(appsettings.json) .Build(); IConfigurationSection section config.GetSection(Logging:LogLevel); string test section[Default];但是这样读取起来很不方便我们可以使用 Microsoft.Extensions.Configuration.Binder 类库里面有大量的扩展方法可以帮助我们将配置字符串转换为强类型。// json: { test:{ Index:1 } } // C#: publicclassTest { publicintIndex{get;set;} } var config newConfigurationBuilder() .AddJsonFile(test.json) .Build(); var section config.GetSection(test); var o section.GetTest();即使源数据没有层次结构我们可以也可以使用Get()方法将配置映射为对象。public classTestOptions { publicstring A {get;set;} publicstring B {get;set;} publicstring C {get;set;} }var dic newDictionarystring,string() { [A]6, [B]66, [C]666 }; TestOptions config newConfigurationBuilder() .AddInMemoryCollection(dic) .Build().GetTestOptions();配置拦截有时从数据源中导入的配置是第三方扩展提供的有些配置无法直接修改因为所有的配置都会以键值对的形式存储在内存中那么我们可以尝试通过增加配置键值对来解决这个问题。比如 Serilog 的配置Serilog 可以在配置文件中设置打印文件日志我们需要在程序运行时确定日志存放到哪里。public staticvoidDynamicLog(thisIServiceCollection 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; }配置和选项的最重要差别是配置是用于整个程序以键值对的形式存储信息而选项是给特定模块提供参数使用强类型。示例项目在 Demo3.Options我们创建一个控制台项目引入以下包ItemGroup PackageReferenceIncludeMicrosoft.Extensions.ConfigurationVersion7.0.0/ PackageReferenceIncludeMicrosoft.Extensions.Configuration.JsonVersion7.0.0/ PackageReferenceIncludeMicrosoft.Extensions.DependencyInjectionVersion7.0.0/ PackageReferenceIncludeMicrosoft.Extensions.Options.ConfigurationExtensionsVersion7.0.0/ /ItemGroup添加一个 test.json 文件内容如下{ Title: 测试, Name: 测试测试 }再创建一个与之对应的模型类public classTestOptions { publicstringTitle{get;set;} publicstringName{get;set;} }选项接口主要有三个接口分别是•IOptionsTOptions•IOptionsSnapshotTOptions•IOptionsMonitorTOptions.NET 8 有了新的变化。使用示例如下static voidMain(string[] args) { var services newServiceCollection(); var configuration newConfigurationBuilder() .AddJsonFile(test.json, optional:true, reloadOnChange:true) .Build(); services.AddSingletonIConfiguration(configuration); services.AddOptionsTestOptions().Bind(configuration); // services.ConfigureTestOptions(name: , configuration); // 或者使用 Microsoft.Extensions.Options.ConfigurationExtensions 包 // services.ConfigureTestOptions(configuration); var ioc services.BuildServiceProvider(); var to1 ioc.GetRequiredServiceIOptionsTestOptions(); var to2 ioc.GetRequiredServiceIOptionsSnapshotTestOptions(); var to3 ioc.GetRequiredServiceIOptionsMonitorTestOptions(); to3.OnChange(s { Console.WriteLine($变更之前的值: {s.Name}); }); while(true) { Console.WriteLine($IOptions: {to1.Value.Name}); Console.WriteLine($IOptionsSnapshot: {to2.Value.Name}); Console.WriteLine($IOptionsMonitor: {to3.CurrentValue.Name}); Thread.Sleep(1000); } }我们可以手动修改 test.json 文件观察控制台打印。这三种方式都可以获取到选项它们之间的区别在于生命周期和文件监控等。ASP.NET Core 源代码services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions), typeof(UnnamedOptionsManager))); services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot), typeof(OptionsManager))); services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor), typeof(OptionsMonitor))); services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory), typeof(OptionsFactory))); services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache), typeof(OptionsCache)));IOptionsTOptions有以下特征注册为单一实例且可以注入到任何服务生存期。不支持在应用启动后读取配置数据。也就是说即使配置源可以动态更新但是IOptionsTOptions不会动态从 IConfiguration 中 最新的配置。也就是说在应用启动前就已经读取配置文件生成对象(单一实例)。当然后续如果修改了配置文件(.json)也不会影响这个对象的。文档解释通过使用IOptionsSnapshotTOptions针对请求生存期访问和缓存选项时每个请求都会计算一次选项。IOptionsSnapshot 的生命作用域是 scoped 在一个请求周期内有效而 IOptionsMonitor 是单例模式但是却可以监听配置的变更。由于 IOptionsSnapshot 每次请求都会进行更新因此配置文件变更后可以及时获得更新。IOptionsMonitor 则略有不同。IOptionsSnapshot 和 IOptionsMonitor 都可以检测到配置文件的更改但是 IOptionsSnapshot 每次请求都是一个新的对象在同一个请求上下文中同一个对象。而 IOptionsMonitor 是单例模式主要用于结合 IConfiguration 使用。但是要注意要使用IOptionsMonitorT很多写法是无效的。比如services.ConfigureTestOptions(o { o.Name new Random().Next(0, 100).ToString(); }); services.ConfigureTestOptions(o { configuration.Bind(o); });实现自定义配置提供器在本节中笔者将会介绍如何编写一个从文件导入的配置提供器并且实现随文件变化动态更新到内存中。代码示例在Demo4.Console中。引入Microsoft.Extensions.FileProviders.Physical用于监听目录、文件变化。我们将来实现读取自定义配置格式的文件创建一个 env.conf 文件内容如下A:111 B:222配置文件通过:号来分隔 key 和 value我们要实现自定义配置提供器需要先编写一个配置源需要继承 IConfigurationSource 接口其接口定义非常简单public interface IConfigurationSource { IConfigurationProvider Build(IConfigurationBuilder builder); }创建一个 MyConfigurationSource 类型代码如下public classMyConfigurationSource:IConfigurationSource { // 配置文件的路径 publicstringPath{get;set;} // 是否实时监听此文件变化 publicboolReloadOnChange{get;set;} public IConfigurationProvider Build(IConfigurationBuilder builder) { returnnewMyConfigurationProvider(this); } }接着定义配置提供器配置提供器需要存储配置信息提供配置查询等接口。代码示例如下public classMyConfigurationProvider:IConfigurationProvider { privatereadonlyMyConfigurationSource _source; privatereadonlyIFileProvider _fileProvider; privatereadonlystring _path; privatereadonlystring _fileName; // 缓存 privatereadonlyDictionarystring,string _cache; publicMyConfigurationProvider(MyConfigurationSource source) { _source source; _cache newDictionarystring,string(); _path Directory.GetParent(_source.Path)!.FullName; _fileName Path.GetFileName(_source.Path); _fileProvider newPhysicalFileProvider(_path); if(_source.ReloadOnChange) { // 监听配置文件变化 ChangeToken.OnChange(() _fileProvider.Watch(_fileName),async()awaitReloadFileAsync()); } else { ReloadFileAsync().Wait(); } } // 重新加载配置文件到内存中 privateasyncTaskReloadFileAsync() { usingvar stream _fileProvider.GetFileInfo(_fileName).CreateReadStream(); usingvar streamReader newStreamReader(stream); _cache.Clear(); while(true) { var line await streamReader.ReadLineAsync(); if(line null)break; var kv line.Split(:)[0..2].Select(x x.Trim( )).ToArray(); _cache.Add(kv[0], kv[1]); } } publicIEnumerablestringGetChildKeys(IEnumerablestring earlierKeys,string? parentPath) _cache.Keys; publicIChangeTokenGetReloadToken()null; publicvoidLoad() { ReloadFileAsync().Wait(); } publicvoidSet(string key,string?value) { _cache[key]value!; } publicboolTryGet(string key,outstring?value) { return _cache.TryGetValue(key,outvalue); } }接着定义扩展以便支持外部依赖注入public staticclassExtensions { publicstaticIConfigurationBuilderAddEnvFile(thisIConfigurationBuilder builder,string path,bool reloadOnChange false) { var source newMyConfigurationSource() { Path path, ReloadOnChange reloadOnChange }; builder.Add(source); return builder; } }然后使用我们自定义的配置提供器static void Main() { var configuration newConfigurationBuilder() .AddEnvFile(env.conf,true) .Build(); while(true) { varvalue configuration[A]; Console.WriteLine($A {value}); Thread.Sleep(1000); } }启动程序后修改程序运行目录下的 env.conf 文件查看控制台中的输出检查控制台的输出是否与修改后的文件一致。实现配置中心在了解配置、选项的使用方法以及如何自定义配置提供器之后在本小节我们将会创建一个配置中心服务然后客户端通过 SignalR 与配置中心通讯当配置中心的配置内容被修改后自动推送到客户端中。新建一个名为 Demo3.ConfigCenter 的 API 项目和一个名为 Demo3.ConfigClient 的控制台项目。首先是实现配置中心的 Demo3.ConfigCenter 的代码创建一个保存客户端信息的模型类。/// summary /// 客户端的信息 /// /summary publicclassClientInfo { /// summary /// SignalR 连接的 id /// /summary publicstringConnectionId{get;set;} /// summary /// 应用名称 /// /summary publicstringAppName{get;set;} /// summary /// 命名空间 /// /summary publicstringNamespace{get;set;} /// summary /// 分组名称 /// /summary publicstringGroupName${AppName}-{Namespace}; /// summary /// 客户端的 IP 地址 /// /summary publicstringIpAddress{get;set;} }创建一个用于 SignalR 通讯的 Hub 服务与客户端进行实时通信。public partialclassConfigCenterHub:Hub { // 客户端连接信息 privatestaticreadonlyConcurrentDictionarystring,ClientInfo _clients new(); // 在内存中保存每个服务的配置 privatestaticreadonlyConcurrentDictionarystring,JsonObject _settings new(); privatereadonlyIHubContextConfigCenterHub _hubContext; publicConfigCenterHub(IHubContextConfigCenterHub hubContext) { _hubContext hubContext; } // 当客户端连接到服务时 publicoverrideasyncTaskOnConnectedAsync() { ClientInfo clientnInfo GetInfo(); await _hubContext.Groups.AddToGroupAsync(clientnInfo.ConnectionId, clientnInfo.GroupName); _clients[clientnInfo.GroupName] clientnInfo; } // 当客户端断开服务时 publicoverrideasyncTaskOnDisconnectedAsync(Exception? exception) { ClientInfo clientnInfo GetInfo(); await _hubContext.Groups.RemoveFromGroupAsync(clientnInfo.ConnectionId, clientnInfo.GroupName); _clients.TryRemove(clientnInfo.ConnectionId,out _); } // 获取客户端的信息 privateClientInfoGetInfo() { var feature Context.Features.GetIHttpConnectionFeature(); var httpContext Context.GetHttpContext(); ArgumentNullException.ThrowIfNull(feature); ArgumentNullException.ThrowIfNull(httpContext); // 从 header 中查询信息 var appName httpContext.Request.Headers[AppName].FirstOrDefault(); var namespaceName httpContext.Request.Headers[Namespace].FirstOrDefault(); ArgumentNullException.ThrowIfNull(appName); ArgumentNullException.ThrowIfNull(namespaceName); var groupName ${appName}-{namespaceName}; // 获取客户端通讯地址 var remoteAddress feature.RemoteIpAddress; ArgumentNullException.ThrowIfNull(remoteAddress); var remotePort feature.RemotePort; returnnewClientInfo { ConnectionId feature.ConnectionId, AppName appName, Namespace namespaceName, IpAddress${remoteAddress.MapToIPv4().ToString()}:{remotePort} }; } // 客户端自行获取配置 publicasyncTaskJsonObjectGetAsync() { ClientInfo clientnInfo GetInfo(); if(_settings.TryGetValue(clientnInfo.GroupName,outvar v)) { return v; } var dic newDictionarystring,JsonNode().ToList(); returnnewJsonObject(dic); } // 更新缓存 publicvoidUpdateCache(string appName,string namespaceName,JsonObject json) { var groupName ${appName}-{namespaceName}; _settings[groupName] json; } }然后注册 Hub 服务... ... builder.Services.AddSwaggerGen(); // 注入 SignalR builder.Services.AddSignalR(); builder.Services.AddScopedConfigCenterHub(); var app builder.Build(); ...... app.MapControllers(); // 加入 Hub 中间件 app.MapHubConfigCenterHub(/config); app.Run(http://*:5000);创建一个 ConfigController 控制器允许通过 API 修改配置中心的内容以及在修改配置后推送到对应的客户端中。[ApiController] [Route([controller])] publicclassConfigController:ControllerBase { privatereadonlyConfigCenterHub _configCenter; privatereadonlyIHubContextConfigCenterHub _hubContext; publicConfigController(IHubContextConfigCenterHub hubContext,ConfigCenterHub configCenter) { _hubContext hubContext; _configCenter configCenter; } [HttpPost(update)] publicasyncTaskstringUpdate(string appName,string namespaceName,[FromBody]JsonObject json) { var groupName ${appName}-{namespaceName}; _configCenter.UpdateCache(appName, namespaceName, json); await _hubContext.Clients.Group(groupName).SendAsync(Publish, json); return已更新配置; } }接下来就是客户端部分。在程序启动时会读取目录的 tmp_config.json 文件注入到配置中如果文件不存在则创建。然后使用框架自带的 JsonConfigurationProvider 为我们动态监听 json 文件的变化减少我们的代码量。在实时监听 Json 文件变化以及解析 json 这部分可以利用官方的 JsonConfigurationSource 来实现我们就不需要重新写一个了。然后使用 SignalR 与配置中心通讯将配置中心的内容写入到临时文件 tmp_config.json 中JsonConfigurationProvider 会自动将修改后的 json 文件加载到内存中。因为我们使用了本地配置文件获取到的配置先缓存在本地中所以当下次程序启动时或网络出现故障时程序依然可以通过本地缓存配置启动起来。这个也是很多配置中心都有实现的。在 Demo3.ConfigClient 中创建 OnlineConfigurationSource、OnlineConfigurationProvider 文件写入以下代码public classOnlineConfigurationSource:IConfigurationSource { /// summary /// 获取最新配置的 API 路径 /// /summary publicstring URL {get;init;} publicstringAppName{get;init;} publicstringNamespace{get;init;} publicIConfigurationProviderBuild(IConfigurationBuilder builder) { returnnewOnlineConfigurationProvider(this, builder); } }然后实现一个配置提供器与 Hub 服务器实时通信并更新到 tmp_config.json 文件中。public classOnlineConfigurationProvider:IConfigurationProvider,IDisposable { privateconststringTmpFiletmp_config.json; privatereadonlystring _jsonPath; privatereadonlyOnlineConfigurationSource _configurationSource; privatereadonlyJsonConfigurationSource _jsonSource; privatereadonlyIConfigurationProvider _provider; privatereadonlyHubConnection _connection; publicOnlineConfigurationProvider(OnlineConfigurationSource configurationSource,IConfigurationBuilder builder) { // 使用框架自带的 JsonConfigurationSource 动态获取 json 文件的内容 var curPath Directory.GetParent(typeof(OnlineConfigurationProvider).Assembly.Location).FullName; _jsonPath Path.Combine(curPath,TmpFile); if(!File.Exists(TmpFile))File.WriteAllText(_jsonPath,{}); _configurationSource configurationSource; _jsonSource newJsonConfigurationSource() { PathTmpFile, ReloadOnChangetrue, }; _provider _jsonSource.Build(builder); // 配置 SignalR 通讯将新的内容写入到 json 文件 _connection newHubConnectionBuilder() .WithUrl(_configurationSource.URL, options { options.Headers.Add(AppName, _configurationSource.AppName); options.Headers.Add(Namespace, _configurationSource.Namespace); }) .WithAutomaticReconnect() .Build(); _connection.OnJsonObject(Publish,async(json) { awaitSaveJsonAsync(json); }); _connection.StartAsync().Wait(); var json _connection.InvokeAsyncJsonObject(GetAsync).Result; SaveJsonAsync(json).Wait(); } privateasyncTaskSaveJsonAsync(JsonObject json) { // 每次清空文件重新写入内容 usingFileStream fs newFileStream(_jsonPath,FileMode.Truncate,FileAccess.ReadWrite); awaitSystem.Text.Json.JsonSerializer.SerializeAsync(fs, json); Console.WriteLine($已更新配置{System.Text.Json.JsonSerializer.Serialize(json)}); } privatebool _disposedValue; ~OnlineConfigurationProvider()Dispose(false); publicvoidDispose() { Dispose(true); GC.SuppressFinalize(this); } protectedvirtualvoidDispose(bool disposing) { if(!_disposedValue) { if(disposing) { _connection.DisposeAsync(); } _disposedValue true; } } publicIEnumerablestringGetChildKeys(IEnumerablestring earlierKeys,string? parentPath) _provider.GetChildKeys(earlierKeys, parentPath); publicIChangeTokenGetReloadToken() _provider.GetReloadToken(); publicvoidLoad() _provider.Load(); publicvoidSet(string key,string?value) _provider.Set(key,value); publicboolTryGet(string key,outstring?value) _provider.TryGet(key,outvalue); }编写扩展方法配置 Hub 服务。public staticclassExtensions { // 添加远程配置 publicstaticIConfigurationBuilderAddReomteConfig(thisIConfigurationBuilder builder,string url,string appName,stringnamespace) { var source newOnlineConfigurationSource() { URL url, AppName appName, Namespacenamespace }; builder.Add(source); return builder; } }使用配置中心服务internal classProgram { staticvoidMain(string[] args) { Thread.Sleep(5000); var builder newConfigurationBuilder() .AddReomteConfig(http://127.0.0.1:5000/config,myapp,dev); var config builder.Build(); while(true) { Console.WriteLine(config[Name]); Thread.Sleep(1000); } } }同时启动配置中心和客户端打开配置中心的 Swagger 地址修改并推送新的配置到客户端。image-20230917194903641

更多文章