as.net core 5.0 Configuration讀取consul的kv存儲


Consul

關於consul的環境搭建很簡單,可以用docker臨時搭建以下, consul關於KV存儲的api也很簡單,注意/v1/kv/是默認的公共路徑

-- 運行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中看看值的內容:

Asp.net core5.0

首先說一下, 我是用vs2019創建調試好了的【虛擬機里面】, 傳到git,在物理機上 用vscode打開運行, 目前感覺 vscode 還是沒有vs 強大[vscode 運行時候需要輸入controller  http://localhost:5000/WeatherForecast]。

asp.net的配置的基礎結構依賴於 Microsoft.Extensions.Configuration.Abstractions NuGet包中的一些內容。首先,IConfigurationProvider 是用於提供配置值的接口,然后IConfigurationSource 用於提供已實現上述接口的 provider 的實例。與直接實現 IConfigurationProvider 相比,可以繼承一個名為 ConfigurationProvider 的類。

1.我們的方法就是利用 HttpClient 去獲取 consul 中的配置。一旦我們得到返回的數據【這里是json串】 ,我們迭代每個鍵值對,解碼 Base64 字符串,然后展平所有鍵和JSON對象,以便放入字典中返回

2.我們可以使用 consul 的變更通知。通過添加一個參數(最后一個索引配置的值)來實現的,HTTP 請求會一直阻塞,直到下一次配置變更(或 HttpClient 超時),方法 ListenToConfigurationChanges,以便在后台監聽 consul 的阻塞 HTTP

3.寫一個 ConfigurationSource 來創建我們的 provider,以及封裝一些擴展方法。

4.我們定義一個配置類 ,然后方便項目 使用

以上是我們需要實現的功能, 首先我們修改consul的內容

{
  "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相關的代碼[我這里是放在一起的]

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM