前言
配置是我们必不可少的功能,我们在开发中,经常会遇到需要获取配置信息的需求,那么如何才能优雅的获取配置信息?
我们希望新的配置:
支持强类型
配置变更后通知
学习难度低
快速入门
根据使用场景我们将配置分为本地配置以及远程配置,下面我们就来看一下本地配置与远程配置是如何来使用的?
本地配置
新建 ASP.NET Core 空项目Assignment.MasaConfiguration
,并安装Masa.Contrib.Configuration
dotnet new web -o Assignment.MasaConfiguration
cd Assignment.MasaConfiguration
dotnet add package Masa.Contrib.Configuration --version 0.6.0-preview.7
复制代码
新建类AppConfig
、ConnectionStrings
,用于存储数据库配置
/// <summary>
/// 应用配置类
/// </summary>
public class AppConfig : LocalMasaConfigurationOptions
{
public ConnectionStrings ConnectionStrings { get; set; }
}
public class ConnectionStrings
{
public string DefaultConnection { get; set; }
}
复制代码
修改文件appsettings.json
{
"AppConfig": {
"ConnectionStrings": {
"DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"
}
}
}
复制代码
注册MasaConfiguration
,修改类Program
builder.AddMasaConfiguration();
复制代码
如何使用?修改类Program
app.MapGet("/AppConfig", (IOptions<AppConfig> appConfig)
{
return appConfig.Value.ConnectionStrings.DefaultConnection);
});
复制代码
如果希望监听配置变更事件,则可使用 IOptionsMonitor<TOptions>的OnChange方法
远程配置
目前我们远程配置的能力仅实现了 MASA DCC(Dcc: Distributed Configuration Center 是一个以 DDD 为指导思想、使用.Net6.0 开发的分布式配置中心), 下面就让我们看看如何来使用它
选中Assignment.MasaConfiguration
,并安装Masa.Contrib.Configuration.ConfigurationApi.Dcc
dotnet add package Masa.Contrib.Configuration.ConfigurationApi.Dcc --version 0.6.0-preview.7
复制代码
修改appsettings.json
{
//Dcc配置,扩展Configuration能力,支持远程配置
"DccOptions": {
"ManageServiceAddress ": "http://localhost:8890",
"RedisOptions": {
"Servers": [
{
"Host": "localhost",
"Port": 8889
}
],
"DefaultDatabase": 0,
"Password": ""
}
}
}
复制代码
新建类RedisOptions
, 用于配置业务项目中使用的缓存地址
public class RedisOptions : ConfigurationApiMasaConfigurationOptions
{
public string Host { get; set; }
public int Port { get; set; }
public string Password { get; set; }
public int DefaultDatabase { get; set; }
}
复制代码
修改类Program
var app = builder.AddMasaConfiguration(configurationBuilder =>
{
configurationBuilder.UseDcc();
}).Build();
复制代码
如何使用?
// 推荐使用,通过IOptions<TOptions>获取配置,支持强类型
app.MapGet("/AppConfig", (IOptions<RedisOptions> options)
{
return options.Value.Host;
});
复制代码
进阶
到目前为止,我们已经学会了如何使用 Masa 提供的配置,但只有了解原理,我们才敢在项目中大胆的用起来,出现问题后才能快速的定位并解决问题,下面我们就来深入了解下
分类
根据使用场景我们将配置划分为:
IConfiguration 结构
在使用 MasaConfiguration 后,IConfiguration 的文件结构变更为:
IConfiguration
├── Local 本地节点(固定)
│ ├── Platforms 自定义配置
│ ├── ├── Name 参数
├── ConfigurationAPI 远程节点(固定)
│ ├── AppId 替换为你的AppId
│ ├── AppId ├── Platforms 自定义节点
│ ├── AppId ├── Platforms ├── Name 参数
复制代码
除了以下配置源以及配置的提供者提供的配置除外,其余的配置会迁移到Local
节点下
全局配置
MasaConfiguration 中提供了全局配置的功能,并默认支持AppId
、Environment
、Cluster
优先级
获取参数值的优先级为:
自定义全局配置 > 从IConfiguration中获取(支持命令、环境变量、配置文件) > 约定配置
复制代码
自定义全局配置
service.Configure<MasaAppConfigureOptions>(options =>
{
options.AppId = "Replace-With-Your-AppId";
options.Environment = "Replace-With-Your-Environment";
options.Cluster = "Replace-With-Your-Cluster";
options.TryAdd("Replace-With-Your-ConfigKey", "Replace-With-Your-ConfigValue");// 自定义全局配置键、值
})
复制代码
IConfiguration 中获取
当未指定配置的值时,将会从配置中获取得到配置的值,默认配置与 Key 的关系为:
当命令行与环境变量获取参数失败后,则会尝试从配置文件根据配置的 Key 获取对应的值
约定默认值
当未自定义配置,且无法从 IConfiguration 中获取到相对应参数的配置后,我们将根据约定好的规则生成对应的值
配置映射
在快速入门的例子中,看似很简单就可以通过IOptions<TOptions>
获取到AppConfig
的配置信息以及Dcc
中配置的Redis
信息,这一切是如何做到的呢?
在 MasaConfiguration 中提供了两种映射方式,用来映射配置与类的对应关系,分别是:自动映射、手动映射。
自动映射
分为本地配置以及远程配置的自动映射
1.1 当配置存储在本地时,则将对应的配置类继承LocalMasaConfigurationOptions
// <summary>
/// 应用配置类
/// </summary>
public class AppConfig : LocalMasaConfigurationOptions
{
// /// <summary>
// /// 如果当前配置挂载在根节点(一级节点)时,则无需重载,如果挂载在二级节点时,则需要重载ParentSection并赋值为一级节点名
// /// 根节点名:默认为一级节点,可不写,格式:一级节点:二级节点:三级节点……
// /// </summary>
// [JsonIgnore]
// public override string? ParentSection => null;
// /// <summary>
// /// 如果类名与节点名保持一致,则可忽略不写,否则重写`Section`并赋值为节点名
// /// </summary>
// [JsonIgnore]
// public override string? Section => "RabbitMq";
public ConnectionStrings ConnectionStrings { get; set; }
}
public class ConnectionStrings
{
public string DefaultConnection { get; set; }
}
复制代码
当配置中的参数直接平铺挂载根节点下,而不是挂载到跟节点下的某个指定节点时,ParentSection
无需重载,Section
需要重载并赋值为空字符串
1.2 当配置存储在<a href="#Dcc">Dcc</a>,则将对应的配置类继承ConfigurationApiMasaConfigurationOptions
public class RedisOptions : ConfigurationApiMasaConfigurationOptions
{
/// <summary>
/// 配置所属的AppId,当AppId与默认AppId一致时,可忽略
/// </summary>
// public virtual string AppId { get; }
/// <summary>
/// Dcc的配置对象名称,当配置对象名称与类名一致时,可忽略
/// </summary>
// public virtual string? ObjectName { get; }
public string Host { get; set; }
public int Port { get; set; }
public string Password { get; set; }
public int DefaultDatabase { get; set; }
}
复制代码
手动映射
虽然自动映射的方式很简单,也很方便,但总是有一些场景使得我们无法通过自动映射来做,那如何手动指定映射关系呢?
为了方便大家理解,手动映射仍然使用AppConfig
以及Redis
来举例
builder.AddMasaConfiguration(configurationBuilder =>
{
configurationBuilder.UseDcc();//使用Dcc 扩展Configuration能力,支持远程配置
configurationBuilder.UseMasaOptions(options =>
{
options.MappingLocal<AppConfig>("AppConfig");//其中参数"AppConfig"可不写(当类与节点名称一致时可忽略)
options.MappingConfigurationApi<RedisOptions>("{替换为Dcc中配置所属的AppId}", "{配置对象名称}");//其中配置对象名称可不写(当配置对象名与类名一致时可忽略)
});
});
复制代码
Dcc 配置
完整的 Dcc 配置如下:
{
"DccOptions": {
"ManageServiceAddress ": "http://localhost:8890",
"RedisOptions": {
"Servers": [
{
"Host": "localhost",
"Port": 8889
}
],
"DefaultDatabase": 0,
"Password": ""
},
"AppId": "Replace-With-Your-AppId",
"Environment": "Development",
"ConfigObjects": [ "Platforms" ],
"Secret": "",
"Cluster": "Default",
"ExpandSections" : [
{
"AppId": "Replace-With-Your-AppId",
"Environment": "Development",
"ConfigObjects": [ "Platforms" ],
"Secret": "",
"Cluster": "Default",
}
],
"PublicId": "Replace-With-Your-Public-AppId",
"PublicSecret": "Replace-With-Your-Public-AppId-Secret"
}
}
复制代码
ManageServiceAddress: 用于更新远程配置使用
RedisOptions(必填):Dcc 会在 Redis 中存储配置的副本,此处是存储 Dcc 配置的的 Redis 地址
AppId:项目中需要获取配置的 AppId,也被称为 Dcc 的默认 AppId,当未赋值时从全局配置中获取
Environment:项目中需要获取配置的环境信息,当未赋值时从全局配置中获取
ConfigObjects:项目中需要使用的配置对象名称,未赋值时默认获取当前环境、当前集群、当前 AppId 下的全部配置对象
Secret:秘钥,用于更新远程配置,每个 AppId 有一个秘钥,非必填(不可使用更新远程配置的能力)
Cluster:需要加载配置的集群,后面我们简称为 Dcc 的默认集群,未赋值时从全局配置中获取
PublicId:Dcc 中公共配置的 AppId,默认:public-$Config
PublicSecret:Dcc 中公共配置的 AppId 的秘钥
ExpandSections:扩展配置的集合,适用于当前应用需要获取多个 AppId 下的配置时使用,其中 AppId 为必填项、Environment、Cluster 为非必填项,当不存在时将与 Dcc 默认环境、集群一致
扩展其它的配置中心
上面提到了目前的远程配置能力仅支持 Dcc,那如果我希望接入自己开发的配置中心或者其它更优秀的配置中心需要接入如何做?
以Apollo
为例:
新建类库Masa.Contrib.Configuration.ConfigurationApi.Apollo
新建ApolloConfigurationRepository
并实现类AbstractConfigurationRepository
internal class ApolloConfigurationRepository : AbstractConfigurationRepository
{
private readonly IConfigurationApiClient _client;
public override SectionTypes SectionType => SectionTypes.ConfigurationAPI;
public DccConfigurationRepository(
IConfigurationApiClient client,
ILoggerFactory loggerFactory)
: base(loggerFactory)
{
_client = client;
//todo: 借助 IConfigurationApiClient 获取需要挂载到远程节点的配置信息并监听配置变化
// 当配置变更时触发FireRepositoryChange(SectionType, Load());
}
public override Properties Load()
{
//todo: 返回当前挂载到远程节点的配置信息
}
}
复制代码
新建类ConfigurationApiClient
,为ConfigurationApi
提供获取基础配置的能力
public class ConfigurationApiClient : IConfigurationApiClient
{
public Task<(string Raw, ConfigurationTypes ConfigurationType)> GetRawAsync(string configObject, Action<string>? valueChanged = null)
{
throw new NotImplementedException();
}
public Task<(string Raw, ConfigurationTypes ConfigurationType)> GetRawAsync(string environment, string cluster, string appId, string configObject, Action<string>? valueChanged = null)
{
throw new NotImplementedException();
}
public Task<T> GetAsync<T>(string configObject, Action<T>? valueChanged = null);
{
throw new NotImplementedException();
}
public Task<T> GetAsync<T>(string environment, string cluster, string appId, string configObject, Action<T>? valueChanged = null);
{
throw new NotImplementedException();
}
public Task<dynamic> GetDynamicAsync(string environment, string cluster, string appId, string configObject, Action<dynamic> valueChanged)
{
throw new NotImplementedException();
}
public Task<dynamic> GetDynamicAsync(string key)
{
throw new NotImplementedException();
}
}
复制代码
新建类ConfigurationApiManage
,为ConfigurationApi
提供管理配置的能力
public class ConfigurationApiManage : IConfigurationApiManage
{
// 通过管理端初始化AppId下的远程配置
public Task InitializeAsync(string environment, string cluster, string appId, Dictionary<string, string> configObjects)
{
throw new NotImplementedException();
}
// 通过管理端更新指定配置的信息
public Task UpdateAsync(string environment, string cluster, string appId, string configObject, object value)
{
throw new NotImplementedException();
}
}
复制代码
新建ConfigurationApiMasaConfigurationOptions
类,并继承MasaConfigurationOptions
我们希望其它自定义配置也能根据约定实现自动映射,我们也清楚不同的配置中心中存储配置的名称是不一样的,例如在Apollo
中配置对象名称叫做命名空间,因此为了方便开发人员可以使用起来更方便,我们建议不同的配置中心可以有自己专属的属性,比如Apollo
的Namespace
,以此来降低开发人员的学习成本
public abstract class ConfigurationApiMasaConfigurationOptions : MasaConfigurationOptions
{
/// <summary>
/// The name of the parent section, if it is empty, it will be mounted under SectionType, otherwise it will be mounted to the specified section under SectionType
/// </summary>
[JsonIgnore]
public sealed override string? ParentSection => AppId;
//
public virtual string AppId => StaticConfig.AppId;
/// <summary>
/// The section null means same as the class name, else load from the specify section
/// </summary>
[JsonIgnore]
public sealed override string? Section => Namespace;
/// <summary>
///
/// </summary>
public virtual string? Namespace { get; }
/// <summary>
/// Configuration object name
/// </summary>
[JsonIgnore]
public sealed override SectionTypes SectionType => SectionTypes.ConfigurationApi;
}
复制代码
选中类库Masa.Contrib.BasicAbility.Apollo
,并新建 IMasaConfigurationBuilder 的扩展方法UseApollo
public static class MasaConfigurationExtensions
{
public static IMasaConfigurationBuilder UseApollo(this IMasaConfigurationBuilder builder)
{
//todo:将IConfigurationApiClient、IConfigurationApiManage注册到到服务集合中,并通过builder.AddRepository()添加ApolloConfigurationRepository
return builder;
}
}
复制代码
总结
如何使用 MasaConfiguration?
新增:builder.AddMasaConfiguration()
为何通过 IOptions<TOptions>获取到的配置为空,但通过 IConfiguration 或者 IMasaConfiguration 根据节点可以获取到?
检查下是否没有绑定节点关系,如何绑定节点关系请查看问题 2
检查节点绑定是否错误
IConfigurationApiClient
与IConfiguration
之间有什么关系?
IConfigurationApiClient
、IConfigurationApiManage
分别是管理远程 Api 的客户端以及管理端,与IConfiguration
相比,IConfigurationApiClient
的信息更全,每次获取配置需要像配置中心请求获取数据,而IConfiguration
是通过调用IConfigurationApiClient
将需要使用的配置对象获取并添加到IConfiguration
中,后续用户获取配置时无需向配置中心请求数据
远程配置对象更新后,IConfiguration
中的信息会更新吗?为什么?
会更新、远程配置更新后会通过 valueChanged 通知远程配置的提供者,然后远程配置的提供者会刷新本地的远程配置并通知IConfiguration
重新刷新数据
本章源码
Assignment08
https://github.com/zhenlei520/MasaFramework.Practice
开源地址
MASA.Framework:https://github.com/masastack/MASA.Framework
如果你对我们的 MASA Framework 感兴趣,无论是代码贡献、使用、提 Issue,欢迎联系我们
评论