.Net Core 自定义配置源从配置中心读取配置
配置,几乎所有的应用程序都离不开它。.Net Framework时代我们使用App.config、Web.config,到了.Net Core的时代我们使用appsettings.json,这些我们再熟悉不过了。然而到了容器化、微服务的时代,这些本地文件配置有的时候就不太合适了。当你把本地部署的服务搬到docker上后,你会发现要修改一个配置文件变的非常麻烦。你不得不通过宿主机进入容器内部来修改文件,也许容器内还不带vi等编辑工具,你连看都不能看,改都不能。更别说当你启动多个容器实例来做分布式应用的时候,一个个去修改容器的配置,这简直要命了。
因为这些原因,所以“配置中心”就诞生了。配置中心是微服务的基础设施,它对配置进行集中的管理并对外暴露接口,当应用程序需要的时候通过接口读取。配置通常为Key/Value模式,然后通过http接口暴露。好了,配置中心不多说了,感觉要偏了,这次是介绍怎么自定义一个配置源从配置中心读取配置。废话不多说直接上代码吧。
模拟配置中心
我们新建一个asp.net core webapi站点来模拟配置中心服务,端口配置到5000,并添加相应的controller来模拟配置中心对外的接口。
[Route("api/[controller]")]
[ApiController]
public class ConfigsController : ControllerBase
{
public List<KeyValuePair<string,string>> Get()
{
var configs = new List<KeyValuePair<string, string>>();
configs.Add(new KeyValuePair<string, string>("SecretKey","1238918290381923"));
configs.Add(new KeyValuePair<string, string>("ConnectionString", "user=123;password=123;server=."));
return configs;
}
}
添加一个configscontroller,并修改Get方法,返回2个配置键值对。

访问下/api/configs看下返回是否正确
自定义配置源
从现在开始我们真正开始来定义一个自定义的配置源然后当程序启动的时候从配置中心读取配置文件信息,并提供给后面的代码使用配置。
新建一个asp.net core mvc站点来模拟客户端程序。
MyConfigProvider
public class MyConfigProvider : ConfigurationProvider
{
/// <summary>
/// 尝试从远程配置中心读取配置信息
/// </summary>
public async override void Load()
{
var response = "";
try
{
var serverAddress = "http://localhost:5000";
var client = new HttpClient();
client.BaseAddress = new Uri(serverAddress);
response = await client.GetStringAsync("/api/configs");
}
catch (Exception ex)
{
//write err log
}
if (string.IsNullOrEmpty(response))
{
throw new Exception("Can not request configs from remote config center .");
}
var configs = JsonConvert.DeserializeObject<List<KeyValuePair<string, string>>>(response);
Data = new ConcurrentDictionary<string, string>();
configs.ForEach(c =>
{
Data.Add(c);
});
}
}
新建一个MyConfigProvider的类,这个类从ConfigurationProvider继承,并重写其中的Load方法。使用HttpClient从配置中心读取信息后,进行反序列化,并把配置转换为字典。这里注意一下,虽然Data的类型为IDictionary<string,string>,但是这里实例化对象的时候使用了ConcurrentDictionary<string, string>类,因为Dictionary<string,string>是非线程安全的,如果进行多线程读写会出问题。
MyConfigSource
public class MyConfigSource : IConfigurationSource
{
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new MyConfigProvider();
}
}
新建一个MyConfigSource的类,这个类实现IConfigurationSource接口,IConfigurationSource接口只有一个Build方法,返回值为IConfigurationProvider,我们刚才定义的MyConfigProvider因为继承自ConfigurationProvider所以已经实现了IConfigurationProvider,我们直接new一个MyConfigProvider并返回。
MyConfigBuilderExt
public static class MyConfigBuilderExt
{
public static IConfigurationBuilder AddMyConfig(
this IConfigurationBuilder builder
)
{
return builder.Add(new MyConfigSource());
}
}
给IConfigurationBuilder定义一个AddMyConfig的扩展方法,跟.Net Core自带的几个配置源使用风格保持一致。当调用AddMyConfig的时候给IConfigurationBuilder实例添加一个MyConfigSource的源。
使用配置源
在Program中添加MyConfigSource
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, configBuiler) =>
{
configBuiler.AddMyConfig();
})
.UseStartup<Startup>();
}
在ConfigureAppConfiguration的匿名委托方法中调用AddMyConfig扩展方法,这样程序启动的时候会自动使用MyConfigSource源并从配置中心读取配置到本地应用程序。
修改HomeController
public class HomeController : Controller
{
IConfiguration _configuration;
public HomeController(IConfiguration configuration)
{
_configuration = configuration;
}
public IActionResult Index()
{
var secretKey = _configuration["SecretKey"];
var connectionString = _configuration["ConnectionString"];
ViewBag.SecretKey = secretKey;
ViewBag.ConnectionString = connectionString;
return View();
}
}
修改homecontroller,把IConfiguration通过构造函数注入进去,在Index Action方法中读取配置,并赋值给ViewBag
修改Index视图
@{
ViewData["Title"] = "Test my config";
}
<h3>
SecretKey: @ViewBag.SecretKey
</h3>
<h3>
ConnectionString: @ViewBag.ConnectionString
</h3>
修改Index视图的代码,把配置信息从ViewBag中读取出来并在网页上展示。
运行一下

先运行配置中心站点再运行一下网站,首页出现了我们在配置中心定义的SecretKey跟ConnectionString信息,表示我们的程序成功的从配置中心读取了配置信息。我们的自定义配置源已经能够成功运行了。
改进
以上配置源虽然能够成功运行,但是仔细看的话显然它有2个比较大的问题。
- 配置中心的服务地址是写死在类里的。我们的配置中心很有可能会修改ip或者域名,写死在代码里显然不是高明之举,所以我们还是需要保留本地配置文件,把配置中心的服务地址写到本地配置文件中。
- 配置中心作为微服务的基础设施一旦故障会引发非常严重的后果,新启动或者重启的客户端会无法正常启动。如果我们在配置中心正常的时候冗余一份配置在本地,当配置中心故障的时候从本地读取配置,至少可以保证一部分客户端程序能够正常运行。
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"myconfigServer": "http://localhost:5000"
}
修改本地appsettings.json文件,添加myconfigServer的配置信息。
public class MyConfigProvider : ConfigurationProvider
{
private string _serverAddress;
public MyConfigProvider()
{
var jsonConfig = new JsonConfigurationSource();
jsonConfig.FileProvider = new PhysicalFileProvider(Directory.GetCurrentDirectory());
jsonConfig.Path = "appsettings.json";
var jsonProvider = new JsonConfigurationProvider(jsonConfig);
jsonProvider.Load();
jsonProvider.TryGet("myconfigServer", out string serverAddress);
if (string.IsNullOrEmpty(serverAddress))
{
throw new Exception("Can not find myconfigServer's address from appsettings.json");
}
_serverAddress = serverAddress;
}
/// <summary>
/// 尝试从远程配置中心读取配置信息,当成功从配置中心读取信息的时候把配置写到本地的myconfig.json文件中,当配置中心无法访问的时候尝试从本地文件恢复配置。
/// </summary>
public async override void Load()
{
var response = "";
try
{
var client = new HttpClient();
client.BaseAddress = new Uri(_serverAddress);
response = await client.GetStringAsync("/api/configs");
WriteToLocal(response);
}
catch (Exception ex)
{
//write err log
response = ReadFromLocal();
}
if (string.IsNullOrEmpty(response))
{
throw new Exception("Can not request configs from remote config center .");
}
var configs = JsonConvert.DeserializeObject<List<KeyValuePair<string, string>>>(response);
Data = new ConcurrentDictionary<string, string>();
configs.ForEach(c =>
{
Data.Add(c);
});
}
private void WriteToLocal(string resp)
{
var file = Directory.GetCurrentDirectory() + "/myconfig.json";
File.WriteAllText(file,resp);
}
private string ReadFromLocal()
{
var file = Directory.GetCurrentDirectory() + "/myconfig.json";
return File.ReadAllText(file);
}
}
修改MyConfigProvider,修改构造函数,通过JsonConfigurationProvider从本地读取appsettings.json中的myconfigServer配置信息。新增WriteToLocal方法把配置中心返回的json数据写到本地文件中。新增ReadFromLocal方法,从本地文件读取json信息。
再次运行
先运行配置中心站点,再运行客户端网站,可以看到配置信息展示到首页界面上。关闭配置中心客跟客户端网站,并且重启客户端网站依然能够展示配置信息,说明自定义配置源当配置中心故障的时候成功从本地文件恢复了配置。图跟上面的图是一致的,就不贴了。
总结
通过以上我们定义了一个比较简单的自定义配置源,它能够通过http从配置中心读取配置,并且提供了同传统json配置文件一致的使用风格,最大程度的复用旧代码,减少因为引入配置中心而大规模改动代码。我们从上面的代码可以更清楚的知道.Net Core的配置源是如何工作的。ConfigurationSource只是ConfigurationProvider的建造器。真正完成配置加载、查找工作的是ConfigurationProvider。
以上代码还是演示级别的代码,还有很多改进的空间,比如http访问失败的重试,我们可以使用polly重构;比如支持定时从配置中心刷新配置等,有兴趣可以自己去实践一下。
.Net Core 自定义配置源从配置中心读取配置的更多相关文章
- ASP.NET Core的配置(1):读取配置信息
提到"配置"二字,我想绝大部分.NET开发人员脑海中会立马浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化 ...
- 使用XML作为配置表,WinForm程序读取配置表来动态显示控件
一.首先创建一个XML文件定义以下格式(uName:显示的中文字,uKey:代表控件的Name属性,ukeyValue:代表是否显示) 二.项目中定义一个通用类,来存放读取的值 这三个字段对应XML文 ...
- SpringCloudAlibaba注册中心与配置中心之利器Nacos实战与源码分析(上)
不断踩坑并解决问题是每个程序员进阶到资深的必要经历并以此获得满足感,而不断阅读开源项目源码和总结思想是每个架构师成长最佳途径.本篇拉开SpringCloud Alibaba最新版本实战和原理序幕,以工 ...
- ASP.NET Core的配置(3): 将配置绑定为对象[上篇]
出于编程上的便利,我们通常不会直接利用ConfigurationBuilder创建的Configuration对象读取某个单一配置项的值,而是倾向于将一组相关的配置绑定为一个对象,我们将后者称为Opt ...
- [ASP.NET Core 3框架揭秘] 配置[2]:读取配置数据[下篇]
[接上篇]提到“配置”二字,我想绝大部分.NET开发人员脑海中会立即浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化的配置定义 ...
- ASP.NET Core的配置(3): 将配置绑定为对象[下篇]
我们在<读取配置信息>通过实例的形式演示了如何利用Options模型以依赖注入的方式直接获取由指定配置节绑定生成的Options对象,我们再次回顾一下当初我们编写的程序.如下面的代码片段所 ...
- Asp.NetCore源码学习[2-1]:配置[Configuration]
Asp.NetCore源码学习[2-1]:配置[Configuration] 在Asp. NetCore中,配置系统支持不同的配置源(文件.环境变量等),虽然有多种的配置源,但是最终提供给系统使用的只 ...
- ASP.NET Core 6框架揭秘实例演示[25]:配置与承载环境的应用
与服务注册一样,针对配置的设置同样可以采用三种不同的编程模式.第一种是利用WebApplicationBuilder的Host属性返回的IHostBuilder对象,它可以帮助我们设置面向宿主和应用的 ...
- diamond源码阅读-循环探测配置信息是否变化rotateCheckConfigInfo
rotateCheckConfigInfo 这是一个定时任务,循环调用 /** * 循环探测配置信息是否变化,如果变化,则再次向DiamondServer请求获取对应的配置信息 */ private ...
随机推荐
- Java的继承与接口
为什么Java里面只允许单一继承,但允许实现多个接口? 1,为什么不能实现类的多继承?主要是防止多个类有相同的方法名,然而方法体不一样,子类就比较难选择了.所以java和C#不支持类的多继承2,允许具 ...
- IIS6.0创建新网站后,浏览显示需输入用户名和密码
1.首先我们需要创建一个用于匿名访问的账号. 我的电脑右键,电脑管理->本地用户和组->用户->新用户 注意勾选(用户不能更改密码和密码永不过期这两项) 2.右键新创建的用户-& ...
- dede添加自定义函数
在dede安装目录下的include/extend.func.php添加自定义函数: /** * 获取文章第一张图片 */ function getFirstImg($arcId) { global ...
- sql标量值函数,将汉字转化为拼音,无音标
USE [db_Test]GO SET ANSI_NULLS ONGO SET QUOTED_IDENTIFIER ONGO create function [dbo].[fn_GetPinyin]( ...
- vue2.0 element学习
1,bootstrap和vue2.0结合使用 vue文件搭建好后,引入jquery和bootstrap 我采用的方式为外部引用 在main.js内部直接导入 用vue-cli直接安装jquery和bo ...
- Java中 Random
Java中的Random()函数 (2013-01-24 21:01:04) 转载▼ 标签: java random 随机函数 杂谈 分类: Java 今天在做Java练习的时候注意到了Java里面的 ...
- Yandex插件使用说明——Slager_Z
Yandex插件使用说明——Slager_Z 操作步骤: 1.1使用Chrome浏览器安装插件 / 1.2使用crx格式文件 2. 改装成Yandex可使用文件 3. 通过Yandex ...
- Codeforces 599B. Spongebob and Joke 模拟
B. Spongebob and Joke time limit per test 2 seconds memory limit per test 256 megabytes input standa ...
- 7.11 cookie 失效后 ,重新登陆 页面 可能跳出 框架 ,只剩主题 部分 ,
判断地址 不在 框架里 (项目 地址栏一般 都是 首页地址 ) function url(){ var page=getpage(); if(window==top&&(page ...
- centos6.5 redis 安装配置及java调用
1.安装gcc 执行命令 yum install gcc 2. 安装ruby环境 yum install ruby yum install rubygems gem install redis 下载 ...