写在前面

经过前面三篇关于.NET Core Configuration的文章之后,本篇文章主要讨论如何扩展一个Configuration组件出来。如果前面三篇文章没有看到,可以点击如下地址访问

了解了Configuration的源码后,再去扩展一个组件就会比较简单,接下来我们将在.NET Core 3.0-preview5的基础上创建一个基于Consul的配置组件。

相信大家对Consul已经比较了解了,很多项目都会使用Consul作为配置中心,此处也不做其他阐述了,主要是讲一下,创建Consul配置扩展的一些思路。使用Consul配置功能时,我们可以将信息转成JSON格式后再存储,那么我们在读取的时候,在体验上就像是从读取JSON文件中读取一样。

开发前的准备

初始化Consul

假设你已经安装并启动了Consul,我们打开Key/Value功能界面,创建两组配置选项出来,分别是commonservice和userservice,如下图所示

配置值采用JSON格式

实现思路

我们知道在Configuration整个的设计框架里,比较重要的类ConfigurationRoot,内部又有一个IConfigurationProvider集合属性,也就是说我们追加IConfigurationProvider实例最终也会被放到到该集合中,如下图所示

该项目中,我使用到了一个已经封装好的Consul(V0.7.2.6)类库,同时基于.NET Core关于Configuration的设计风格,做如下的框架设计

考虑到我会在该组件内部创建ConsulClient实例,所以对ConsulClient构造函数的一部分参数做了抽象提取,并添加到了IConsulConfigurationSource中,以增强该组件的灵活性。

之前说过,Consul中的配置信息是以JSON格式存储的,所以此处使用到了Microsoft.Extensions.Configuration.Json.JsonConfigurationFileParser,用以将JSON格式的信息转换为Configuration的通用格式Key/Value。

核心代码

IConsulConfigurationSource

   1:  /// <summary>
   2:  /// ConsulConfigurationSource
   3:  /// </summary>
   4:  public interface IConsulConfigurationSource : IConfigurationSource
   5:  {
   6:      /// <summary>
   7:      /// CancellationToken
   8:      /// </summary>
   9:      CancellationToken CancellationToken { get; }
  10:   
  11:      /// <summary>
  12:      /// Consul构造函数实例,可自定义传入
  13:      /// </summary>
  14:      Action<ConsulClientConfiguration> ConsulClientConfiguration { get; set; }
  15:   
  16:      /// <summary>
  17:      ///  Consul构造函数实例,可自定义传入
  18:      /// </summary>
  19:      Action<HttpClient> ConsulHttpClient { get; set; }
  20:   
  21:      /// <summary>
  22:      ///  Consul构造函数实例,可自定义传入
  23:      /// </summary>
  24:      Action<HttpClientHandler> ConsulHttpClientHandler { get; set; }
  25:   
  26:      /// <summary>
  27:      /// 服务名称
  28:      /// </summary>
  29:      string ServiceKey { get; }
  30:   
  31:      /// <summary>
  32:      /// 可选项
  33:      /// </summary>
  34:      bool Optional { get; set; }
  35:   
  36:      /// <summary>
  37:      /// Consul查询选项
  38:      /// </summary>
  39:      QueryOptions QueryOptions { get; set; }
  40:   
  41:      /// <summary>
  42:      /// 重新加载延迟时间,单位是毫秒
  43:      /// </summary>
  44:      int ReloadDelay { get; set; }
  45:   
  46:      /// <summary>
  47:      /// 是否在配置改变的时候重新加载
  48:      /// </summary>
  49:      bool ReloadOnChange { get; set; }
  50:  }

ConsulConfigurationSource

该类提供了一个构造函数,用于接收ServiceKey和CancellationToken实例

   1:  public ConsulConfigurationSource(string serviceKey, CancellationToken cancellationToken)
   2:  {
   3:      if (string.IsNullOrWhiteSpace(serviceKey))
   4:      {
   5:          throw new ArgumentNullException(nameof(serviceKey));
   6:      }
   7:   
   8:      this.ServiceKey = serviceKey;
   9:      this.CancellationToken = cancellationToken;
  10:  }

其build()方法也比较简单,主要是初始化ConsulConfigurationParser实例

   1:  public IConfigurationProvider Build(IConfigurationBuilder builder)
   2:  {
   3:      ConsulConfigurationParser consulParser = new ConsulConfigurationParser(this);
   4:   
   5:      return new ConsulConfigurationProvider(this, consulParser);
   6:  }

ConsulConfigurationParser

该类比较复杂,主要实现Consul配置的获取、监控以及容错处理,公共方法源码如下

   1:  /// <summary>
   2:  /// 获取并转换Consul配置信息
   3:  /// </summary>
   4:  /// <param name="reloading"></param>
   5:  /// <param name="source"></param>
   6:  /// <returns></returns>
   7:  public async Task<IDictionary<string, string>> GetConfig(bool reloading, IConsulConfigurationSource source)
   8:  {
   9:      try
  10:      {
  11:          QueryResult<KVPair> kvPair = await this.GetKvPairs(source.ServiceKey, source.QueryOptions, source.CancellationToken).ConfigureAwait(false);
  12:          if ((kvPair?.Response == null) && !source.Optional)
  13:          {
  14:              if (!reloading)
  15:              {
  16:                  throw new FormatException(Resources.Error_InvalidService(source.ServiceKey));
  17:              }
  18:   
  19:              return new Dictionary<string, string>();
  20:          }
  21:   
  22:          if (kvPair?.Response == null)
  23:          {
  24:              throw new FormatException(Resources.Error_ValueNotExist(source.ServiceKey));
  25:          }
  26:   
  27:          this.UpdateLastIndex(kvPair);
  28:   
  29:          return JsonConfigurationFileParser.Parse(source.ServiceKey, new MemoryStream(kvPair.Response.Value));
  30:      }
  31:      catch (Exception exception)
  32:      {
  33:          throw exception;
  34:      }
  35:  }
  36:   
  37:  /// <summary>
  38:  /// Consul配置信息监控
  39:  /// </summary>
  40:  /// <param name="key"></param>
  41:  /// <param name="cancellationToken"></param>
  42:  /// <returns></returns>
  43:  public IChangeToken Watch(string key, CancellationToken cancellationToken)
  44:  {
  45:      Task.Run(() => this.RefreshForChanges(key, cancellationToken), cancellationToken);
  46:   
  47:      return this.reloadToken;
  48:  }

另外,关于Consul的监控主要利用了QueryResult.LastIndex属性,该类缓存了该属性的值,并与实获取的值进行比较,以判断是否需要重新加载内存中的缓存配置

ConsulConfigurationProvider

该类除了实现Load方法外,还会根据ReloadOnChange属性,在构造函数中注册OnChange事件,用于重新加载配置信息,源码如下:

   1:  public sealed class ConsulConfigurationProvider : ConfigurationProvider
   2:  {
   3:      private readonly ConsulConfigurationParser configurationParser;
   4:      private readonly IConsulConfigurationSource source;
   5:   
   6:      public ConsulConfigurationProvider(IConsulConfigurationSource source, ConsulConfigurationParser configurationParser)
   7:      {
   8:          this.configurationParser = configurationParser;
   9:          this.source = source;
  10:   
  11:          if (source.ReloadOnChange)
  12:          {
  13:              ChangeToken.OnChange(
  14:                  () => this.configurationParser.Watch(this.source.ServiceKey, this.source.CancellationToken),
  15:                  async () =>
  16:                  {
  17:                      await this.configurationParser.GetConfig(true, source).ConfigureAwait(false);
  18:   
  19:                      Thread.Sleep(source.ReloadDelay);
  20:   
  21:                      this.OnReload();
  22:                  });
  23:          }
  24:      }
  25:   
  26:      public override void Load()
  27:      {
  28:          try
  29:          {
  30:              this.Data = this.configurationParser.GetConfig(false, this.source).ConfigureAwait(false).GetAwaiter().GetResult();
  31:          }
  32:          catch (AggregateException aggregateException)
  33:          {
  34:              throw aggregateException.InnerException;
  35:          }
  36:      }
  37:  }

调用及运行结果

此处调用在Program中实现

   1:  public class Program
   2:  {
   3:      public static void Main(string[] args)
   4:      {
   5:          CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
   6:   
   7:          WebHost.CreateDefaultBuilder(args).ConfigureAppConfiguration(
   8:              (hostingContext, builder) =>
   9:              {
  10:                  builder.AddConsul("userservice", cancellationTokenSource.Token, source =>
  11:                  {
  12:                      source.ConsulClientConfiguration = cco => cco.Address = new Uri("http://localhost:8500");
  13:                      source.Optional = true;
  14:                      source.ReloadOnChange = true;
  15:                      source.ReloadDelay = 300;
  16:                      source.QueryOptions = new QueryOptions
  17:                      {
  18:                          WaitIndex = 0
  19:                      };
  20:                  });
  21:   
  22:                  builder.AddConsul("commonservice", cancellationTokenSource.Token, source =>
  23:                  {
  24:                      source.ConsulClientConfiguration = cco => cco.Address = new Uri("http://localhost:8500");
  25:                      source.Optional = true;
  26:                      source.ReloadOnChange = true;
  27:                      source.ReloadDelay = 300;
  28:                      source.QueryOptions = new QueryOptions
  29:                      {
  30:                          WaitIndex = 0
  31:                      };
  32:                  });
  33:              }).UseStartup<Startup>().Build().Run();
  34:      }
  35:  }

运行结果,如下图所示,我们已经加载到了两个ConsulProvider实例,这与我们在Program中添加的两个Consul配置一致,其中所加载到的值也和.NET Core Configuration的Key/Value风格相一致,所加载到的值也会Consul中所存储的相一致

总结

基于源码扩展一个配置组件出来,还是比较简单的,另外需要说明的是,该组件关于JSON的处理主要基于.NET Core原生类库,位于命名空间内的System.Text.Json中,所以该组件无法在.NET Core 3.0之前的版本中运行,需要引入额外的JSON组件辅助处理。

源码已经托管于GitHub,地址:https://github.com/edison0621/Navyblue.Extensions.Configuration.Consul,记得点个小星星哦

.NET Core 3.0之创建基于Consul的Configuration扩展组件的更多相关文章

  1. .NET Core 3.0之深入源码理解Configuration(一)

    Configuration总体介绍 微软在.NET Core里设计出了全新的配置体系,并以非常灵活.可扩展的方式实现.从其源码来看,其运行机制大致是,根据其Source,创建一个Builder实例,并 ...

  2. asp.net core 2.0 web api基于JWT自定义策略授权

    JWT(json web token)是一种基于json的身份验证机制,流程如下: 通过登录,来获取Token,再在之后每次请求的Header中追加Authorization为Token的凭据,服务端 ...

  3. .NET Core 3.0之深入源码理解Configuration(二)

      文件型配置基本内容 上一篇文章讨论了Configuration的几个核心对象,本文继续讨论Configuration中关于文件型配置的相关内容.相比较而言,文件型配置的使用场景更加广泛,用户自定义 ...

  4. easyui基于 layui.laydate日期扩展组件

    本人后端开发码农一个,公司前端忙的一逼,项目使用的是easyui组件,其自带的datebox组件使用起来非常不爽,主要表现在 1.自定义显示格式很麻烦 2.选择年份和月份用户体验也不好 网上有关于和M ...

  5. 为.net Core 3.0 WebApi 创建Linux守护进程

    前言 我们一般可以在Linux服务器上执行 dotnet <app_assembly.dll> 命令来运行我们的.net Core WebApi应用.但是这样运行起来的应用很不稳定,关闭终 ...

  6. .NET Core 3.0之深入源码理解Configuration(三)

      写在前面 上一篇文章讨论了文件型配置的基本内容,本篇内容讨论JSON型配置的实现方式,理解了这一种配置类型的实现方式,那么其他类型的配置实现方式基本可以触类旁通.看过了上一篇文章的朋友,应该看得出 ...

  7. ASP.NET Core 实战:将 .NET Core 2.0 项目升级到 .NET Core 2.1

    一.前言  最近一两个星期,加班,然后回去后弄自己的博客,把自己的电脑从 Windows 10 改到 Ubuntu 18.10 又弄回 Windows 10,原本计划的学习 Vue 中生命周期的相关知 ...

  8. .NET Core 3.0之深入源码理解Host(二)

      写在前面 停了近一个月的技术博客,随着正式脱离996的魔窟,接下来也正式恢复了.本文从源码角度进一步讨论.NET Core 3.0 中关于Host扩展的一些技术点,主要讨论Long Run Pro ...

  9. 译 .NET Core 3.0 发布

    原文:<Announcing .NET Core 3.0> 宣布.NET Core 3.0 发布 很高兴宣布.NET Core 3.0的发布.它包括许多改进,包括添加Windows窗体和W ...

随机推荐

  1. Python基础篇 -- 字符串

    字符串 字符串是不可变的对象,任何操作对原字符串是不会有任何影响的. 索引和切片 索引 . 索引就是下标, 下标从 0 开始, 使用[] 来获取数据 s1 = "0123456" ...

  2. C++ lvalue,prvalue,xvalue,glvalue和rvalue详解(from cppreference)

    General 每一个C++表达式(一个操作符和它的操作数,一个字面值,一个变量名等等)都代表着两个独立属性:类型+属性分类.在现代C++中 glvalue(泛左值) = lvalue (传统意义上的 ...

  3. (15)zabbix ODBC数据库监控

    概述 ODBC监控对应于Zabbix Web管理端中的Database monitor监控项类型. ODBC是用于访问数据库管理系统(DBMS)的C语言中间件API.ODBC由Microsoft开发, ...

  4. python安装mysql-connector出错

    windows 7环境 1.进入命令行执行以下命令: C:\Users\Administrator>pip install mysql-connector 注:安装下载较慢,直接失败,改用VPN ...

  5. PyCharm(一)——PyCharm设置SSH远程调试

    一.环境 系统环境:windows10 64位 软件:PyCharm2017.3 本地Python环境:Python2.7 二.配置 2.1配置远程调试 第一步:运行PyCharm,然后点击设置如下图 ...

  6. 【cpu】CPU版本认识

    此图应该是摘选自<鸟哥的linux私房菜>

  7. ios开发中遇到的文件和字符的问题大总结

    我今天遇到的NSString问题 今天遇到一个字符串空指针问题,让我明白了许多 其实我们定义一个NSString * string,其实是定义了一个字符串指针,现在string没有指向任何地方,我们必 ...

  8. PAT Basic 1019

    1019 数字黑洞 给定任一个各位数字不完全相同的4位正整数,如果我们先把4个数字按非递增排序,再按非递减排序,然后用第1个数字减第2个数字,将得到一个新的数字.一直重复这样做,我们很快会停在有“数字 ...

  9. [转]python开发_shelve_完整版

    ''' python中的shelve模块,可以提供一些简单的数据操作 他和python中的dbm很相似. 区别如下: 都是以键值对的形式保存数据,不过在shelve模块中, key必须为字符串,而值可 ...

  10. 【LeetCode】Pancake Sorting(煎饼排序)

    这道题是LeetCode里的第969道题. 题目要求: 给定数组 A,我们可以对其进行煎饼翻转:我们选择一些正整数 k <= A.length,然后反转 A 的前 k 个元素的顺序.我们要执行零 ...