- IConfigurationBuilder
- IConfigurationSource
- IConfigurationProvider
- ConfigurationBuilder
- ConfigurationProvider
- IChangeToken
- ConfigurationReloadToken
- MemoryConfigurationSource
- MemoryConfigurationBuilderExtensions
- MemoryConfigurationProvider
- FileConfigurationSource
- XmlConfigurationSource
- JsonConfigurationSource
- IniConfigurationSource
- FileConfigurationExtensions
- IFileProvider
- JsonConfigurationExtensions
- XmlConfigurationExtensions
- IniConfigurationExtensions
- ConfigurationExtensions
- FileConfigurationProvider
- ChangeToken
- ChangeTokenRegistration<>
- CommandLineConfigurationSource
- CommandLineConfigurationExtensions
- EnvironmentVariablesConfigurationSource
- EnvironmentVariablesExtensions
- ChainedConfigurationSource
- ChainedConfigurationProvider
- ChainedBuilderExtensions
- IConfiguration
- IConfigurationRoot
- IConfigurationSection
- ConfigurationRoot
- ConfigurationSection
- InternalConfigurationRootExtensions
- ConfigurationBinder
- BinderOptions
- BindingPoint
// 配置建造者的抽象表示
public interface IConfigurationBuilder
{
IDictionary<string, object> Properties { get; }
IList<IConfigurationSource> Sources { get; }
IConfigurationBuilder Add(IConfigurationSource source);
IConfigurationRoot Build();
}
// 不同配置源的抽象表示
public interface IConfigurationSource
{
// 每个配置源最终会构建出对应的 IConfigurationProvider 配置字典提供者
IConfigurationProvider Build(IConfigurationBuilder builder);
}
// 配置字典提供者的抽象表示
public interface IConfigurationProvider
{
// 尝试从配置字典得到值
bool TryGet(string key, out string? value);
// 设置配置字典的值
void Set(string key, string? value);
// 得到改变令牌
IChangeToken GetReloadToken();
// 加载配置源并转换为配置字典
void Load();
// 从配置字典中提取出所有的子 key(不包含 parentPath)
IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string? parentPath);
}
// IConfigurationBuilder 的默认实现
public class ConfigurationBuilder : IConfigurationBuilder
{
// 不同配置源的集合
public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>();
// 在通过 IConfigurationSource 配置源构建对应的 IConfigurationProvider 配置提供者的过程中
// 可能需要通过这组提供的共享的数据帮助构建
public IDictionary<string, object> Properties { get; } = new Dictionary<string, object>();
// 收集配置源
public IConfigurationBuilder Add(IConfigurationSource source)
{
ThrowHelper.ThrowIfNull(source);
Sources.Add(source);
return this;
}
// 根据不同的 IConfigurationSource 配置源得到对应的 IConfigurationProvider 配置提供者,最终利用配置提供者集合构建 IConfigurationRoot 配置根
public IConfigurationRoot Build()
{
var providers = new List<IConfigurationProvider>();
foreach (IConfigurationSource source in Sources)
{
IConfigurationProvider provider = source.Build(this);
providers.Add(provider);
}
return new ConfigurationRoot(providers);
}
}
// 抽象配置提供者
public abstract class ConfigurationProvider : IConfigurationProvider
{
// 配置改变令牌
private ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken();
protected ConfigurationProvider()
{
// 初始化配置字典(配置键忽略大小写)
Data = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
}
// 配置字典
// 配置键是以 “:” 分段的字符串
// 字典中每一条记录的配置键都表示从根出发到叶配置节的路径化字符串(绝对路径)
protected IDictionary<string, string?> Data { get; set; }
// 尝试通过配置键获取配置值
public virtual bool TryGet(string key, out string? value)
=> Data.TryGetValue(key, out value);
// 设置配置键对应的值
public virtual void Set(string key, string? value)
=> Data[key] = value;
// 加载配置源并转换为配置字典
// 由具体实现类型重写
// 注意:在创建配置字典时,字典的值不会为 null,如果是 null 则会被转换为空字符串
// 目的是只要键存在,则通过 TryGet 方法得到的值不会为 null
// 这样可以保证只要 IConfigurationSection.Value 属性值为 null,则肯定不存在对应的 path 路径,即叶配置节不存在
public virtual void Load() { }
// 得到父路径下的所有子路径配置键
// earlierKeys 代表通过累加器从其他 IConfigurationProvider 中收集到的配置键
public virtual IEnumerable<string> GetChildKeys(
IEnumerable<string> earlierKeys,
string? parentPath)
{
var results = new List<string>();
if (parentPath is null)
{
// 如果是从根开始找
foreach (KeyValuePair<string, string?> kv in Data)
{
// 查找起始位置为 0
results.Add(Segment(kv.Key, 0));
}
}
else
{
Debug.Assert(ConfigurationPath.KeyDelimiter == ":");
foreach (KeyValuePair<string, string?> kv in Data)
{
// 配置键长度大于父路径长度并且配置键以父路径开头(忽略大小写)并且配置键字符串在以父路径长度的索引位置的字符是 “:”
if (kv.Key.Length > parentPath.Length &&
kv.Key.StartsWith(parentPath, StringComparison.OrdinalIgnoreCase) &&
kv.Key[parentPath.Length] == ':')
{
results.Add(Segment(kv.Key, parentPath.Length + 1));
}
}
}
// 将先收集的子路径段添加到末尾
results.AddRange(earlierKeys);
// 按字符编码升序排序
results.Sort(ConfigurationKeyComparer.Comparison);
return results;
}
// 得到子路径段
// 从 prefixLength 父路径长度以后开始
private static string Segment(string key, int prefixLength)
{
// 得到 prefixLength 长度以后的第一个 “:” 索引位置(忽略大小写)
int indexOf = key.IndexOf(ConfigurationPath.KeyDelimiter, prefixLength, StringComparison.OrdinalIgnoreCase);
// 如果没有找到 “:” 返回从 prefixLength 截取到末尾的字符串
// 如果找到 “:” 返回从 prefixLength 开始,长度为 indexOf - prefixLength 的字符串
return indexOf < 0 ? key.Substring(prefixLength) : key.Substring(prefixLength, indexOf - prefixLength);
}
// 获取配置改变令牌
// 供外部调用,监听改变通知,触发回调
public IChangeToken GetReloadToken()
{
return _reloadToken;
}
// 如果是文件配置源,则会利用其引用的 IFileProvider 对象,调用 IFileProvider.Watch 方法返回的 IChangeToken 注册 OnReload 方法,以此来监听文件改动
// 配置源发生改变后,首先创建一个新的配置改变令牌并交换出前一个配置改变令牌
// 最终利用交换出的前一个配置改变令牌触发注册在其上的回调
// 新的配置改变令牌将通过 GetReloadToken 方法供外部重新注册
protected void OnReload()
{
// 配置源发生改变,则通过原子操作创建新的 ConfigurationReloadToken 令牌并替换 _reloadToken 的值
// 返回 _reloadToken 中原来的 ConfigurationReloadToken
ConfigurationReloadToken previousToken = Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken());
// 使用旧的 ConfigurationReloadToken 调用 OnReload 方法触发回调
previousToken.OnReload();
}
// 返回实际的 IConfigurationProvider 配置提供者的类型名称
// 此处为什么要重写 ToString 方法?
// 从 System.Object 继承的 ToString 方法实现就是返回实际的类型名称(通过实例的类型对象指针所指向的 MethodTable)
public override string ToString() => $"{GetType().Name}";
}
// 改变令牌的抽象表示
public interface IChangeToken
{
bool HasChanged { get; }
bool ActiveChangeCallbacks { get; }
IDisposable RegisterChangeCallback(Action<object?> callback, object? state);
}
// 配置改变令牌
public class ConfigurationReloadToken : IChangeToken
{
// 取消令牌源
// 早期是为取消异步任务设计的
private CancellationTokenSource _cts = new CancellationTokenSource();
// 允许触发改变回调
public bool ActiveChangeCallbacks => true;
// 取消令牌源
public bool HasChanged => _cts.IsCancellationRequested;
// 注册配置改变后的回调
// 使用 CancellationToken.Register 方法注册
public IDisposable RegisterChangeCallback(Action<object?> callback, object? state) => _cts.Token.Register(callback, state);
// 调用 CancellationTokenSource.Cancel 方法触发注册的回调
public void OnReload() => _cts.Cancel();
}
- MemoryConfigurationSource
// 基于内存的配置源
public class MemoryConfigurationSource : IConfigurationSource
{
// 原始配置源
// 最终这个原始配置源会在 MemoryConfigurationProvider 的构造函数中完成配置数据到配置字典的转移
// 所以最终 MemoryConfigurationProvider 也就不需要重写 Load 方法
public IEnumerable<KeyValuePair<string, string?>>? InitialData { get; set; }
// 构建对应的 IConfigurationProvider 配置提供者
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new MemoryConfigurationProvider(this);
}
}
- MemoryConfigurationBuilderExtensions
// 添加内存配置源的扩展方法
public static class MemoryConfigurationBuilderExtensions
{
// 添加一个空配置源
public static IConfigurationBuilder AddInMemoryCollection(this IConfigurationBuilder configurationBuilder)
{
ThrowHelper.ThrowIfNull(configurationBuilder);
// 收集配置源
configurationBuilder.Add(new MemoryConfigurationSource());
return configurationBuilder;
}
// 添加 initialData 承载的配置源
public static IConfigurationBuilder AddInMemoryCollection(
this IConfigurationBuilder configurationBuilder,
IEnumerable<KeyValuePair<string, string?>>? initialData)
{
ThrowHelper.ThrowIfNull(configurationBuilder);
// 收集配置源
configurationBuilder.Add(new MemoryConfigurationSource { InitialData = initialData });
return configurationBuilder;
}
}
- MemoryConfigurationProvider
// 使用 MemoryConfigurationSource 构建的内存配置提供者
public class MemoryConfigurationProvider : ConfigurationProvider, IEnumerable<KeyValuePair<string, string?>>
{
private readonly MemoryConfigurationSource _source;
public MemoryConfigurationProvider(MemoryConfigurationSource source)
{
ThrowHelper.ThrowIfNull(source);
_source = source;
if (_source.InitialData != null)
{
// 将原始 InitialData 配置数据转移到配置字典
foreach (KeyValuePair<string, string?> pair in _source.InitialData)
{
Data.Add(pair.Key, pair.Value);
}
}
}
// 添加配置
public void Add(string key, string? value)
{
Data.Add(key, value);
}
// 实现 IEnumerable<KeyValuePair<string, string?>>
public IEnumerator<KeyValuePair<string, string?>> GetEnumerator()
{
return Data.GetEnumerator();
}
// 实现 IEnumerable
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
// 抽象文件配置源
// 实现类型:
// 1. XmlConfigurationSource
// 2. JsonConfigurationSource
// 3. IniConfigurationSource
// 实现类型都只是重写 Build 方法,并在内部调用 EnsureDefaults 方法后创建对应的 IConfigurationProvider
public abstract class FileConfigurationSource : IConfigurationSource
{
// IFileProvider 文件提供者
public IFileProvider? FileProvider { get; set; }
// 包含相对路径的文件名(包含后缀名)
public string? Path { get; set; }
// 文件是否必须存在
public bool Optional { get; set; }
// 是否需要监控文件修改变动
public bool ReloadOnChange { get; set; }
// 文件修改变动后的延迟触发(等待文件 Flush 过程中,写入磁盘可能有一定延迟)
// 单位毫秒
public int ReloadDelay { get; set; } = 250;
// 在构建对应的 IConfigurationProvider 过程中,调用 FileConfigurationProvider.Load 方法读取文件内容加载配置字典的过程中可能发生问题
public Action<FileLoadExceptionContext>? OnLoadException { get; set; }
// 抽象方法
// 由子类重写,构建对应的 IConfigurationProvider
public abstract IConfigurationProvider Build(IConfigurationBuilder builder);
// 确定 IFileProvider 已经被创建
// 如果在创建 FileConfigurationSource 子类的时候,调用 ResolveFileProvider 方法没有成功创建出 IFileProvider
// 就会通过调用 GetFileProvider 扩展方法,创建应用程序默认的 IFileProvider(可能来源于共享数据,也可能利用应用程序根目录创建)
// 一般都在子类重写的 Build 方法中调用
public void EnsureDefaults(IConfigurationBuilder builder)
{
FileProvider ??= builder.GetFileProvider();
OnLoadException ??= builder.GetFileLoadExceptionHandler();
}
// 根据绝对路径创建 IFileProvider
// 如果 Path 是相对路径,则最终会通过调用 EnsureDefaults 方法创建默认的 IFileProvider 并作为共享数据,并存储在 IConfigurationBuilder.Properties 中
public void ResolveFileProvider()
{
// 如果 IFileProvider 未创建,并且路径是绝对路径
if (FileProvider == null &&
!string.IsNullOrEmpty(Path) &&
System.IO.Path.IsPathRooted(Path))
{
// 得到目录路径,返回路径中最后的 "\" 之前的内容作为目录路径
string? directory = System.IO.Path.GetDirectoryName(Path);
// 不论文件或目录,返回路径中最后的 "\" 后面的内容作为文件名(包含后缀名)
string? pathToFile = System.IO.Path.GetFileName(Path);
// 如果目录路径不存在
while (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
// 通过 GetFileName 方法得到的文件名作为目录名 + 原来的文件名(包含后缀名)的相对路径
pathToFile = System.IO.Path.Combine(System.IO.Path.GetFileName(directory), pathToFile);
// 通过 GetDirectoryName 方法得到原有路径对应目录的父目录路径
directory = System.IO.Path.GetDirectoryName(directory);
}
if (Directory.Exists(directory))
{
// 使用目录路径创建 PhysicalFileProvider
FileProvider = new PhysicalFileProvider(directory);
// 实际用作 IFileProvider.GetFileInfo 方法的 subPath
Path = pathToFile;
}
}
}
}
- FileConfigurationExtensions
// 设置 IConfigurationBuilder 中 Properties 共享数据的扩展方法
public static class FileConfigurationExtensions
{
private static string FileProviderKey = "FileProvider";
private static string FileLoadExceptionHandlerKey = "FileLoadExceptionHandler";
// 通过外部 IFileProvider 设置共享数据
public static IConfigurationBuilder SetFileProvider(this IConfigurationBuilder builder, IFileProvider fileProvider)
{
ThrowHelper.ThrowIfNull(builder);
ThrowHelper.ThrowIfNull(fileProvider);
builder.Properties[FileProviderKey] = fileProvider;
return builder;
}
// 得到 IFileProvider
// 优先从共享数据获取 IFileProvider 返回
// 如果共享数据没有 IFileProvider,则使用应用程序根目录创建 IFileProvider
public static IFileProvider GetFileProvider(this IConfigurationBuilder builder)
{
ThrowHelper.ThrowIfNull(builder);
if (builder.Properties.TryGetValue(FileProviderKey, out object? provider))
{
return (IFileProvider)provider;
}
return new PhysicalFileProvider(AppContext.BaseDirectory ?? string.Empty);
}
// 使用 basePath 创建 IFileProvider 设置共享数据
public static IConfigurationBuilder SetBasePath(this IConfigurationBuilder builder, string basePath)
{
ThrowHelper.ThrowIfNull(builder);
ThrowHelper.ThrowIfNull(basePath);
return builder.SetFileProvider(new PhysicalFileProvider(basePath));
}
// 设置创建 IConfigurationProvider 时调用 FileConfigurationProvider.Load 方法发生异常时的处理程序
public static IConfigurationBuilder SetFileLoadExceptionHandler(this IConfigurationBuilder builder, Action<FileLoadExceptionContext> handler)
{
ThrowHelper.ThrowIfNull(builder);
builder.Properties[FileLoadExceptionHandlerKey] = handler;
return builder;
}
// 返回异常处理程序
public static Action<FileLoadExceptionContext>? GetFileLoadExceptionHandler(this IConfigurationBuilder builder)
{
ThrowHelper.ThrowIfNull(builder);
if (builder.Properties.TryGetValue(FileLoadExceptionHandlerKey, out object? handler))
{
return handler as Action<FileLoadExceptionContext>;
}
return null;
}
}
// 文件提供者的抽象表示
public interface IFileProvider
{
// 得到子路径下对应的文件
IFileInfo GetFileInfo(string subpath);
// 得到子路径下的目录内容
IDirectoryContents GetDirectoryContents(string subpath);
// 利用过滤规则监控文件改变
IChangeToken Watch(string filter);
}
- JsonConfigurationExtensions
// 添加 Json 文件配置源的扩展方法
public static class JsonConfigurationExtensions
{
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, IFileProvider? provider, string path, bool optional, bool reloadOnChange)
{
ThrowHelper.ThrowIfNull(builder);
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException(SR.Error_InvalidFilePath, nameof(path));
}
return builder.AddJsonFile(s =>
{
s.FileProvider = provider;
s.Path = path;
s.Optional = optional;
s.ReloadOnChange = reloadOnChange;
s.ResolveFileProvider();
});
}
// 不启用文件变动监控并且文件路径必须存在对应的文件(非可选)
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path)
{
return AddJsonFile(builder, provider: null, path: path, optional: false, reloadOnChange: false);
}
// 不启用文件监控
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path, bool optional)
{
return AddJsonFile(builder, provider: null, path: path, optional: optional, reloadOnChange: false);
}
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange)
{
return AddJsonFile(builder, provider: null, path: path, optional: optional, reloadOnChange: reloadOnChange);
}
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Action<JsonConfigurationSource>? configureSource)
=> builder.Add(configureSource);
public static IConfigurationBuilder AddJsonStream(this IConfigurationBuilder builder, Stream stream)
{
ThrowHelper.ThrowIfNull(builder);
return builder.Add<JsonStreamConfigurationSource>(s => s.Stream = stream);
}
}
- XmlConfigurationExtensions
// 添加 Xml 文件配置源的扩展方法
public static class XmlConfigurationExtensions
{
// 实现同 JsonConfigurationExtensions 类似
}
- IniConfigurationExtensions
// 添加 Ini 文件配置源的扩展方法
public static class IniConfigurationExtensions
{
// 实现同 JsonConfigurationExtensions 类似
}
// 配置相关的扩展方法
public static class ConfigurationExtensions
{
// 根据配置委托配置不同的 IConfigurationSource
public static IConfigurationBuilder Add<TSource>(this IConfigurationBuilder builder, Action<TSource>? configureSource)
where TSource : IConfigurationSource, new()
{
var source = new TSource();
configureSource?.Invoke(source);
return builder.Add(source);
}
// 得到 ConnectionStrings 配置节下 name 表示的子配置节(叶配置节)的值
public static string? GetConnectionString(this IConfiguration configuration, string name)
{
return configuration?.GetSection("ConnectionStrings")[name];
}
// 判断配置节是否存在
// 因为创建的 IConfigurationSection 可能根本不存在对应的路径
public static bool Exists(this IConfigurationSection? section)
{
if (section == null)
{
return false;
}
// 是叶配置节或配置节存在子配置的情况返回 true
return section.Value != null || section.GetChildren().Any();
}
// 得到必须的配置节
public static IConfigurationSection GetRequiredSection(this IConfiguration configuration, string key)
{
ThrowHelper.ThrowIfNull(configuration);
IConfigurationSection section = configuration.GetSection(key);
// 使用 Exists 方法判断配置节是否存在
if (section.Exists())
{
return section;
}
// 不存在,则抛出 InvalidOperationException
throw new InvalidOperationException(SR.Format(SR.InvalidSectionName, key));
}
// 枚举所有配置节
// makePathsRelative 参数:
// 1. 如果是 true,则从当前配置节之后枚举所有路径对应的配置节(使用相对路径)
// 2. 如果是 false,则从当前配置节(包含)枚举所有路径对应的配置节(使用绝对路径)
public static IEnumerable<KeyValuePair<string, string?>> AsEnumerable(this IConfiguration configuration, bool makePathsRelative);
}
- FileConfigurationProvider
// 抽象文件配置提供者
// 实现类型:
// 1. XmlConfigurationProvider
// 2. JsonConfigurationProvider
// 3. IniConfigurationProvider
public abstract class FileConfigurationProvider : ConfigurationProvider, IDisposable
{
// 可以通过调用 IDisposable.Dispose 方法取消注册配置改变回调
private readonly IDisposable? _changeTokenRegistration;
public FileConfigurationProvider(FileConfigurationSource source)
{
ThrowHelper.ThrowIfNull(source);
Source = source;
// 启用配置源改变监控并且存在 IFileProvider
if (Source.ReloadOnChange && Source.FileProvider != null)
{
// 监控配置源文件改变
_changeTokenRegistration = ChangeToken.OnChange(
() => Source.FileProvider.Watch(Source.Path!),
() =>
{
// 回调触发后阻塞线程一段时间
// 等待文件 Flush 过程中,写入磁盘可能有一定延迟
Thread.Sleep(Source.ReloadDelay);
// 重新加载配置源
Load(reload: true);
});
}
}
// 对应的文件配置源
public FileConfigurationSource Source { get; }
private void Load(bool reload)
{
// 得到代表文件信息的 IFileInfo
// 只有在 FileProvider 为空时才可能 file == null
// 其他任何情况都会得到一个 IFileInfo,物理文件是否存在通过 IFileInfo.Exist 方法判断
IFileInfo? file = Source.FileProvider?.GetFileInfo(Source.Path ?? string.Empty);
if (file == null || !file.Exists)
{
// 文件不存在
if (Source.Optional || reload)
{
// 如果是配置源存在性可选或重新加载,则初始化一个空的配置字典
Data = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
}
else
{
// 抛出异常
var error = new StringBuilder(SR.Format(SR.Error_FileNotFound, Source.Path));
if (!string.IsNullOrEmpty(file?.PhysicalPath))
{
error.Append(SR.Format(SR.Error_ExpectedPhysicalPath, file.PhysicalPath));
}
// 使用 FileConfigurationSource.OnLoadException 注册的异常处理程序处理异常
HandleException(ExceptionDispatchInfo.Capture(new FileNotFoundException(error.ToString())));
}
}
else
{
static Stream OpenRead(IFileInfo fileInfo)
{
// 物理文件路径存在
if (fileInfo.PhysicalPath != null)
{
// 根据物理文件绝对路径得到文件流
return new FileStream(
fileInfo.PhysicalPath,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite,
bufferSize: 1,
FileOptions.SequentialScan);
}
// 通过 IFileInfo.CreateReadStream 创建文件读取流
return fileInfo.CreateReadStream();
}
using Stream stream = OpenRead(file);
try
{
// 从文件流中加载配置源文件内容
Load(stream);
}
catch (Exception ex)
{
if (reload)
{
// 如果是重新加载,则初始化配置字典
Data = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
}
var exception = new InvalidDataException(SR.Format(SR.Error_FailedToLoad, file.PhysicalPath), ex);
// 使用 FileConfigurationSource.OnLoadException 注册的异常处理程序处理异常
HandleException(ExceptionDispatchInfo.Capture(exception));
}
}
// 调用父类的 OnReload 方法激活回调
OnReload();
}
// ConfigurationRoot 就是通过调用这个方法初始化每个文件配置提供者的配置字典
public override void Load()
{
Load(reload: false);
}
// 根据文件流将将文件内容转换为配置字典
// 实现类型主要就是重写这个方法,完成实际的文件配置到配置字典的转换
public abstract void Load(Stream stream);
// 实现 IDisposable
public void Dispose() => Dispose(true);
// 取消注册配置改变回调
protected virtual void Dispose(bool disposing)
{
_changeTokenRegistration?.Dispose();
}
}
// IChangeToken 回调注册工具类
public static class ChangeToken
{
// 根据 changeTokenProducer 得到 IChangeToken
// 使用 changeTokenConsumer 注册 IChangeToken.RegisterChangeCallback
public static IDisposable OnChange(Func<IChangeToken?> changeTokenProducer, Action changeTokenConsumer)
{
if (changeTokenProducer is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.changeTokenProducer);
}
if (changeTokenConsumer is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.changeTokenConsumer);
}
// Action<TState> 参数类型是 Action<Action>
// TState 泛型参数类型是 Action
return new ChangeTokenRegistration<Action>(changeTokenProducer, callback => callback(), changeTokenConsumer);
}
// 根据 changeTokenProducer 得到 IChangeToken
// 使用 changeTokenConsumer 注册 IChangeToken.RegisterChangeCallback
public static IDisposable OnChange<TState>(Func<IChangeToken?> changeTokenProducer, Action<TState> changeTokenConsumer, TState state)
{
if (changeTokenProducer is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.changeTokenProducer);
}
if (changeTokenConsumer is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.changeTokenConsumer);
}
return new ChangeTokenRegistration<TState>(changeTokenProducer, changeTokenConsumer, state);
}
// 内部类
// 表示 IChangeToken 注册登记
private sealed class ChangeTokenRegistration<TState> : IDisposable
{
private readonly Func<IChangeToken?> _changeTokenProducer;
private readonly Action<TState> _changeTokenConsumer;
private readonly TState _state;
public ChangeTokenRegistration(Func<IChangeToken?> changeTokenProducer, Action<TState> changeTokenConsumer, TState state)
{
_changeTokenProducer = changeTokenProducer;
_changeTokenConsumer = changeTokenConsumer;
_state = state;
// 使用生产者委托得到 IChangeToken 改变令牌
IChangeToken? token = changeTokenProducer();
// 使用 IChangeToken.RegisterChangeCallback 注册代理回调
RegisterChangeTokenCallback(token);
}
// 注册 IChangeToken.RegisterChangeCallback 的回调方法
// 此方法属于代理回调,内部使用消费者委托触发实际的回调
private void OnChangeTokenFired()
{
// 使用生产者委托得到新的 IChangeToken 改变令牌
IChangeToken? token = _changeTokenProducer();
try
{
// 使用消费者委托触发实际的回调
_changeTokenConsumer(_state);
}
finally
{
// 使用新改变令牌的 IChangeToken.RegisterChangeCallback 注册代理回调
RegisterChangeTokenCallback(token);
}
}
// 注册代理回调
private void RegisterChangeTokenCallback(IChangeToken? token)
{
if (token is null)
{
return;
}
// 使用 OnChangeTokenFired 方法注册 IChangeToken.RegisterChangeCallback
IDisposable registraton = token.RegisterChangeCallback(s => ((ChangeTokenRegistration<TState>?)s)!.OnChangeTokenFired(), this);
}
}
}
- CommandLineConfigurationSource
// 命令行配置源
// 单参数形式:
// 1. {name}={value}
// 2. {prefix}{name}={value}
// 双参数形式:
// 1. {prefix}{name} {value}
// 前缀支持:
// 1. --
// 2. -
// 3. /
public class CommandLineConfigurationSource : IConfigurationSource
{
// 命名行开关映射
// 命令行缩写与全名的映射配置(缩写忽略大小写)
// 缩写只能使用 “-” 或 “--” 作为前缀
// 如果使用 “-” 前缀,映射配置中必须存在相应配置,否则抛出 FormatException
public IDictionary<string, string>? SwitchMappings { get; set; }
// 命令行参数
public IEnumerable<string> Args { get; set; } = Array.Empty<string>();
// 构建对应的 IConfigurationProvider
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new CommandLineConfigurationProvider(Args, SwitchMappings);
}
}
- CommandLineConfigurationExtensions
// 添加命令行配置源的扩展方法
public static class CommandLineConfigurationExtensions
{
public static IConfigurationBuilder AddCommandLine(this IConfigurationBuilder configurationBuilder, string[] args)
{
return configurationBuilder.AddCommandLine(args, switchMappings: null);
}
public static IConfigurationBuilder AddCommandLine(
this IConfigurationBuilder configurationBuilder,
string[] args,
IDictionary<string, string>? switchMappings)
{
configurationBuilder.Add(new CommandLineConfigurationSource { Args = args, SwitchMappings = switchMappings });
return configurationBuilder;
}
public static IConfigurationBuilder AddCommandLine(this IConfigurationBuilder builder, Action<CommandLineConfigurationSource>? configureSource)
=> builder.Add(configureSource);
}
- EnvironmentVariablesConfigurationSource
// 环境变量配置源
// 来源:
// 1. 当前系统 HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Session Manager\Environment
// 2. 当前用户 HKEY_CURRENT_USER\Environment
// 3. 当前进程 launchSettings.json
public class EnvironmentVariablesConfigurationSource : IConfigurationSource
{
// 环境变量前缀
public string? Prefix { get; set; }
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new EnvironmentVariablesConfigurationProvider(Prefix);
}
}
- EnvironmentVariablesExtensions
// 添加环境变量配置源的扩展方法
public static class EnvironmentVariablesExtensions
{
public static IConfigurationBuilder AddEnvironmentVariables(this IConfigurationBuilder configurationBuilder)
{
configurationBuilder.Add(new EnvironmentVariablesConfigurationSource());
return configurationBuilder;
}
public static IConfigurationBuilder AddEnvironmentVariables(
this IConfigurationBuilder configurationBuilder,
string? prefix)
{
configurationBuilder.Add(new EnvironmentVariablesConfigurationSource { Prefix = prefix });
return configurationBuilder;
}
public static IConfigurationBuilder AddEnvironmentVariables(this IConfigurationBuilder builder, Action<EnvironmentVariablesConfigurationSource>? configureSource)
=> builder.Add(configureSource);
}
- ChainedConfigurationSource
// 外部链接配置源
public class ChainedConfigurationSource : IConfigurationSource
{
// 使用外部设置的 IConfiguration 作为配置源
public IConfiguration? Configuration { get; set; }
// IConfiguration 的释放是否由 ChainedConfigurationSource 创建的 ChainedConfigurationProvider 控制(是否一起释放)
public bool ShouldDisposeConfiguration { get; set; }
public IConfigurationProvider Build(IConfigurationBuilder builder)
=> new ChainedConfigurationProvider(this);
}
- ChainedConfigurationProvider
// 链接配置提供者
public class ChainedConfigurationProvider : IConfigurationProvider, IDisposable
{
private readonly IConfiguration _config;
private readonly bool _shouldDisposeConfig;
public ChainedConfigurationProvider(ChainedConfigurationSource source)
{
ThrowHelper.ThrowIfNull(source);
// IConfiguration 不存在,则抛出 ArgumentException
_config = source.Configuration ?? throw new ArgumentException(SR.Format(SR.InvalidNullArgument, "source.Configuration"), nameof(source));
_shouldDisposeConfig = source.ShouldDisposeConfiguration;
}
// 返回 IConfiguration
public IConfiguration Configuration => _config;
// 内部实际返回从 IConfiguration 得到配置值
public bool TryGet(string key, out string? value)
{
value = _config[key];
return !string.IsNullOrEmpty(value);
}
// 内部实际是对 IConfiguration 设置值
public void Set(string key, string? value) => _config[key] = value;
// 内部实际是调用 IConfiguration 的 GetReloadToken 方法
public IChangeToken GetReloadToken() => _config.GetReloadToken();
// 空实现
// 不需要转换配置字典操作
public void Load() { }
// 得到所有子配置键
public IEnumerable<string> GetChildKeys(
IEnumerable<string> earlierKeys,
string? parentPath)
{
// 根据 parentPath 路径得到配置节
IConfiguration section = parentPath == null ? _config : _config.GetSection(parentPath);
var keys = new List<string>();
// 循环所有子配置节
foreach (IConfigurationSection child in section.GetChildren())
{
// 添加将子配置节的配置键
keys.Add(child.Key);
}
// 将 earlierKeys 添加到末尾
keys.AddRange(earlierKeys);
// 根据字符编码升序排序
keys.Sort(ConfigurationKeyComparer.Comparison);
return keys;
}
// 释放
public void Dispose()
{
// IConfiguration 是否随 ChainedConfigurationProvider 一起执行释放
if (_shouldDisposeConfig)
{
(_config as IDisposable)?.Dispose();
}
}
}
// 使用外部 IConfiguration 作为配置源注册 IConfigurationSource 的扩展方法
public static class ChainedBuilderExtensions
{
public static IConfigurationBuilder AddConfiguration(this IConfigurationBuilder configurationBuilder, IConfiguration config)
=> AddConfiguration(configurationBuilder, config, shouldDisposeConfiguration: false);
public static IConfigurationBuilder AddConfiguration(
this IConfigurationBuilder configurationBuilder, IConfiguration config, bool shouldDisposeConfiguration)
{
ThrowHelper.ThrowIfNull(configurationBuilder);
ThrowHelper.ThrowIfNull(config);
configurationBuilder.Add(new ChainedConfigurationSource
{
Configuration = config,
ShouldDisposeConfiguration = shouldDisposeConfiguration,
});
return configurationBuilder;
}
}
// 配置节的抽象表示
// 逻辑上配置体现为一颗由不同配置节组成的配置树
// 具体配置值保存在这颗树的叶节点上
public interface IConfiguration
{
// 通过索引器访问配置节路径(key)得到对应的值
// 如果是叶配置节,则返回配置字典中以 key 为键所对应的值
// 否则返回 null
string? this[string key] { get; set; }
// 通过给定的配置节路径(key)得到配置节
// 无论配置节是否存在最终都会得到 IConfigurationSection
IConfigurationSection GetSection(string key);
// 得到当前配置节下所有的子配置节
IEnumerable<IConfigurationSection> GetChildren();
// 得到配置改变令牌
IChangeToken GetReloadToken();
}
// 配置根的抽象表示
public interface IConfigurationRoot : IConfiguration
{
// 重新加载
// 内部实际遍历调用 IConfigurationProvider.Load 方法重新加载配置字典
void Reload();
// 保存了所有的配置提供者
IEnumerable<IConfigurationProvider> Providers { get; }
}
// 非根配置节的抽象表示
public interface IConfigurationSection : IConfiguration
{
// 配置节名称
string Key { get; }
// 配置节路径
string Path { get; }
// 如果是叶配置节,则返回配置字典中以 Path 为键所对应的值
string? Value { get; set; }
}
// IConfigurationRoot 的默认实现
public class ConfigurationRoot : IConfigurationRoot, IDisposable
{
// 配置提供者集合
private readonly IList<IConfigurationProvider> _providers;
// 针对每个配置提供者的 IChangeToken 注册的 IChangeToken.RegisterChangeCallback 回调都可以通过 IDisposable.Dispose 方法取消
private readonly IList<IDisposable> _changeTokenRegistrations;
// 对外提供注册 IChangeToken.RegisterChangeCallback 回调
private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();
public ConfigurationRoot(IList<IConfigurationProvider> providers)
{
ThrowHelper.ThrowIfNull(providers);
_providers = providers;
_changeTokenRegistrations = new List<IDisposable>(providers.Count);
foreach (IConfigurationProvider p in providers)
{
// 将每个配置提供器对应的配置源加载并转换为配置字典
p.Load();
// 收集针对每个配置提供者的 IChangeToken 注册的 IChangeToken.RegisterChangeCallback 回调产生的 IDisposable 对象
_changeTokenRegistrations.Add(ChangeToken.OnChange(p.GetReloadToken, RaiseChanged));
}
}
// 返回配置提供者集合
public IEnumerable<IConfigurationProvider> Providers => _providers;
// 实现索引器
// 通过配置节路径(key)从配置提供者所有持有的配置字典中得到对应的值
public string? this[string key]
{
get => GetConfiguration(_providers, key);
set => SetConfiguration(_providers, key, value);
}
// 从配置提供器的配置字典中得到对应配置键的配置值
internal static string? GetConfiguration(IList<IConfigurationProvider> providers, string key)
{
// 从后添加的配置提供者往前遍历
// 寻找配置字典中存在对应配置节路径的值
for (int i = providers.Count - 1; i >= 0; i--)
{
IConfigurationProvider provider = providers[i];
if (provider.TryGet(key, out string? value))
{
return value;
}
}
return null;
}
// 根据配置键和配置值设置所有配置提供器的配置字典
internal static void SetConfiguration(IList<IConfigurationProvider> providers, string key, string? value)
{
if (providers.Count == 0)
{
throw new InvalidOperationException(SR.Error_NoSources);
}
// 会以给定的配置节路径在每个配置提供者的配置字典中创建或更新对应的配置值
foreach (IConfigurationProvider provider in providers)
{
provider.Set(key, value);
}
}
// 得到当前配置节下的子配置节集合
// 根下的子配置节,所以调用 InternalConfigurationRootExtensions.GetChildrenImplementation 方法时实参传 null
public IEnumerable<IConfigurationSection> GetChildren() => this.GetChildrenImplementation(null);
// 返回 IChangeToken
// 对外提供注册 IChangeToken.RegisterChangeCallback 回调
public IChangeToken GetReloadToken() => _changeToken;
// 得到路径为 Key 的配置节
// 可能返回配置节路径不存在的配置节对象
public IConfigurationSection GetSection(string key)
=> new ConfigurationSection(this, key);
// 可用用来重新加载配置源并转换为配置字典
public void Reload()
{
foreach (IConfigurationProvider provider in _providers)
{
provider.Load();
}
// 激活回调
RaiseChanged();
}
// 用来注册每个配置提供器的 IChangeToken.RegisterChangeCallback 回调方法
private void RaiseChanged()
{
// 原子操作创建新的 ConfigurationReloadToken,返回原来的 ConfigurationReloadToken
ConfigurationReloadToken previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());
// 使用原来的 ConfigurationReloadToken 激活外部注册的 IChangeToken.RegisterChangeCallback 回调
previousToken.OnReload();
}
// 释放
public void Dispose()
{
foreach (IDisposable registration in _changeTokenRegistrations)
{
// 取消回调注册
registration.Dispose();
}
foreach (IConfigurationProvider provider in _providers)
{
// 释放配置提供者
(provider as IDisposable)?.Dispose();
}
}
}
// IConfigurationSection 的默认实现
public class ConfigurationSection : IConfigurationSection
{
// 配置根
private readonly IConfigurationRoot _root;
// 配置节路径
private readonly string _path;
// 配置键(配置节路径最后一段)
private string? _key;
public ConfigurationSection(IConfigurationRoot root, string path)
{
ThrowHelper.ThrowIfNull(root);
ThrowHelper.ThrowIfNull(path);
_root = root;
_path = path;
}
// 返回配置节路径
public string Path => _path;
// 返回配置键(配置节路径最后一段)
public string Key =>
_key ??= ConfigurationPath.GetSectionKey(_path);
// 返回由当前配置节路径作为配置键得到的配置值
// 内部实际是调用配置根实现的索引器
public string? Value
{
get
{
return _root[Path];
}
set
{
_root[Path] = value;
}
}
// 实现索引器
// 内部实际是调用配置根实现的索引器
// 返回由 [当前配置节路径]:[参数 key] 表示的配置节路径作为配置键得到的配置值
public string? this[string key]
{
get
{
return _root[ConfigurationPath.Combine(Path, key)];
}
set
{
_root[ConfigurationPath.Combine(Path, key)] = value;
}
}
// 返回配置节路径为 [当前配置节路径]:[参数 key] 的配置节(子配置节)
// 可能返回配置节路径不存在的配置节对象
public IConfigurationSection GetSection(string key) => _root.GetSection(ConfigurationPath.Combine(Path, key));
// 得到所有子配置节,父路径参数为当前配置节路径
public IEnumerable<IConfigurationSection> GetChildren() => _root.GetChildrenImplementation(Path);
// 返回配置根的 IChangeToken
public IChangeToken GetReloadToken() => _root.GetReloadToken();
}
- InternalConfigurationRootExtensions
// IConfigurationRoot 的扩展方法
internal static class InternalConfigurationRootExtensions
{
// 根据父配置节路径,得到所有子配置节
internal static IEnumerable<IConfigurationSection> GetChildrenImplementation(this IConfigurationRoot root, string? path)
{
// 得到所有配置提供者
using ReferenceCountedProviders? reference = (root as ConfigurationManager)?.GetProvidersReference();
IEnumerable<IConfigurationProvider> providers = reference?.Providers ?? root.Providers;
// 通过累加器得到父配置节路径下所有子配置节的配置键并去重
// 最终得到配置节路径为 [path 表示的父配置节路径]:[得到的子配置节的配置键] 的所有子配置节
IEnumerable<IConfigurationSection> children = providers
.Aggregate(Enumerable.Empty<string>(),
(seed, source) => source.GetChildKeys(seed, path))
.Distinct(StringComparer.OrdinalIgnoreCase)
.Select(key => root.GetSection(path == null ? key : ConfigurationPath.Combine(path, key)));
if (reference is null)
{
return children;
}
else
{
return children.ToList();
}
}
}
// 配置绑定
// IConfiguration 扩展方法
public static class ConfigurationBinder
{
// 根据给定的配置节创建对应类型的对象后绑定配置值
public static T? Get<T>(this IConfiguration configuration)
=> configuration.Get<T>(_ => { });
public static T? Get<T>(this IConfiguration configuration, Action<BinderOptions>? configureOptions)
{
ThrowHelper.ThrowIfNull(configuration);
object? result = configuration.Get(typeof(T), configureOptions);
if (result == null)
{
return default(T);
}
return (T)result;
}
public static object Get(this IConfiguration configuration, Type type)
=> configuration.Get(type, _ => { });
public static object Get(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions)
{
ThrowHelper.ThrowIfNull(configuration);
var options = new BinderOptions();
configureOptions?.Invoke(options);
var bindingPoint = new BindingPoint();
BindInstance(type, bindingPoint, config: configuration, options: options);
return bindingPoint.Value;
}
// 根据给定的配置节对已存在的对象绑定配置值
public static void Bind(this IConfiguration configuration, string key, object? instance)
=> configuration.GetSection(key).Bind(instance);
public static void Bind(this IConfiguration configuration, object? instance)
=> configuration.Bind(instance, _ => { });
public static void Bind(this IConfiguration configuration, object? instance, Action<BinderOptions>? configureOptions)
{
ThrowHelper.ThrowIfNull(configuration);
if (instance != null)
{
var options = new BinderOptions();
configureOptions?.Invoke(options);
// 设置绑定点只读
var bindingPoint = new BindingPoint(instance, isReadOnly: true);
BindInstance(instance.GetType(), bindingPoint, configuration, options);
}
}
// 根据给定的配置节和子路径得到配置值(字符串),并根据给定的 type 目标类型将字符串转换为目标类型值
// 如果 type 表示的目标类型不能从字符串转换,需要通过继承 TypeConverter 类型实现从字符串类型到目标类型的转换规则
public static T? GetValue<T>(this IConfiguration configuration, string key)
{
return GetValue(configuration, key, default(T));
}
public static T? GetValue<T>(this IConfiguration configuration, string key, T defaultValue)
{
return (T?)GetValue(configuration, typeof(T), key, defaultValue);
}
public static object? GetValue(this IConfiguration configuration, Type type, string key)
{
return GetValue(configuration, type, key, defaultValue: null);
}
public static object? GetValue(this IConfiguration configuration, Type type, string key, object? defaultValue)
{
IConfigurationSection section = configuration.GetSection(key);
string? value = section.Value;
if (value != null)
{
// 尝试从字符串转换为 type 表示的目标类型
return ConvertValue(type, value, section.Path);
}
return defaultValue;
}
// 将配置绑定到实例属性上
private static void BindInstance(Type type, BindingPoint bindingPoint, IConfiguration config, BinderOptions options)
{
// 如果 type 表示的目标类型实现 IConfigurationSection,则直接返回这个 config
if (type == typeof(IConfigurationSection))
{
// 对绑定点设置新的值
bindingPoint.TrySetValue(config);
return;
}
var section = config as IConfigurationSection;
string? configValue = section?.Value;
// 如果配置节是叶节点并且可以从字符串转换为 type 表示的目标类型
if (configValue != null && TryConvertValue(type, configValue, section?.Path, out object? convertedValue, out Exception? error))
{
if (error != null)
{
throw error;
}
// 对绑定点设置新的值
bindingPoint.TrySetValue(convertedValue);
return;
}
// 如果是非叶配置节并且存在子配置节
if (config != null && config.GetChildren().Any())
{
// 如果 type 表示的目标类型是数组类型或数组类型实现的接口
// 如果是接口类型必须是 IEnumerable<>、ICollection<>、IReadOnlyCollection<>、IList<>、IReadOnlyList<> 类型,并且是被构建的泛型类型
// 所有数组类型在 JIT 编译期的程序集加载和 MethodTable 创建时,CLR 都会为相应的数组类型实现一些集合相关的接口
// 比如 T 类型的数组,那么 T[] 最终在运行时会实现
// IEnumerable<T>、ICollection<T>、IReadOnlyCollection<T>、IList<T>、IReadOnlyList<T> 以及相应的非泛型接口
// 注:type 一定要是接口类型或数组类型
if (type.IsArray || IsArrayCompatibleInterface(type))
{
if (!bindingPoint.IsReadOnly)
{
// 作为数组绑定,并将绑定完成的对象作为绑定点新的值
// 如果没有对应的绑定配置值并且是通过 Get 相关方法调用,则返回空数组(不是 null)
bindingPoint.SetValue(BindArray(type, (IEnumerable?)bindingPoint.Value, config, options));
}
return;
}
// 如果 type 表示的目标类型是 ISet<> 或 IReadOnlySet<> 类型,并且是被构建的泛型类型
// 注:type 一定要是接口类型
if (TypeIsASetInterface(type))
{
if (!bindingPoint.IsReadOnly)
{
// 作为 HashSet 集合绑定(表示无序且不可重复的集合),并将绑定完成的对象作为绑定点新的值
// 如果没有对应的绑定配置值并且是通过 Get 相关方法调用,则返回 null
object? newValue = BindSet(type, (IEnumerable?)bindingPoint.Value, config, options);
if (newValue != null)
{
bindingPoint.SetValue(newValue);
}
}
return;
}
// 如果 type 表示的目标类型是 IDictionary<,> 或 IReadOnlyDictionary<,> 类型,并且是被构建的泛型类型
// 注:type 一定要是接口类型
if (TypeIsADictionaryInterface(type))
{
if (!bindingPoint.IsReadOnly)
{
// 作为字典绑定,并将绑定完成的对象作为绑定点新的值
// 如果没有对应的绑定配置值并且是通过 Get 相关方法调用,则返回 null
object? newValue = BindDictionaryInterface(bindingPoint.Value, type, config, options);
if (newValue != null)
{
bindingPoint.SetValue(newValue);
}
}
return;
}
// 如果 type 表示的目标类型不是集合接口或数组类型并且通过 Get 相关方法调用(不是 Bind 方法)
if (bindingPoint.Value is null)
{
if (bindingPoint.IsReadOnly)
{
return;
}
// 创建实例并设置绑定点的值
// BindingPoint.Value 可能是如下几种类型的对象
// 1. 实现 IDictionary<,> 的字典对象并且不是接口类型(未通过 TypeIsADictionaryInterface 判断)
// 2. 实现 ICollection<> 的集合对象但不是数组类型也不是接口类型(未通过 IsArrayCompatibleInterface 判断)
// 3. 非集合实例对象
// 如果是作为非集合实例对象绑定,在满足以下情况下创建的实例将直接通过构造函数的参数绑定配置
// 1. 唯一的构造函数存在参数(不通过构造函数绑定无法实例化)
// 2. 构造函数参数列表的参数名称必须在以下得到的属性列表中全部存在(忽略大小写)
// 1. 类型定义中所有只读属性
// 2. 属性的 Set 方法是继承自父类的
// 3. 属性的 Set 方法是非虚实例的
bindingPoint.SetValue(CreateInstance(type, config, options));
}
// 代码执行到这里绑定点的 BindingPoint.Value 一定不为 null
// type 表示的目标类型可能是实现 IDictionary<,> 的类型
Type? dictionaryInterface = FindOpenGenericInterface(typeof(IDictionary<,>), type);
if (dictionaryInterface != null)
{
// 调用绑定具体字典
BindConcreteDictionary(bindingPoint.Value!, dictionaryInterface, config, options);
}
else
{
// type 表示的目标类型可能是实现 ICollection<,> 的类型
Type? collectionInterface = FindOpenGenericInterface(typeof(ICollection<>), type);
if (collectionInterface != null)
{
// 调用绑定具体集合
BindCollection(bindingPoint.Value!, collectionInterface, config, options);
}
else
{
// 绑定实例中具体属性
BindProperties(bindingPoint.Value!, config, options);
}
}
}
}
// 绑定数组
private static Array BindArray(Type type, IEnumerable? source, IConfiguration config, BinderOptions options)
{
Type elementType;
if (type.IsArray)
{
// 如果是数组类型并且数组的秩大于 1(非一维数组),则抛出 InvalidOperationException
if (type.GetArrayRank() > 1)
{
throw new InvalidOperationException(SR.Format(SR.Error_UnsupportedMultidimensionalArray, type));
}
// 得到数组元素类型
elementType = type.GetElementType()!;
}
else
{
// 否则从泛型接口的第一个泛型实参得到,比如 IEnumerable<>、ICollection<T>、IReadOnlyCollection<T>、IList<T>、IReadOnlyList<T> 得到类型 T
elementType = type.GetGenericArguments()[0];
}
IList list = new List<object?>();
// 如果通过调用 Bind 相关方法
// 绑定点已存在绑定的集合
if (source != null)
{
// 转移到新的集合中
foreach (object? item in source)
{
list.Add(item);
}
}
// 遍历所有子配置节
foreach (IConfigurationSection section in config.GetChildren())
{
var itemBindingPoint = new BindingPoint();
try
{
// 根据每个子配置节调用 BindInstance
BindInstance(
type: elementType,
bindingPoint: itemBindingPoint,
config: section,
options: options);
// 将得到的绑定实例添加到集合末尾
// 如果没有绑定成功则跳过
if (itemBindingPoint.HasNewValue)
{
list.Add(itemBindingPoint.Value);
}
}
catch
{
}
}
// 创建新的数组并从列表拷贝元素
Array result = Array.CreateInstance(elementType, list.Count);
list.CopyTo(result, 0);
return result;
}
// 绑定 HashSet 集合
// 仅支持配置值是字符串或枚举值的配置节
private static object? BindSet(Type type, IEnumerable? source, IConfiguration config, BinderOptions options)
{
Type elementType = type.GetGenericArguments()[0];
Type keyType = type.GenericTypeArguments[0];
bool keyTypeIsEnum = keyType.IsEnum;
// 仅支持泛型参数类型为 string 或枚举类型作为 key
// HashSet 集合内部是调用 GetHashCode 方法得到散列值来确定位置的
if (keyType != typeof(string) && !keyTypeIsEnum)
{
return null;
}
Type genericType = typeof(HashSet<>).MakeGenericType(keyType);
object instance = Activator.CreateInstance(genericType)!;
MethodInfo addMethod = genericType.GetMethod("Add", DeclaredOnlyLookup)!;
object?[] arguments = new object?[1];
// 如果通过调用 Bind 相关方法
// 绑定点已存在绑定的集合
if (source != null)
{
// 转移到新的 HashSet 集合中
foreach (object? item in source)
{
arguments[0] = item;
// 通过反射方式调用 Add 方法
addMethod.Invoke(instance, arguments);
}
}
// 遍历所有子配置节
foreach (IConfigurationSection section in config.GetChildren())
{
var itemBindingPoint = new BindingPoint();
try
{
// 根据每个子配置节调用 BindInstance
BindInstance(
type: elementType,
bindingPoint: itemBindingPoint,
config: section,
options: options);
// 将得到的绑定实例放入 HashSet
// 如果没有绑定成功则跳过
if (itemBindingPoint.HasNewValue)
{
arguments[0] = itemBindingPoint.Value;
addMethod.Invoke(instance, arguments);
}
}
catch
{
}
}
return instance;
}
// 绑定字典
private static object? BindDictionaryInterface(object? source, Type dictionaryType, IConfiguration config, BinderOptions options)
{
// 得到表示 Key 的泛型参数类型
Type keyType = dictionaryType.GenericTypeArguments[0];
// 得到表示 Value 的泛型参数类型
Type valueType = dictionaryType.GenericTypeArguments[1];
bool keyTypeIsEnum = keyType.IsEnum;
// 仅支持泛型参数类型为 string 或枚举类型作为 key
if (keyType != typeof(string) && !keyTypeIsEnum)
{
return null;
}
Type genericType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType);
MethodInfo addMethod = genericType.GetMethod("Add", DeclaredOnlyLookup)!;
Type kvpType = typeof(KeyValuePair<,>).MakeGenericType(keyType, valueType);
PropertyInfo keyMethod = kvpType.GetProperty("Key", DeclaredOnlyLookup)!;
PropertyInfo valueMethod = kvpType.GetProperty("Value", DeclaredOnlyLookup)!;
object dictionary = Activator.CreateInstance(genericType)!;
var orig = source as IEnumerable;
object?[] arguments = new object?[2];
// 如果通过调用 Bind 相关方法
// 绑定点已存在绑定字典
if (orig != null)
{
// 转移到新的 Dictionary 字典中
foreach (object? item in orig)
{
object? k = keyMethod.GetMethod!.Invoke(item, null);
object? v = valueMethod.GetMethod!.Invoke(item, null);
arguments[0] = k;
arguments[1] = v;
addMethod.Invoke(dictionary, arguments);
}
}
// 绑定具体的字典
BindConcreteDictionary(dictionary, dictionaryType, config, options);
return dictionary;
}
// 实际处理配置绑定到字典的方法
private static void BindConcreteDictionary(object? dictionary, Type dictionaryType, IConfiguration config, BinderOptions options)
{
// 得到表示 Key 的泛型参数类型
Type keyType = dictionaryType.GenericTypeArguments[0];
// 得到表示 Value 的泛型参数类型
Type valueType = dictionaryType.GenericTypeArguments[1];
bool keyTypeIsEnum = keyType.IsEnum;
// 仅支持表示 Key 的泛型参数为 string 和枚举类型作为 key
if (keyType != typeof(string) && !keyTypeIsEnum)
{
return;
}
Type genericType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType);
MethodInfo tryGetValue = dictionaryType.GetMethod("TryGetValue")!;
// 得到索引器的属性元数据定义(仅限在类型定义中查找索引器定义)
PropertyInfo setter = genericType.GetProperty("Item", DeclaredOnlyLookup)!;
// 遍历所有子配置节
foreach (IConfigurationSection child in config.GetChildren())
{
try
{
// 使用配置节的 Key 或对应的枚举值作为字典的键
object key = keyTypeIsEnum ? Enum.Parse(keyType, child.Key) : child.Key;
// 创建绑定点的初始值提供者
// 此处通过绑定提供器的目的是因为可能操作是通过 Bind 方式发起的
// 这样就可能存在从已有实例转移过来的数据
var valueBindingPoint = new BindingPoint(
initialValueProvider: () =>
{
// 参数列表的第二个 null 对应输出参数(out)
object?[] tryGetValueArgs = { key, null };
// 存在对应的键,返回输出参数的值,否则返回 null
return (bool)tryGetValue.Invoke(dictionary, tryGetValueArgs)! ? tryGetValueArgs[1] : null;
},
isReadOnly: false);
BindInstance(
type: valueType,
bindingPoint: valueBindingPoint,
config: child,
options: options);
// 将得到的绑定实例放入字典
// 如果没有绑定成功则跳过
if (valueBindingPoint.HasNewValue)
{
// 通过索引器反射设置字典值
setter.SetValue(dictionary, valueBindingPoint.Value, new object[] { key });
}
}
catch
{
}
}
}
// 绑定实现 ICollection<> 接口的集合
private static void BindCollection(object collection, Type collectionType, IConfiguration config, BinderOptions options)
{
// 的到集合元素类型
Type itemType = collectionType.GenericTypeArguments[0];
// 反射得到实现了 ICollection 的 Add 方法
MethodInfo? addMethod = collectionType.GetMethod("Add", DeclaredOnlyLookup);
foreach (IConfigurationSection section in config.GetChildren())
{
try
{
BindingPoint itemBindingPoint = new();
BindInstance(
type: itemType,
bindingPoint: itemBindingPoint,
config: section,
options: options);
// 将得到的绑定实例添加到集合末尾
// 如果没有绑定成功则跳过
if (itemBindingPoint.HasNewValue)
{
// 通过反射添加集合元素
addMethod?.Invoke(collection, new[] { itemBindingPoint.Value });
}
}
catch
{
}
}
}
// 绑定属性
private static void BindProperties(object instance, IConfiguration configuration, BinderOptions options)
{
List<PropertyInfo> modelProperties = GetAllProperties(instance.GetType());
// 如果 BinderOptions.ErrorOnUnknownConfiguration 属性值为 true
// 则需要在绑定属性前检查所有配置节的 Key 是否都可以找到对应的同名属性(忽略大小写)
// 如果存在未知配置,则抛出异常
if (options.ErrorOnUnknownConfiguration)
{
HashSet<string> propertyNames = new(modelProperties.Select(mp => mp.Name),
StringComparer.OrdinalIgnoreCase);
// 实体的属性集合必须是所有子配置节 Key 的集合的超集
// 即所有的子配置节的 key 必须能够找到同名的属性(忽略大小写)
IEnumerable<IConfigurationSection> configurationSections = configuration.GetChildren();
List<string> missingPropertyNames = configurationSections
.Where(cs => !propertyNames.Contains(cs.Key))
.Select(mp => $"'{mp.Key}'")
.ToList();
// 不满足超集原则(子配置节 key 没有找到同名属性),则抛出 InvalidOperationException
if (missingPropertyNames.Count > 0)
{
throw new InvalidOperationException(SR.Format(SR.Error_MissingConfig,
nameof(options.ErrorOnUnknownConfiguration), nameof(BinderOptions), instance.GetType(),
string.Join(", ", missingPropertyNames)));
}
}
foreach (PropertyInfo property in modelProperties)
{
BindProperty(property, instance, configuration, options);
}
}
// 通过反射方式绑定属性
private static void BindProperty(PropertyInfo property, object instance, IConfiguration config, BinderOptions options)
{
// 不支持只写属性(即使绑定上了也没有意义)、非公共属性、索引器(有参属性)
if (property.GetMethod == null ||
(!options.BindNonPublicProperties && !property.GetMethod.IsPublic) ||
property.GetMethod.GetParameters().Length > 0)
{
return;
}
// 创建绑定点的初始值提供者
// 使用创建实例时属性的默认值作为绑定点初始值提供者
// 如果是只读属性或属性的 Set 方法非公开并且不绑定非公开属性,则设置绑定点只读(只使用默认值)
// 如果是通过 Bind 方法调用的话,初始值提供者将提供已存在的值(如果属性是集合将会和新绑定的元素合并)
var propertyBindingPoint = new BindingPoint(
initialValueProvider: () => property.GetValue(instance),
isReadOnly: property.SetMethod is null || (!property.SetMethod.IsPublic && !options.BindNonPublicProperties));
// 使用属性名创建子配置节并调用 BindInstance 方法
BindInstance(
property.PropertyType,
propertyBindingPoint,
config.GetSection(GetPropertyName(property)),
options);
// 只有绑定过程创建了绑定点的新值才通过反射对实例属性赋值
if (propertyBindingPoint.HasNewValue)
{
property.SetValue(instance, propertyBindingPoint.Value);
}
}
}
// 绑定选项
public class BinderOptions
{
// 是否绑定非公开属性
public bool BindNonPublicProperties { get; set; }
// 在绑定属性时,如果配置节的 Key 找不到对应的同名属性时(忽略大小写),是否抛出异常
public bool ErrorOnUnknownConfiguration { get; set; }
}
// 表示绑定上下文
// 用来在绑定配置时存储实例值
internal sealed class BindingPoint
{
// 初始值提供者
private readonly Func<object?>? _initialValueProvider;
// 初始值
private object? _initialValue;
// 设置值
private object? _setValue;
// 是否从外部设置过值
private bool _valueSet;
public BindingPoint(object? initialValue = null, bool isReadOnly = false)
{
_initialValue = initialValue;
IsReadOnly = isReadOnly;
}
public BindingPoint(Func<object?> initialValueProvider, bool isReadOnly)
{
_initialValueProvider = initialValueProvider;
IsReadOnly = isReadOnly;
}
// 是否只读
public bool IsReadOnly { get; }
// 是否由外部设置过新值
public bool HasNewValue
{
get
{
if (IsReadOnly)
{
return false;
}
if (_valueSet)
{
return true;
}
// 可变值类型(自定义的结构)返回 true
// 原始值类型都属于不可变值类型(初始化结构后不能修改)
return _initialValue?.GetType() is { } initialValueType
&& initialValueType.IsValueType
&& !initialValueType.IsPrimitive;
}
}
// 按优先顺序从外部设置新值、初始值、初始值提供者得到值
public object? Value => _valueSet ? _setValue : _initialValue ??= _initialValueProvider?.Invoke();
// 从外部设置值
public void SetValue(object? newValue)
{
Debug.Assert(!IsReadOnly);
Debug.Assert(!_valueSet);
_setValue = newValue;
_valueSet = true;
}
// 尝试从外部设置值
public void TrySetValue(object? newValue)
{
if (!IsReadOnly)
{
SetValue(newValue);
}
}
}