专栏首页ESOFAR ♥ .NET CORE携程 Apollo 配置中心传统 .NET 项目集成实践

携程 Apollo 配置中心传统 .NET 项目集成实践

官方文档存在的问题

可能由于 Apollo 配置中心的客户端源码一直处于更新中,导致其相关文档有些跟不上节奏,部分文档写的不规范,很容易给做对接的新手朋友造成误导。

比如,我在参考如下两个文档使用传统 .NET 客户端做接入的时候就发现了些问题。

1.两个文档关于标识应用身份的AppId的配置节点不一致。

2.第二个文档关于应用配置发布环境的Environment配置节点的描述出现明显错误。

当然,这些问题随时都有可能被修复。若您看到文档内容与本文描述不符,请以官方文档为准。

传统 .NET 项目快速接入

快速进入正题。

安装依赖包

在您项目的基础设施层,通过 NuGet 包管理器或使用如下命令添加传统 .NET 项目使用的客户端:

Install-Package Com.Ctrip.Framework.Apollo.ConfigurationManager -Version 2.0.3

从上面的包名能看出什么?我这里选装的是2.0.3的版本。还有,这应该是一个 Javaer 起的名字。

配置应用标识 & 服务地址

在您的启动项目中,打开App.configWeb.config配置文件,在<appSettings>节点中增加如下节点:

<!-- Change to the actual app id -->
<add key="Apollo.AppID" value="R01001" />
<add key="Apollo.MetaServer" value="http://localhost:8080" />

若您部署了多套 Config Service,支持多环境,请参考如下配置:

<!-- Change to the actual app id -->
<add key="Apollo.AppID" value="R01001" />

<!-- Should change the apollo config service url for each environment -->
<add key="Apollo.Env" value="DEV" />
<add key="Apollo.DEV.Meta" value="http://localhost:8080"/>
<add key="Apollo.FAT.Meta" value="http://localhost:8081"/>
<add key="Apollo.UAT.Meta" value="http://localhost:8082"/>
<add key="Apollo.PRO.Meta" value="http://localhost:8083"/>

配置完成后,就可以准备在我们项目中使用 Apollo 客户端了。

二次封装代码

我们习惯在项目中使用第三方库的时候封装一层,这种封装是浅层的,一般都是在项目的基础设施层来做,这样其他层使用就不需要再次引入依赖包。

不说了,直接上代码吧。

代码结构大致如下:

├─MyCompany.MyProject.Infrastructure         # 项目基础设施层
│  │                                                       
│  └─Configuration                         
│          ApolloConfiguration.cs            # Apollo 分布式配置项读取实现     
│          ConfigurationChangeEventArgs.cs   # 配置更改回调事件参数
│          IConfiguration.cs                 # 配置抽象接口,可基于此接口实现本地配置读取

IConfiguration

using System;
using System.Configuration;

namespace MyCompany.MyProject.Infrastructure
{
    /// <summary>
    /// 配置抽象接口。
    /// </summary>
    public interface IConfiguration
    {
        /// <summary>
        /// 配置更改回调事件。
        /// </summary>
        event EventHandler<ConfigurationChangeEventArgs> ConfigChanged;

        /// <summary>
        /// 获取配置项。
        /// </summary>
        /// <param name="key">键</param>
        /// <param name="namespaces">命名空间集合</param>
        /// <returns></returns>
        string GetValue(string key, params string[] namespaces);

        /// <summary>
        /// 获取配置项。
        /// </summary>
        /// <typeparam name="TValue">值类型</typeparam>
        /// <param name="key">键</param>
        /// <param name="namespaces">命名空间集合</param>
        /// <returns></returns>
        TValue GetValue<TValue>(string key, params string[] namespaces);

        /// <summary>
        /// 获取配置项,如果值为 <see cref="null"/> 则取参数 <see cref="defaultValue"/> 值。
        /// </summary>
        /// <param name="key">键</param>
        /// <param name="defaultValue">默认值</param>
        /// <param name="namespaces">命名空间集合</param>
        /// <returns></returns>
        string GetDefaultValue(string key, string defaultValue, params string[] namespaces);

        /// <summary>
        /// 获取配置项,如果值为 <see cref="null"/> 则取参数 <see cref="defaultValue"/> 值。
        /// </summary>
        /// <typeparam name="TValue">值类型</typeparam>
        /// <param name="key">键</param>
        /// <param name="defaultValue">默认值</param>
        /// <param name="namespaces">命名空间集合</param>
        /// <returns></returns>
        TValue GetDefaultValue<TValue>(string key, TValue defaultValue, params string[] namespaces);
    }
}

ConfigurationChangeEventArgs

using Com.Ctrip.Framework.Apollo.Model;
using System.Collections.Generic;

namespace MyCompany.MyProject.Infrastructure
{
    public class ConfigurationChangeEventArgs
    {
        public IEnumerable<string> ChangedKeys => Changes.Keys;
        public bool IsChanged(string key) => Changes.ContainsKey(key);
        public string Namespace { get; }
        public IReadOnlyDictionary<string, ConfigChange> Changes { get; }
        public ConfigurationChangeEventArgs(string namespaceName, IReadOnlyDictionary<string, ConfigChange> changes)
        {
            Namespace = namespaceName;
            Changes = changes;
        }
        public ConfigChange GetChange(string key)
        {
            Changes.TryGetValue(key, out var change);
            return change;
        }
    }
}

ApolloConfiguration

using System;
using System.Configuration;
using System.Globalization;
using Com.Ctrip.Framework.Apollo;
using Com.Ctrip.Framework.Apollo.Model;

namespace MyCompany.MyProject.Infrastructure
{
    public class ApolloConfiguration : IConfiguration
    {
        private readonly string _defaultValue = null;

        public event EventHandler<ConfigurationChangeEventArgs> ConfigChanged;

        private IConfig GetConfig(params string[] namespaces)
        {
            var config = namespaces == null || namespaces.Length == 0 ?
                ApolloConfigurationManager.GetAppConfig().GetAwaiter().GetResult() :
                ApolloConfigurationManager.GetConfig(namespaces).GetAwaiter().GetResult();

            config.ConfigChanged += (object sender, ConfigChangeEventArgs args) =>
            {
                ConfigChanged(sender, new ConfigurationChangeEventArgs(args.Namespace, args.Changes));
            };

            return config;
        }

        public string GetValue(string key, params string[] namespaces)
        {
            key = key ?? throw new ArgumentNullException(nameof(key));
            var config = GetConfig(namespaces);
            return config.GetProperty(key, _defaultValue);
        }

        public TValue GetValue<TValue>(string key, params string[] namespaces)
        {
            var value = GetValue(key, namespaces);
            return value == null ?
                default(TValue) :
                (TValue)Convert.ChangeType(value, typeof(TValue), CultureInfo.InvariantCulture);
        }

        public string GetDefaultValue(string key, string defaultValue, params string[] namespaces)
        {
            key = key ?? throw new ArgumentNullException(nameof(key));
            var config = GetConfig(namespaces);
            return config.GetProperty(key, defaultValue);
        }

        public TValue GetDefaultValue<TValue>(string key, TValue defaultValue, params string[] namespaces)
        {
            var value = GetDefaultValue(key, defaultValue, namespaces);
            return value == null ?
                default(TValue) :
                (TValue)Convert.ChangeType(value, typeof(TValue), CultureInfo.InvariantCulture);
        }
    }
}

正确使用姿势

在使用之前需要先把ApolloConfiguration注册到应用容器中,请参考如下代码:

// 这里我们项目使用的 DI 框架是`Autofac`,按需修改吧,记得将实例注册成单例模式。
public class DependencyRegistrar : IDependencyRegistrar
{
    public void Register(ContainerBuilder builder, ITypeFinder typeFinder)
    {
        
        builder.RegisterType<ApolloConfiguration>()
            .As<IConfiguration>()
            .Named<IConfiguration>("configuration")
            .SingleInstance();
            
        ...
    }

    public int Order
    {
        get { return 1; }
    }
}

接下来就可以在项目中使用了,请参考如下代码:

public class UserController : BaseController
{
    private readonly IConfiguration _configuration;

    public UserController(IConfiguration configuration)
    {
        _configuration = configuration;
    }
    
    public ActionResult Add(AddUserInput model)
    {
        if (ModelState.IsValid)
        {
            // 从 Apollo 分布式配置中心 项目`R01001` 默认命名空间`application`下 读取配置项。
            model.Password = _configuration.GetValue("DefaultUserPassword");
            ...
        }
        ...
    }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • [译]RabbitMQ教程C#版 - 路由

    在本教程中,我们会日志系统其再添加一个特性,使其可以只订阅消息的一个子集。例如,将所有日志消息打印到 控制台的同时,只会将严重错误消息写入日志文件(保存到磁盘...

    Esofar
  • [译]RabbitMQ教程C#版 - 主题

    在 教程[4] 中,我们改进了我们日志系统。我们用direct交换器替换了只能呆滞广播消息的fanout交换器,从而可以有选择性的接收日志。

    Esofar
  • C#基础篇 - 理解委托和事件

    委托类似于C++中的函数指针(一个指向内存位置的指针)。委托是C#中类型安全的,可以订阅一个或多个具有相同签名方法的函数指针。简单理解,委托是一种可以把函数当做...

    Esofar
  • Android开发之那些好用的数据结构与API

    由于Android Application 主要是Java语言开发的,所以在写程序的时候,很多朋友们都会用到Java里面常用的数据结构,但是Android中提供...

    YungFan
  • 程序员防猝死指南

    这里罗列了9大程序员职业病,你也可以对号入座,如果你找不到座位,那么恭喜你,你是一个健康的程序员。

    顶级程序员
  • 如何让Sublime Text支持ABAP7.40之后新的关键字语法高亮

    Jerry Wang
  • 为什么Redis要比Memcached更火?

    作者:Kaito 链接:kaito-kidd.com/2020/06/28/redis-vs-memcached/

    民工哥
  • client-go 之 Indexer 的理解

    前面我们讲到 DeltaFIFO 中的元素通过 Pop 函数弹出后,在指定的回调函数中将元素添加到了 Indexer 中。Indexer 是什么?字面意思是索引...

    我是阳明
  • 分布式锁

    1、锁的应用场景: 在单体应用中,我们会使用ReentrantLock或Synchronized来应对并发场景。 比如最常见的卖票场景,假如总共有100张票...

    贪挽懒月
  • emacs配置文件3

    ;;普通设置 (setq inhibit-startup-message t);关闭起动时闪屏 (setq visible-bell t);关闭出错时的提示声...

    py3study

扫码关注云+社区

领取腾讯云代金券