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

posted on 2021-01-24 14:16  dz45693  阅读(2301)  评论(0编辑  收藏  举报

导航