.Net Core 自定义配置源从配置中心读取配置
配置,幾乎所有的應(yīng)用程序都離不開(kāi)它。.Net Framework時(shí)代我們使用App.config、Web.config,到了.Net Core的時(shí)代我們使用appsettings.json,這些我們?cè)偈煜げ贿^(guò)了。然而到了容器化、微服務(wù)的時(shí)代,這些本地文件配置有的時(shí)候就不太合適了。當(dāng)你把本地部署的服務(wù)搬到docker上后,你會(huì)發(fā)現(xiàn)要修改一個(gè)配置文件變的非常麻煩。你不得不通過(guò)宿主機(jī)進(jìn)入容器內(nèi)部來(lái)修改文件,也許容器內(nèi)還不帶vi等編輯工具,你連看都不能看,改都不能。更別說(shuō)當(dāng)你啟動(dòng)多個(gè)容器實(shí)例來(lái)做分布式應(yīng)用的時(shí)候,一個(gè)個(gè)去修改容器的配置,這簡(jiǎn)直要命了。? 因?yàn)檫@些原因,所以“配置中心”就誕生了。配置中心是微服務(wù)的基礎(chǔ)設(shè)施,它對(duì)配置進(jìn)行集中的管理并對(duì)外暴露接口,當(dāng)應(yīng)用程序需要的時(shí)候通過(guò)接口讀取。配置通常為Key/Value模式,然后通過(guò)http接口暴露。好了,配置中心不多說(shuō)了,感覺(jué)要偏了,這次是介紹怎么自定義一個(gè)配置源從配置中心讀取配置。廢話(huà)不多說(shuō)直接上代碼吧。
模擬配置中心
我們新建一個(gè)asp.net core webapi站點(diǎn)來(lái)模擬配置中心服務(wù),端口配置到5000,并添加相應(yīng)的controller來(lái)模擬配置中心對(duì)外的接口。
?添加一個(gè)configscontroller,并修改Get方法,返回2個(gè)配置鍵值對(duì):
[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;}}訪問(wèn)下/api/configs看下返回是否正確:
自定義配置源
從現(xiàn)在開(kāi)始我們真正開(kāi)始來(lái)定義一個(gè)自定義的配置源然后當(dāng)程序啟動(dòng)的時(shí)候從配置中心讀取配置文件信息,并提供給后面的代碼使用配置。
新建一個(gè)asp.net core mvc站點(diǎn)來(lái)模擬客戶(hù)端程序。
MyConfigProvider
public class MyConfigProvider : ConfigurationProvider{/// <summary>/// 嘗試從遠(yuǎn)程配置中心讀取配置信息/// </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);});}}新建一個(gè)MyConfigProvider的類(lèi),這個(gè)類(lèi)從ConfigurationProvider繼承,并重寫(xiě)其中的Load方法。使用HttpClient從配置中心讀取信息后,進(jìn)行反序列化,并把配置轉(zhuǎn)換為字典。這里注意一下,雖然Data的類(lèi)型為IDictionary,但是這里實(shí)例化對(duì)象的時(shí)候使用了ConcurrentDictionary類(lèi),因?yàn)镈ictionary是非線(xiàn)程安全的,如果進(jìn)行多線(xiàn)程讀寫(xiě)會(huì)出問(wèn)題。
MyConfigSource
public class MyConfigSource : IConfigurationSource{public IConfigurationProvider Build(IConfigurationBuilder builder){return new MyConfigProvider();}}新建一個(gè)MyConfigSource的類(lèi),這個(gè)類(lèi)實(shí)現(xiàn)IConfigurationSource接口,IConfigurationSource接口只有一個(gè)Build方法,返回值為IConfigurationProvider,我們剛才定義的MyConfigProvider因?yàn)槔^承自ConfigurationProvider所以已經(jīng)實(shí)現(xiàn)了IConfigurationProvider,我們直接new一個(gè)MyConfigProvider并返回。
MyConfigBuilderExt
public static class MyConfigBuilderExt{public static IConfigurationBuilder AddMyConfig(this IConfigurationBuilder builder){return builder.Add(new MyConfigSource());}}給IConfigurationBuilder定義一個(gè)AddMyConfig的擴(kuò)展方法,跟.Net Core自帶的幾個(gè)配置源使用風(fēng)格保持一致。當(dāng)調(diào)用AddMyConfig的時(shí)候給IConfigurationBuilder實(shí)例添加一個(gè)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的匿名委托方法中調(diào)用AddMyConfig擴(kuò)展方法,這樣程序啟動(dòng)的時(shí)候會(huì)自動(dòng)使用MyConfigSource源并從配置中心讀取配置到本地應(yīng)用程序。
修改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通過(guò)構(gòu)造函數(shù)注入進(jìn)去,在Index Action方法中讀取配置,并賦值給ViewBag
修改Index視圖
@{ViewData["Title"] = "Test my config"; } <h3>SecretKey: @ViewBag.SecretKey </h3> <h3>ConnectionString: @ViewBag.ConnectionString </h3>修改Index視圖的代碼,把配置信息從ViewBag中讀取出來(lái)并在網(wǎng)頁(yè)上展示。
運(yùn)行一下
先運(yùn)行配置中心站點(diǎn)再運(yùn)行一下網(wǎng)站,首頁(yè)出現(xiàn)了我們?cè)谂渲弥行亩x的SecretKey跟ConnectionString信息,表示我們的程序成功的從配置中心讀取了配置信息。我們的自定義配置源已經(jīng)能夠成功運(yùn)行了。
改進(jìn)
以上配置源雖然能夠成功運(yùn)行,但是仔細(xì)看的話(huà)顯然它有2個(gè)比較大的問(wèn)題。
配置中心的服務(wù)地址是寫(xiě)死在類(lèi)里的。我們的配置中心很有可能會(huì)修改ip或者域名,寫(xiě)死在代碼里顯然不是高明之舉,所以我們還是需要保留本地配置文件,把配置中心的服務(wù)地址寫(xiě)到本地配置文件中。
配置中心作為微服務(wù)的基礎(chǔ)設(shè)施一旦故障會(huì)引發(fā)非常嚴(yán)重的后果,新啟動(dòng)或者重啟的客戶(hù)端會(huì)無(wú)法正常啟動(dòng)。如果我們?cè)谂渲弥行恼5臅r(shí)候冗余一份配置在本地,當(dāng)配置中心故障的時(shí)候從本地讀取配置,至少可以保證一部分客戶(hù)端程序能夠正常運(yùn)行。
修改本地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>/// 嘗試從遠(yuǎn)程配置中心讀取配置信息,當(dāng)成功從配置中心讀取信息的時(shí)候把配置寫(xiě)到本地的myconfig.json文件中,當(dāng)配置中心無(wú)法訪問(wèn)的時(shí)候嘗試從本地文件恢復(fù)配置。/// </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 logresponse = 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,修改構(gòu)造函數(shù),通過(guò)JsonConfigurationProvider從本地讀取appsettings.json中的myconfigServer配置信息。新增WriteToLocal方法把配置中心返回的json數(shù)據(jù)寫(xiě)到本地文件中。新增ReadFromLocal方法,從本地文件讀取json信息。
再次運(yùn)行
先運(yùn)行配置中心站點(diǎn),再運(yùn)行客戶(hù)端網(wǎng)站,可以看到配置信息展示到首頁(yè)界面上。關(guān)閉配置中心客跟客戶(hù)端網(wǎng)站,并且重啟客戶(hù)端網(wǎng)站依然能夠展示配置信息,說(shuō)明自定義配置源當(dāng)配置中心故障的時(shí)候成功從本地文件恢復(fù)了配置。圖跟上面的圖是一致的,就不貼了。
總結(jié)
通過(guò)以上我們定義了一個(gè)比較簡(jiǎn)單的自定義配置源,它能夠通過(guò)http從配置中心讀取配置,并且提供了同傳統(tǒng)json配置文件一致的使用風(fēng)格,最大程度的復(fù)用舊代碼,減少因?yàn)橐肱渲弥行亩笠?guī)模改動(dòng)代碼。我們從上面的代碼可以更清楚的知道.Net Core的配置源是如何工作的。ConfigurationSource只是ConfigurationProvider的建造器。真正完成配置加載、查找工作的是ConfigurationProvider。
以上代碼還是演示級(jí)別的代碼,還有很多改進(jìn)的空間,比如http訪問(wèn)失敗的重試,我們可以使用polly重構(gòu);比如支持定時(shí)從配置中心刷新配置等,有興趣可以自己去實(shí)踐一下。
總結(jié)
以上是生活随笔為你收集整理的.Net Core 自定义配置源从配置中心读取配置的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 微服务框架Demo.MicroServe
- 下一篇: 基于.NetCore3.1系列 —— 日