as.net core 5.0 Configuration读取consul的kv存储

Consul

关于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

Asp.net core5.0

首先说一下, 我是用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

相关文章
相关标签/搜索