配置,几乎所有的应用程序都离不开它。.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 自定义配置源从配置中心读取配置的更多相关文章

  1. ASP.NET Core的配置(1):读取配置信息

    提到"配置"二字,我想绝大部分.NET开发人员脑海中会立马浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化 ...

  2. 使用XML作为配置表,WinForm程序读取配置表来动态显示控件

    一.首先创建一个XML文件定义以下格式(uName:显示的中文字,uKey:代表控件的Name属性,ukeyValue:代表是否显示) 二.项目中定义一个通用类,来存放读取的值 这三个字段对应XML文 ...

  3. SpringCloudAlibaba注册中心与配置中心之利器Nacos实战与源码分析(上)

    不断踩坑并解决问题是每个程序员进阶到资深的必要经历并以此获得满足感,而不断阅读开源项目源码和总结思想是每个架构师成长最佳途径.本篇拉开SpringCloud Alibaba最新版本实战和原理序幕,以工 ...

  4. ASP.NET Core的配置(3): 将配置绑定为对象[上篇]

    出于编程上的便利,我们通常不会直接利用ConfigurationBuilder创建的Configuration对象读取某个单一配置项的值,而是倾向于将一组相关的配置绑定为一个对象,我们将后者称为Opt ...

  5. [ASP.NET Core 3框架揭秘] 配置[2]:读取配置数据[下篇]

    [接上篇]提到“配置”二字,我想绝大部分.NET开发人员脑海中会立即浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化的配置定义 ...

  6. ASP.NET Core的配置(3): 将配置绑定为对象[下篇]

    我们在<读取配置信息>通过实例的形式演示了如何利用Options模型以依赖注入的方式直接获取由指定配置节绑定生成的Options对象,我们再次回顾一下当初我们编写的程序.如下面的代码片段所 ...

  7. Asp.NetCore源码学习[2-1]:配置[Configuration]

    Asp.NetCore源码学习[2-1]:配置[Configuration] 在Asp. NetCore中,配置系统支持不同的配置源(文件.环境变量等),虽然有多种的配置源,但是最终提供给系统使用的只 ...

  8. ASP.NET Core 6框架揭秘实例演示[25]:配置与承载环境的应用

    与服务注册一样,针对配置的设置同样可以采用三种不同的编程模式.第一种是利用WebApplicationBuilder的Host属性返回的IHostBuilder对象,它可以帮助我们设置面向宿主和应用的 ...

  9. diamond源码阅读-循环探测配置信息是否变化rotateCheckConfigInfo

    rotateCheckConfigInfo 这是一个定时任务,循环调用 /** * 循环探测配置信息是否变化,如果变化,则再次向DiamondServer请求获取对应的配置信息 */ private ...

随机推荐

  1. X_PU

    通俗易懂告诉你CPU/GPU/TPU/NPU...XPU都是些什么鬼?[附把妹秘籍] 2017-10-27 19:54移动芯片/谷歌 作者:iot101君 物联网智库 原创 转载请注明来源和出处 现在 ...

  2. 【英宝通Unity4.0公开课学习 】(一)资源管理

    经过多次面试后发现自己对Unity3D的框架缺乏一个整体的认识. 而前面由于离职等原因总是忙于修修补补,疲于奔命,感觉相当疲惫. 还好,后来经过调整,开始淡定了起来.得特别感谢一本书哇:<高效人 ...

  3. swift UITextfield 添加点击方法 - 简单实现

    1. 真正在任何系统上都有效的方法 1./// 城市选择 private lazy var cityTextfield:UITextField = { let tf = UITextField() t ...

  4. discuz回贴通知插件实现-插件后台管理配置

    1.登出discuz后台,再次设计插件 2.使用变量    

  5. ADF backing Bean中常用的代码

    // 刷新iterator bindings.refreshControl(); iterBind.executeQuery(); iterBind.refresh(DCIteratorBinding ...

  6. adf 笔记

    1>jsf在bean中如何获取url参数,注意bean的范围,如果存在分页,范围不能设置为request,否则第二次加载的时候参数会为空. 最小设置为view,在当前页面中一直有效. 方法一:F ...

  7. 什么是springMvc的参数绑定?

    参数绑定通俗来讲就是从页面传过来的数据通过SpringMvc进行接收.接收的数据类型可以有: (1)SpringMvc默认支持的类型:request.session.application等. (2) ...

  8. 那些你不知道的PS大片摄影

    你以为这张照片是P出来的? 才不是! 人家是开个起重机吊着拍的! 而且没有任何保护措施! 这样的照片战斗民族的摄影师才敢这样拍…要是在天朝这么做估计都找不到模特… 这张照片出自乌兹别克斯坦的摄影师Ra ...

  9. [SoapUI] 通过正则表达式从xml格式的response中提取ID

    import com.eviware.soapui.support.GroovyUtils import java.util.regex.* //Get response def groovyUtil ...

  10. java.text.SimpleDateFormat的使用

    SimpleDateFormat 是一个以国别敏感的方式格式化和分析数据的具体类. 它允许格式化 (date -> text).语法分析 (text -> date)和标准化. Simpl ...