如何在 ASP.Net Core 中使用 Consul 来存储配置(2)

这个配置的基础结构依赖于 Microsoft.Extensions.Configuration.Abstractions NuGet包中的一些内容。首先,IConfigurationProvider 是用于提供配置值的接口,然后IConfigurationSource 用于提供已实现上述接口的 provider 的实例。
您可以在 ASP.Net GitHub 上查看一些实现。
与直接实现 IConfigurationProvider 相比,可以在 Microsoft.Extensions.Configuration 包中继承一个名为 ConfigurationProvider 的类,该类提供了一些样版代码(例如重载令牌的实现)。
这个类包含两个重要的东西:

/* Excerpt from the implementation */
public abstract class ConfigurationProvider : IConfigurationProvider
{
    protected IDictionary<string, string> Data { get; set; }
    public virtual void Load()
    {
    }
}

Data 是包含所有键和值的字典,Load 是应用程序开始时使用的方法,正如其名称所示,它从某处(配置文件或我们的 consul 实例)加载配置并填充字典。

在 ASP.Net Core 中加载 consul 配置

我们第一个想到的方法就是利用 HttpClient 去获取 consul 中的配置。然后,由于配置在层级式的,像一棵树,我们需要把它展开,以便放入字典中,是不是很简单?

首先,实现 Load 方法,但是我们需要一个异步的方法,原始方法会阻塞,所以加入一个异步的 LoadAsync 方法

public override void Load() => LoadAsync().ConfigureAwait(false).GetAwaiter().GetResult();

然后,我们将以递归的方式查询 consul 以获取配置值。它使用类中定义的一些对象,例如_consulUrls,这是一个数组用来保存 consul 实例们的 url(用于故障转移),_path 是键的前缀(例如App1/Dev)。一旦我们得到 json ,我们迭代每个键值对,解码 Base64 字符串,然后展平所有键和JSON对象。

private async Task<IDictionary<string, string>> ExecuteQueryAsync()
{
    int consulUrlIndex = 0;
    while (true)
    {
        try
        {
            using (var httpClient = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip }, true))
            using (var request = new HttpRequestMessage(HttpMethod.Get, new Uri(_consulUrls[consulUrlIndex], "?recurse=true")))
            using (var response = await httpClient.SendAsync(request))
            {
                response.EnsureSuccessStatusCode();
                var tokens = JToken.Parse(await response.Content.ReadAsStringAsync());
                return 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))
                    .SelectMany(Flatten)
                    .ToDictionary(v => ConfigurationPath.Combine(v.Key.Split('/')), v => v.Value, StringComparer.OrdinalIgnoreCase);
            }
        }
        catch
        {
            consulUrlIndex++;
            if (consulUrlIndex >= _consulUrls.Count)
                throw;
        }
    }
}

使键值变平的方法是对树进行简单的深度优先搜索。

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}";
        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;
        }
    }
}

包含构造方法和私有字段的完整的类代码如下:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/dc6dada1191ce973c790f42c47ceaf07.html