關於consul的環境搭建很簡單,能夠用docker臨時搭建如下, consul關於KV存儲的api也很簡單,注意/v1/kv/是默認的公共路徑html
-- 運行docker docker pull consul:latest docker run --name consul -d -p 8500:8500 consul --create /v1/kv/是公共路徑 curl --request PUT --data '{"host":"localhost"}' http://127.0.0.1:8500/v1/kv/config/v1/local -- Get curl http://127.0.0.1:8500/v1/kv/config/v1/local -- delete curl --request DELETE http://127.0.0.1:8500/v1/kv/config/v1/local
在UI中看看值的內容:git
首先說一下, 我是用vs2019建立調試好了的【虛擬機裏面】, 傳到git,在物理機上 用vscode打開運行, 目前感受 vscode 仍是沒有vs 強大[vscode 運行時候須要輸入controller http://localhost:5000/WeatherForecast]。github
asp.net的配置的基礎結構依賴於 Microsoft.Extensions.Configuration.Abstractions NuGet包中的一些內容。首先,IConfigurationProvider 是用於提供配置值的接口,而後IConfigurationSource 用於提供已實現上述接口的 provider 的實例。與直接實現 IConfigurationProvider 相比,能夠繼承一個名爲 ConfigurationProvider 的類。web
1.咱們的方法就是利用 HttpClient 去獲取 consul 中的配置。一旦咱們獲得返回的數據【這裏是json串】 ,咱們迭代每一個鍵值對,解碼 Base64 字符串,而後展平全部鍵和JSON對象,以便放入字典中返回mongodb
2.咱們能夠使用 consul 的變動通知。經過添加一個參數(最後一個索引配置的值)來實現的,HTTP 請求會一直阻塞,直到下一次配置變動(或 HttpClient 超時),方法 ListenToConfigurationChanges,以便在後臺監聽 consul 的阻塞 HTTPdocker
3.寫一個 ConfigurationSource 來建立咱們的 provider,以及封裝一些擴展方法。json
4.咱們定義一個配置類 ,而後方便項目 使用api
以上是咱們須要實現的功能, 首先咱們修改consul的內容asp.net
{ "Logging": { "LogLevel": { "Default": "Warning" } }, "option1": "value1_from_json", "option2": 2, "subsection": { "suboption1": "subvalue1_from_json" }, "student": [ { "Name": "Gandalf", "Age": "1000" }, { "Name": "Harry", "Age": "17" } ], "AllowedHosts": "*", "MongodbHost": { "Connection": "mongodb://127.0.0.1:27018", "DataBase": "TemplateDb", "Table": "CDATemplateInfo" } }
其次 建立讀取consul相關的代碼[我這裏是放在一塊兒的]dom
using Microsoft.Extensions.Configuration; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; namespace ConsulApi { public class ConsulConfigurationProvider : ConfigurationProvider { private const string ConsulIndexHeader = "X-Consul-Index"; //consul 的變動通知 最後一個索引配置的值 private readonly string _path; private readonly HttpClient _httpClient; private readonly IReadOnlyList<Uri> _consulUrls; private readonly Task _configurationListeningTask; private int _consulUrlIndex; private int _failureCount; private int _consulConfigurationIndex; public ConsulConfigurationProvider(IEnumerable<Uri> consulUrls, string path) { _path = path; _consulUrls = consulUrls.Select(u => new Uri(u, $"v1/kv/{path}")).ToList(); if (_consulUrls.Count <= 0) { throw new ArgumentOutOfRangeException(nameof(consulUrls)); } _httpClient = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip }, true); _configurationListeningTask = new Task(ListenToConfigurationChanges); } public override void Load() => LoadAsync().ConfigureAwait(false).GetAwaiter().GetResult(); private async Task LoadAsync() { Data = await ExecuteQueryAsync(); if (_configurationListeningTask.Status == TaskStatus.Created) _configurationListeningTask.Start(); } // consul 的變動通知 private async void ListenToConfigurationChanges() { while (true) { try { if (_failureCount > _consulUrls.Count) { _failureCount = 0; await Task.Delay(TimeSpan.FromMinutes(1)); } Data = await ExecuteQueryAsync(true); OnReload(); _failureCount = 0; } catch (TaskCanceledException) { _failureCount = 0; } catch { _consulUrlIndex = (_consulUrlIndex + 1) % _consulUrls.Count; _failureCount++; } } } private async Task<IDictionary<string, string>> ExecuteQueryAsync(bool isBlocking = false) { //?recurse=true以遞歸方式查詢任何節點 var requestUri = isBlocking ? $"?recurse=true&index={_consulConfigurationIndex}" : "?recurse=true"; using (var request = new HttpRequestMessage(HttpMethod.Get, new Uri(_consulUrls[_consulUrlIndex], requestUri))) using (var response = await _httpClient.SendAsync(request)) { response.EnsureSuccessStatusCode(); if (response.Headers.Contains(ConsulIndexHeader)) { var indexValue = response.Headers.GetValues(ConsulIndexHeader).FirstOrDefault(); int.TryParse(indexValue, out _consulConfigurationIndex); } var tokens = JToken.Parse(await response.Content.ReadAsStringAsync()); List<KeyValuePair<string, JToken>> pairs=null; Dictionary<string, string> retDic = null; //我這裏實際只有一個token int tokenCount = tokens.Count(); if (tokenCount == 1) { string valueStr = tokens[0].Value<string>("Value"); JToken value = string.IsNullOrEmpty(valueStr) ? null : JToken.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(valueStr))); pairs = new List<KeyValuePair<string, JToken>>(1); pairs.Add(KeyValuePair.Create(string.Empty, value)); } else if (tokenCount > 1) { pairs = tokens.Select(k => KeyValuePair.Create ( k.Value<string>("Key").Substring(_path.Length + 1), k.Value<string>("Value") != null ? JToken.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(k.Value<string>("Value")))) : null )) .Where(v => !string.IsNullOrWhiteSpace(v.Key)).ToList(); } if (pairs!=null) { retDic= pairs.SelectMany(Flatten) .ToDictionary(v => ConfigurationPath.Combine(v.Key.Split('/')), v => v.Value, StringComparer.OrdinalIgnoreCase); } return retDic; } } // 使鍵值變平的方法是對樹進行簡單的深度優先搜索 private static IEnumerable<KeyValuePair<string, string>> Flatten(KeyValuePair<string, JToken> tuple) { if (!(tuple.Value is JObject value)) yield break; foreach (var property in value) { var propertyKey = $"{tuple.Key}/{property.Key}"; if (string.IsNullOrEmpty(tuple.Key)) { propertyKey = property.Key; } switch (property.Value.Type) { case JTokenType.Object: foreach (var item in Flatten(KeyValuePair.Create(propertyKey, property.Value))) yield return item; break; case JTokenType.Array: break; default: yield return KeyValuePair.Create(propertyKey, property.Value.Value<string>()); break; } } } } // 有了一個 ConfigurationProvider, 再寫一個 ConfigurationSource 來建立 咱們的 provide public class ConsulConfigurationSource : IConfigurationSource { public IEnumerable<Uri> ConsulUrls { get; } public string Path { get; } public ConsulConfigurationSource(IEnumerable<Uri> consulUrls, string path) { ConsulUrls = consulUrls; Path = path; } public IConfigurationProvider Build(IConfigurationBuilder builder) { return new ConsulConfigurationProvider(ConsulUrls, Path); } } // 擴展方法 public static class ConsulConfigurationExtensions { public static IConfigurationBuilder AddConsul(this IConfigurationBuilder configurationBuilder, IEnumerable<Uri> consulUrls, string consulPath) { return configurationBuilder.Add(new ConsulConfigurationSource(consulUrls, consulPath)); } public static IConfigurationBuilder AddConsul(this IConfigurationBuilder configurationBuilder, IEnumerable<string> consulUrls, string consulPath) { return configurationBuilder.AddConsul(consulUrls.Select(u => new Uri(u)), consulPath); } } public class MongodbHostOptions { public string Connection { get; set; } public string DataBase { get; set; } public string Table { get; set; } } }
3投入使用:
A:修改Program.cs 文件:
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureAppConfiguration(cb => { var configuration = cb.Build(); List<Uri> uris = new List<Uri>(); uris.Add(new Uri("http://192.168.100.19:8500/")); cb.AddConsul( uris, "config/v1/local"); }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
B: 修改Startup.cs文件:
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddOptions(); services.Configure<MongodbHostOptions>(Configuration.GetSection("MongodbHost")); }
C: 修改默認的WeatherForecastController.cs
private readonly ILogger<WeatherForecastController> _logger; MongodbHostOptions _options; public WeatherForecastController(IOptionsSnapshot<MongodbHostOptions> options ,ILogger<WeatherForecastController> logger) { _logger = logger; _options = options.Value; } [HttpGet] public IEnumerable<WeatherForecast> Get() { var rng = new Random(); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)]+ _options.Connection }) .ToArray(); }
運行效果【若是consul內容跟新後,讀取的也是最新數據】:
下載:https://github.com/dz45693/asp.netcoreConsulKv.git
參考:https://www.cnblogs.com/rwing/p/consul-configuration-aspnet-core.html