A:增加说明文档
This commit is contained in:
1 parent
b2f113e89f
commit
61e769a0f6
5 files changed
+360
-13
No files matched your search
@@ -17,4 +17,8 @@
|
|||||||
<PackageReference Include="Tomlyn" Version="0.19.0" />
|
<PackageReference Include="Tomlyn" Version="0.19.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -149,20 +149,9 @@ public sealed class ConfigManager
|
|||||||
|
|
||||||
public IList<ValidationResult> ValidateObject(object instance, bool throwOnError)
|
public IList<ValidationResult> ValidateObject(object instance, bool throwOnError)
|
||||||
{
|
{
|
||||||
var context = new ValidationContext(instance);
|
|
||||||
var results = new List<ValidationResult>();
|
var results = new List<ValidationResult>();
|
||||||
// validate top-level
|
var visited = new HashSet<object>(ReferenceEqualityComparer.Instance);
|
||||||
Validator.TryValidateObject(instance, context, results, validateAllProperties: true);
|
RecursiveValidate(instance, results, visited);
|
||||||
|
|
||||||
// validate nested properties with DataAnnotations recursively
|
|
||||||
foreach (var prop in instance.GetType().GetProperties())
|
|
||||||
{
|
|
||||||
var value = prop.GetValue(instance);
|
|
||||||
if (value is null) continue;
|
|
||||||
if (IsSimple(prop.PropertyType)) continue;
|
|
||||||
var nestedContext = new ValidationContext(value);
|
|
||||||
Validator.TryValidateObject(value, nestedContext, results, validateAllProperties: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (throwOnError && results.Count > 0)
|
if (throwOnError && results.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -172,6 +161,39 @@ public sealed class ConfigManager
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void RecursiveValidate(object instance, IList<ValidationResult> results, HashSet<object> visited)
|
||||||
|
{
|
||||||
|
if (instance is null) return;
|
||||||
|
if (!visited.Add(instance)) return;
|
||||||
|
|
||||||
|
var type = instance.GetType();
|
||||||
|
if (IsSimple(type)) return;
|
||||||
|
|
||||||
|
var context = new ValidationContext(instance);
|
||||||
|
Validator.TryValidateObject(instance, context, results, validateAllProperties: true);
|
||||||
|
|
||||||
|
foreach (var prop in type.GetProperties())
|
||||||
|
{
|
||||||
|
var value = prop.GetValue(instance);
|
||||||
|
if (value is null) continue;
|
||||||
|
var pType = value.GetType();
|
||||||
|
if (IsSimple(pType)) continue;
|
||||||
|
|
||||||
|
if (value is System.Collections.IEnumerable enumerable && value is not string)
|
||||||
|
{
|
||||||
|
foreach (var item in enumerable)
|
||||||
|
{
|
||||||
|
if (item is null) continue;
|
||||||
|
RecursiveValidate(item, results, visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RecursiveValidate(value, results, visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsSimple(Type t)
|
private static bool IsSimple(Type t)
|
||||||
{
|
{
|
||||||
t = Nullable.GetUnderlyingType(t) ?? t;
|
t = Nullable.GetUnderlyingType(t) ?? t;
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Config;
|
||||||
|
|
||||||
|
public static class ConfigServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 将 ConfigManager 注册到依赖注入容器中。
|
||||||
|
/// </summary>
|
||||||
|
public static IServiceCollection AddConfig(this IServiceCollection services, Action<ConfigOptions> configure)
|
||||||
|
{
|
||||||
|
services.AddSingleton(provider =>
|
||||||
|
{
|
||||||
|
var options = new ConfigOptions();
|
||||||
|
configure(options);
|
||||||
|
return ConfigManager.Build(options);
|
||||||
|
});
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
# Config 模块
|
||||||
|
|
||||||
|
面向 .NET 的通用配置模块,聚合多种配置源(JSON / INI / XML / TOML / 环境变量),支持强类型绑定、数据注解验证、可选热更新、文件读写序列化,并提供对外扩展 Hook 以便与外部公共库(如 Common 模块)集成。
|
||||||
|
|
||||||
|
本模块仅包含在 `src/Sharp/Config` 目录内的实现,遵从“公共项目,可插拔”的目标:你可以直接使用,也可通过依赖注入或 Hook 接入整体项目体系。
|
||||||
|
|
||||||
|
## 功能清单
|
||||||
|
|
||||||
|
- 多格式配置读取:
|
||||||
|
- JSON / XML:使用 Microsoft 官方 provider,并支持 `ReloadOnChange`
|
||||||
|
- INI / TOML:内置轻量解析,注入为内存配置源(当前默认不启用文件级热更新)
|
||||||
|
- 多格式配置写入:支持将强类型对象写入为 JSON / XML / INI / TOML 文件
|
||||||
|
- 强类型绑定:`Get<T>()` / `TryGet<T>()` 直接从 IConfiguration 绑定到 POCO
|
||||||
|
- 数据注解验证:`[Required]` / `[Range]` 等;支持递归验证嵌套对象与集合
|
||||||
|
- 环境变量合并:可设置前缀,合并到配置树(优先级最高)
|
||||||
|
- 热更新(可选):JSON / XML 支持文件监控自动重载;回调 `OnReload`
|
||||||
|
- 可扩展:
|
||||||
|
- `ConfigOptions.ConfigureBuilderHook`:注入外部对 `IConfigurationBuilder` 的自定义构建(可与 Common 模块对接)
|
||||||
|
- `AddConfig(IServiceCollection, ...)`:便捷注册到 DI 容器
|
||||||
|
|
||||||
|
## 架构与设计
|
||||||
|
|
||||||
|
模块核心集中在以下类:
|
||||||
|
|
||||||
|
- `ConfigManager`:构建配置、获取/验证、保存、热更新回调
|
||||||
|
- `ConfigOptions`:配置构建选项(BasePath、Sources、ReloadOnChange、Env 前缀、Hook 等)
|
||||||
|
- `ConfigSource`:单个配置源描述(路径、格式、是否可选)
|
||||||
|
- `ConfigBuilder`:便捷构建器(从文件列表快速构建)
|
||||||
|
- 序列化器:
|
||||||
|
- `Serialization/JsonConfigSerializer`
|
||||||
|
- `Serialization/XmlConfigSerializer`
|
||||||
|
- `Serialization/IniConfigSerializer`
|
||||||
|
- `Serialization/TomlConfigSerializer`
|
||||||
|
|
||||||
|
注意:当前 INI / TOML 的读取在构建期解析为键值对注入,不启用文件级热更新;JSON / XML 使用官方 provider,支持 `ReloadOnChange`。
|
||||||
|
|
||||||
|
## 使用方式
|
||||||
|
|
||||||
|
### 1) 从文件构建(常见)
|
||||||
|
|
||||||
|
```
|
||||||
|
var cm = Config.ConfigBuilder.BuildFromFiles(
|
||||||
|
files: new[] { "appsettings.json", "settings.ini", "extra.xml", "feature.toml" },
|
||||||
|
reloadOnChange: true, // JSON/XML 支持
|
||||||
|
includeEnvironment: true, // 合并环境变量
|
||||||
|
environmentPrefix: "APP_", // 推荐:无双下划线的短前缀,如 "APP"
|
||||||
|
basePath: AppContext.BaseDirectory // 或自定义目录
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
环境变量键:使用双下划线分隔层级,例如 `APP__Database__Host`。如果设置了 `environmentPrefix: "APP_"`,也兼容 `APP__Name` 这类书写,但推荐前缀不以 `_` 结尾,例如 `"APP"`。
|
||||||
|
|
||||||
|
### 2) 强类型绑定与验证
|
||||||
|
|
||||||
|
```
|
||||||
|
public class AppSettings
|
||||||
|
{
|
||||||
|
public string? Name { get; set; }
|
||||||
|
[Range(1,65535)] public int Port { get; set; }
|
||||||
|
[Required] public string? RequiredField { get; set; }
|
||||||
|
public DbSettings Database { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DbSettings { [Required] public string? Host { get; set; } }
|
||||||
|
|
||||||
|
var settings = cm.Get<AppSettings>(validate: true);
|
||||||
|
```
|
||||||
|
|
||||||
|
验证失败会抛出 `ConfigurationValidationException`,异常中包含 `Errors` 详细列表。
|
||||||
|
|
||||||
|
### 3) 序列化写入与直接读取文件
|
||||||
|
|
||||||
|
```
|
||||||
|
// 保存为 JSON / XML / INI / TOML
|
||||||
|
cm.Save(settings, "out/appsettings.json", ConfigFormat.Json);
|
||||||
|
cm.Save(settings, "out/extra.xml", ConfigFormat.Xml);
|
||||||
|
cm.Save(settings, "out/settings.ini", ConfigFormat.Ini);
|
||||||
|
cm.Save(settings, "out/feature.toml", ConfigFormat.Toml);
|
||||||
|
|
||||||
|
// 从文件读取到强类型对象(JSON/XML/INI 支持;TOML 建议用配置管线绑定)
|
||||||
|
var a1 = cm.LoadFromFile<AppSettings>("out/appsettings.json", ConfigFormat.Json);
|
||||||
|
var a2 = cm.LoadFromFile<AppSettings>("out/extra.xml", ConfigFormat.Xml);
|
||||||
|
var a3 = cm.LoadFromFile<AppSettings>("out/settings.ini", ConfigFormat.Ini);
|
||||||
|
|
||||||
|
// TOML:直接泛型反序列化暂不支持(抛 NotSupportedException)
|
||||||
|
// 建议:通过 AddTomlFile(若集成第三方)或当前内置的 TOML 解析+绑定路径获取强类型
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4) 环境变量合并
|
||||||
|
|
||||||
|
```
|
||||||
|
var cm = Config.ConfigBuilder.BuildFromFiles(
|
||||||
|
new[] { "appsettings.json" },
|
||||||
|
includeEnvironment: true,
|
||||||
|
environmentPrefix: "APP" // 推荐不带下划线
|
||||||
|
);
|
||||||
|
|
||||||
|
// Windows / Linux 环境变量示例:
|
||||||
|
// APP__Name=fromEnv
|
||||||
|
// APP__Database__Host=envHost
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5) 热更新与回调
|
||||||
|
|
||||||
|
```
|
||||||
|
// 仅 JSON / XML 文件通过官方 provider 支持重载
|
||||||
|
using var sub = cm.OnReload(() =>
|
||||||
|
{
|
||||||
|
var newSettings = cm.Get<AppSettings>(validate: true);
|
||||||
|
// TODO: 应用变更
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
如需对 INI / TOML 也启用热更新,可在后续迭代提供自定义 `FileConfigurationProvider` 实现(欢迎按需添加)。
|
||||||
|
|
||||||
|
### 6) Hook 扩展(对接 Common 模块)
|
||||||
|
|
||||||
|
```
|
||||||
|
var cm = Config.ConfigBuilder.Build(opt =>
|
||||||
|
{
|
||||||
|
opt.BasePath = AppContext.BaseDirectory;
|
||||||
|
opt.Sources.Add(new ConfigSource { Path = "appsettings.json", Format = ConfigFormat.Json, Optional = true });
|
||||||
|
opt.ConfigureBuilderHook = builder =>
|
||||||
|
{
|
||||||
|
// 在此对 IConfigurationBuilder 做进一步处理
|
||||||
|
// 例如:调用 Common 模块的扩展方法
|
||||||
|
// builder = Common.ConfigExtensions.AddCommonSources(builder);
|
||||||
|
return builder;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7) 依赖注入
|
||||||
|
|
||||||
|
```
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddConfig(opt =>
|
||||||
|
{
|
||||||
|
opt.BasePath = AppContext.BaseDirectory;
|
||||||
|
opt.Sources.Add(new ConfigSource { Path = "appsettings.json", Format = ConfigFormat.Json });
|
||||||
|
});
|
||||||
|
|
||||||
|
var provider = services.BuildServiceProvider();
|
||||||
|
var cm = provider.GetRequiredService<ConfigManager>();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 重要行为与限制
|
||||||
|
|
||||||
|
- 配置源合并策略:后添加覆盖先添加(与官方一致)
|
||||||
|
- INI 读取:
|
||||||
|
- 将“根键(未在任何节中)”与“节内键”一起展平成键路径
|
||||||
|
- 当前默认行为:将根键复制到所有已声明的一级节(若该节未定义同名键)。此举旨在方便简化配置,但可能与部分 INI 规范理解有差异;如需关闭或自定义,可在后续版本提供开关
|
||||||
|
- INI 写入:支持简单类型与一层嵌套对象
|
||||||
|
- TOML 读取:
|
||||||
|
- 解析为键值对注入,默认不启用文件级热更新
|
||||||
|
- 直接 `LoadFromFile<T>(..., Toml)` 暂不支持;请通过配置管线绑定获取强类型
|
||||||
|
- 环境变量前缀:
|
||||||
|
- 推荐使用不带下划线的短前缀(如 `APP`)+ 双下划线分隔层级(`APP__Section__Key`)
|
||||||
|
- 模块对 `"APP_"` 这类带下划线结尾的前缀做了兼容处理,但仍建议遵循推荐写法
|
||||||
|
- 验证:
|
||||||
|
- 使用 DataAnnotations,递归验证嵌套对象与集合
|
||||||
|
- 性能:
|
||||||
|
- INI/TOML 读取为一次性解析(非流式);适合小中等规模配置。大型配置建议 JSON/XML
|
||||||
|
|
||||||
|
## 常见问题(FAQ)
|
||||||
|
|
||||||
|
1. 为什么 INI/TOML 没有热更新?
|
||||||
|
- 当前实现为轻量解析注入内存,未挂载文件监控。JSON/XML 已可直接使用热更新。若需要对 INI/TOML 启用热更新,可按 `FileConfigurationProvider` 模式扩展(可在后续版本提供)。
|
||||||
|
|
||||||
|
2. 环境变量用法推荐?
|
||||||
|
- 使用 `environmentPrefix: "APP"`,并设置 `APP__Section__Key`。尽量避免在前缀尾部加 `_`,可减少歧义。
|
||||||
|
|
||||||
|
3. TOML 的写入后如何强类型读取?
|
||||||
|
- 通过配置构建(Add... + 绑定)获取强类型对象;`LoadFromFile<T>` 暂不支持 TOML 直接反序列化。
|
||||||
|
|
||||||
|
4. 能否与 Common 模块集成?
|
||||||
|
- 可以,通过 `ConfigOptions.ConfigureBuilderHook` 把 Common 的扩展注入到构建管线。
|
||||||
|
|
||||||
|
## 变更建议与后续计划
|
||||||
|
|
||||||
|
- 提供选项开关以切换 INI 映射策略(是否复制根键到各节)
|
||||||
|
- 为 INI/TOML 增加自定义 `FileConfigurationProvider`,支持 `ReloadOnChange`
|
||||||
|
- 增强序列化器的集合/字典类型支持(当前对 INI 做了简化)
|
||||||
|
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
# 使用指南(Config 模块)
|
||||||
|
|
||||||
|
本文档以简洁示例展示常见用法,配合 README.md 的说明一起参考。
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
```
|
||||||
|
using Config;
|
||||||
|
|
||||||
|
var cm = ConfigBuilder.BuildFromFiles(
|
||||||
|
new[] { "appsettings.json", "settings.ini", "extra.xml", "feature.toml" },
|
||||||
|
reloadOnChange: true,
|
||||||
|
includeEnvironment: true,
|
||||||
|
environmentPrefix: "APP",
|
||||||
|
basePath: AppContext.BaseDirectory
|
||||||
|
);
|
||||||
|
|
||||||
|
var settings = cm.Get<AppSettings>(validate: true);
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
public class AppSettings
|
||||||
|
{
|
||||||
|
public string? Name { get; set; }
|
||||||
|
[Range(1,65535)] public int Port { get; set; }
|
||||||
|
[Required] public string? RequiredField { get; set; }
|
||||||
|
public DbSettings Database { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DbSettings
|
||||||
|
{
|
||||||
|
[Required] public string? Host { get; set; }
|
||||||
|
public string? User { get; set; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 序列化写入与读取
|
||||||
|
|
||||||
|
```
|
||||||
|
// 写入
|
||||||
|
cm.Save(settings, "out/appsettings.json", ConfigFormat.Json);
|
||||||
|
cm.Save(settings, "out/extra.xml", ConfigFormat.Xml);
|
||||||
|
cm.Save(settings, "out/settings.ini", ConfigFormat.Ini);
|
||||||
|
cm.Save(settings, "out/feature.toml", ConfigFormat.Toml);
|
||||||
|
|
||||||
|
// 读取(JSON/XML/INI)
|
||||||
|
var fromJson = cm.LoadFromFile<AppSettings>("out/appsettings.json", ConfigFormat.Json);
|
||||||
|
var fromXml = cm.LoadFromFile<AppSettings>("out/extra.xml", ConfigFormat.Xml);
|
||||||
|
var fromIni = cm.LoadFromFile<AppSettings>("out/settings.ini", ConfigFormat.Ini);
|
||||||
|
|
||||||
|
// TOML:使用配置构建 + 绑定
|
||||||
|
var cmToml = ConfigBuilder.BuildFromFiles(new[] { "out/feature.toml" }, basePath: AppContext.BaseDirectory);
|
||||||
|
var fromToml = cmToml.Get<AppSettings>();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 环境变量
|
||||||
|
|
||||||
|
```
|
||||||
|
var cm = ConfigBuilder.BuildFromFiles(new[] { "appsettings.json" }, includeEnvironment: true, environmentPrefix: "APP");
|
||||||
|
// 设置环境变量(Windows 或 Linux):
|
||||||
|
// APP__Name=fromEnv
|
||||||
|
// APP__Database__Host=envHost
|
||||||
|
```
|
||||||
|
|
||||||
|
## 热更新回调(JSON/XML)
|
||||||
|
|
||||||
|
```
|
||||||
|
using var sub = cm.OnReload(() =>
|
||||||
|
{
|
||||||
|
var s = cm.Get<AppSettings>(validate: true);
|
||||||
|
Console.WriteLine($"Reloaded: {s.Name} : {s.Port}");
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 与 Common 模块集成(通过 Hook)
|
||||||
|
|
||||||
|
```
|
||||||
|
var cm = ConfigBuilder.Build(opt =>
|
||||||
|
{
|
||||||
|
opt.BasePath = AppContext.BaseDirectory;
|
||||||
|
opt.Sources.Add(new ConfigSource { Path = "appsettings.json", Format = ConfigFormat.Json });
|
||||||
|
|
||||||
|
opt.ConfigureBuilderHook = builder =>
|
||||||
|
{
|
||||||
|
// builder = Common.ConfigExtensions.AddCommonSources(builder);
|
||||||
|
return builder;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 依赖注入
|
||||||
|
|
||||||
|
```
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Config;
|
||||||
|
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddConfig(opt =>
|
||||||
|
{
|
||||||
|
opt.BasePath = AppContext.BaseDirectory;
|
||||||
|
opt.Sources.Add(new ConfigSource { Path = "appsettings.json", Format = ConfigFormat.Json });
|
||||||
|
});
|
||||||
|
|
||||||
|
var sp = services.BuildServiceProvider();
|
||||||
|
var cm = sp.GetRequiredService<ConfigManager>();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- 合并策略:后添加覆盖先添加
|
||||||
|
- INI 映射默认会将根键复制到一级节(若该节没定义同名键),有助于简化配置;如不符合你的期望,可在后续版本通过选项开关关闭
|
||||||
|
- TOML 暂不直接 `LoadFromFile<T>`,推荐通过配置管线 + 绑定
|
||||||
|
- JSON/XML 支持 `ReloadOnChange`;INI/TOML 默认不启用文件级热更新
|
||||||
|
|
||||||
Reference in new issue
Block a user