NetCore 入门 (四) : 配置数据源
1. 介绍
一般来说,定义一种配置源,需要经过如下三个步骤:
- [必须] 实现
IConfigurationSource
接口 - [必须] 实现
IConfigurationProvider
接口 - [可选] 在
IConfigurationBuilder
接口上提供Add{Source}
扩展方法
配置源列表
ASP.NET Core框架提供了如下几种配置源:
Microsoft.Extensions.Configuration
- MemoryConfigurationSource - 内存配置源
- StreamConfigurationSource - 流配置源
- ChainedConfigurationSource - 链式配置源
Microsoft.Extensions.Configuration.FileExtensions
Microsoft.Extensions.Configuration.Json
Microsoft.Extensions.Configuration.Xml
Microsoft.Extensions.Configuration.Ini
Microsoft.Extensions.Configuration.CommandLine
Microsoft.Extensions.Configuration.EnvironmentVariables
2. 内存变量
MemoryConfigurationSource
是最简单的一种配置,采用字典对象(KeyValuePair<string, string>
的集合)作为存储配置数据的容器。
核心类
MemoryConfigurationSource
public class MemoryConfigurationSource : IConfigurationSource
{
public IEnumerable<KeyValuePair<string, string>> InitialData { get; set; }
public IConfigurationProvider Build(IConfigurationBuilder builder);
}
InitialData
: 用来存放配置的原始数据。
MemoryConfigurationProvider
public class MemoryConfigurationProvider : ConfigurationProvider, IEnumerable<KeyValuePair<string, string>>, IEnumerable
{
public MemoryConfigurationProvider(MemoryConfigurationSource source)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
this._source = source;
if (this._source.InitialData != null)
{
foreach (KeyValuePair<string, string> keyValuePair in this._source.InitialData)
{
base.Data.Add(keyValuePair.Key, keyValuePair.Value);
}
}
}
public void Add(string key, string value)
{
base.Data.Add(key, value);
}
private readonly MemoryConfigurationSource _source;
...
}
从MemoryConfigurationProvider
的实现中,我们进一步明确了IConfigurationProvider
接口的职责,那就是读取原始配置数据并将其转换成配置字典。MemoryConfigurationProvider
是最简单、直接的一种,因为它对应的配置源就是配置字典,不需要做任何转换。
扩展方法
public static class MemoryConfigurationBuilderExtensions
{
public static IConfigurationBuilder AddInMemoryCollection(this IConfigurationBuilder configurationBuilder);
public static IConfigurationBuilder AddInMemoryCollection(this IConfigurationBuilder configurationBuilder,
IEnumerable<KeyValuePair<string, string>> initialData);
}
3. 物理文件
物理文件是我们最常用的配置方式,比如json文件、xml文件以及ini文件。包Microsoft.Extensions.Configuration.FileExtensions
提供了文件类配置源的基础实现,至于具体的文件类型(json、xml),需要在此基础上去扩展。
3.1 核心类
- 抽象实现
- Json文件
- Xml文件
- Ini文件
3.2 抽象实现
3.2.1 FileConfiguratuonSource
public abstract class FileConfigurationSource : IConfigurationSource
{
public IFileProvider FileProvider { get; set; }
public string Path { get; set; }
public bool Optional { get; set; }
public bool ReloadOnChange { get; set; }
public int ReloadDelay { get; set; } = 250;
public Action<FileLoadExceptionContext> OnLoadException { get; set; }
public abstract IConfigurationProvider Build(IConfigurationBuilder builder);
...
}
FileProvider
: 利用IFileProvider
对象来读取配置文件。Path
: 配置文件的路径。一般来说,这个是针对FileProvider
对象根目录的相对路径。Optional
: 标识当前配置文件是否是可选的。如果为false
,则在配置文件不存在的时候会抛出异常。默认false
。ReloadOnChange
: 标识配置文件在更新后是否自动实时加载。这个功能同样借助IFileProvider
对象来完成。默认false
。ReloadDelay
: "延迟加载"的时间,默认250毫秒。如果ReloadOnChange
为true
,一旦配置文件发生变化,IFileProvider
对象会在第一时间通知对应的FileConfigurationProvider
对象,后者会调用Load
方法重新加载配置文件。考虑到配置文件的写入此时可能尚未结束,所以采用“延迟加载”的方式来解决这个问题。OnLoadException
: 考虑到配置文件的加载不可能百分之百成功,所以提供了相应的异常处理机制。通过OnLoadException
属性注册一个异常处理器,处理在文件加载过程中出现的异常。将一个FileLoadExceptionContext
对象作为异常处理的执行上下文,其中包括加载文件的FileConfigurationProvider
对象以及发生的异常Exception
等信息。
扩展类只需实现Build
方法。
3.2.2 FileLoadExceptionContext
public class FileLoadExceptionContext
{
// The FileConfigurationProvider that caused the exception.
public FileConfigurationProvider Provider { get; set; }
// The exception that occured in Load.
public Exception Exception { get; set; }
// If true, the exception will not be rethrown.
public bool Ignore { get; set; }
}
如果异常处理完成后,将Ignore
设置为true
,FileConfigurationProvider
对象会认为当前异常是可以被忽略的,程序可以继续执行。否则异常对象还是会抛出来。
3.2.3 FileConfigurationProvider
public abstract class FileConfigurationProvider : ConfigurationProvider, IDisposable
{
public FileConfigurationProvider(FileConfigurationSource source)
{
this.Source = source;
if (this.Source.ReloadOnChange && this.Source.FileProvider != null)
{
this._changeTokenRegistration = ChangeToken.OnChange(() => this.Source.FileProvider.Watch(this.Source.Path),
delegate()
{
Thread.Sleep(this.Source.ReloadDelay);
this.Load(true); // 2. 文件变化后重新加载
});
}
}
// 3. 核心加载逻辑
private void Load(bool reload)
{
IFileProvider fileProvider = this.Source.FileProvider;
IFileInfo fileInfo = (fileProvider != null) ? fileProvider.GetFileInfo(this.Source.Path) : null;
if (fileInfo == null || !fileInfo.Exists)
{
if (this.Source.Optional || reload)
{
base.Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
}
else
{
if (reload)
{
base.Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
using (Stream stream = fileInfo.CreateReadStream())
{
this.Load(stream); // 4. 最终通过Stream的方式进行加载
}
}
base.OnReload(); // 通知外部:配置数据已更新
}
// 1. 外部调用,首次加载
public override void Load()
{
this.Load(false);
}
public abstract void Load(Stream stream);
...
}
FileConfigurationProvider
已经实现了文件加载逻辑,扩展类只需实现void Load(Stream stream);
方法即可。
3.3 Json文件
JsonConfigurationSource
public class JsonConfigurationSource : FileConfigurationSource
{
public override IConfigurationProvider Build(IConfigurationBuilder builder);
}
仅扩展了Build
方法。
JsonConfigurationProvider
public class JsonConfigurationProvider : FileConfigurationProvider
{
public override void Load(Stream stream);
}
实现了Load(Stream stream)
方法。
IConfigurationBuilder扩展
public static class JsonConfigurationExtensions
{
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path);
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path, bool optional);
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange);
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, IFileProvider provider, string path, bool optional, bool reloadOnChange);
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Action<JsonConfigurationSource> configureSource);
}
3.4 Xml文件
XmlConfigurationSource
public class XmlConfigurationSource : FileConfigurationSource
{
public override IConfigurationProvider Build(IConfigurationBuilder builder);
}
XmlConfigurationProvider
public class XmlConfigurationProvider : FileConfigurationProvider
{
public override void Load(Stream stream);
}
IConfigurationBuilder扩展
public static class XmlConfigurationExtensions
{
public static IConfigurationBuilder AddXmlFile(this IConfigurationBuilder builder, string path);
public static IConfigurationBuilder AddXmlFile(this IConfigurationBuilder builder, string path, bool optional);
public static IConfigurationBuilder AddXmlFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange);
public static IConfigurationBuilder AddXmlFile(this IConfigurationBuilder builder, IFileProvider provider, string path, bool optional, bool reloadOnChange);
public static IConfigurationBuilder AddXmlFile(this IConfigurationBuilder builder, Action<XmlConfigurationSource> configureSource);
}
3.5 Ini文件
IniConfigurationSource
public class IniConfigurationSource : FileConfigurationSource
{
public override IConfigurationProvider Build(IConfigurationBuilder builder);
}
IniConfigurationProvider
public class IniConfigurationProvider : FileConfigurationProvider
{
public override void Load(Stream stream);
}
IConfigurationBuilder扩展
public static class IniConfigurationExtensions
{
public static IConfigurationBuilder AddIniFile(this IConfigurationBuilder builder, string path);
public static IConfigurationBuilder AddIniFile(this IConfigurationBuilder builder, string path, bool optional);
public static IConfigurationBuilder AddIniFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange);
public static IConfigurationBuilder AddIniFile(this IConfigurationBuilder builder, IFileProvider provider, string path, bool optional, bool reloadOnChange);
public static IConfigurationBuilder AddIniFile(this IConfigurationBuilder builder, Action<IniConfigurationSource> configureSource);
}
4. Stream
通过Stream读取配置内容,这种配置源具有更加灵活的应用。
4.1 核心类
- 抽象实现
- Json流
- Xml流
- Ini流
4.2 抽象实现
4.2.1 StreamConfigurationSource
public abstract class StreamConfigurationSource : IConfigurationSource
{
public Stream Stream { get; set; }
public abstract IConfigurationProvider Build(IConfigurationBuilder builder);
}
4.2.2 StreamConfigurationProvider
public abstract class StreamConfigurationProvider : ConfigurationProvider
{
private bool _loaded;
public StreamConfigurationSource Source { get; }
public StreamConfigurationProvider(StreamConfigurationSource source)
{
this.Source = source;
}
public abstract void Load(Stream stream);
public override void Load()
{
if (this._loaded)
{
throw new InvalidOperationException("StreamConfigurationProviders cannot be loaded more than once.");
}
this.Load(this.Source.Stream);
this._loaded = true;
}
}
具体实现类只需实现方法abstract void Load(Stream stream);
即可。
上面介绍的JSON文件、Ini文件和Xml文件还定义了StreamConfigurationSource
的版本。
4.3 Json流
JsonStreamConfigurationSource
public class JsonStreamConfigurationSource : StreamConfigurationSource
{
public override IConfigurationProvider Build(IConfigurationBuilder builder);
}
JsonStreamConfigurationProvider
public class JsonStreamConfigurationProvider : StreamConfigurationProvider
{
public override void Load(Stream stream);
}
IConfigurationBuilder扩展
public static class JsonConfigurationExtensions
{
public static IConfigurationBuilder AddJsonStream(this IConfigurationBuilder builder, Stream stream);
}
4.4 Xml流
XmlStreamConfigurationSource
public class XmlStreamConfigurationSource : StreamConfigurationSource
{
public override IConfigurationProvider Build(IConfigurationBuilder builder);
}
XmlStreamConfigurationProvider
public class XmlStreamConfigurationProvider : StreamConfigurationProvider
{
public override void Load(Stream stream);
}
IConfigurationBuilder扩展
public static class XmlConfigurationExtensions
{
public static IConfigurationBuilder AddXmlStream(this IConfigurationBuilder builder, Stream stream);
}
4.5 Ini流
IniStreamConfigurationSource
public class IniStreamConfigurationSource : StreamConfigurationSource
{
public override IConfigurationProvider Build(IConfigurationBuilder builder);
}
IniStreamConfigurationProvider
public class IniStreamConfigurationProvider : StreamConfigurationProvider
{
public override void Load(Stream stream);
}
IConfigurationBuilder扩展
public static class IniConfigurationExtensions
{
public static IConfigurationBuilder AddIniStream(this IConfigurationBuilder builder, Stream stream);
}
5. 命令行参数
多数情况下,我们习惯以命令行的形式启动一个ASP.NET Core应用,并希望通过 命令行开关(Switch) 来控制应用的一些行为。所以命令行也就成为了配置常用的来源之一。
核心类
5.1 命令行介绍
下面通过一个简单的实例说明命令行开关的几种指定方式。假设我们有一个命令exec
,并采用如下方式启动托管程序app
:
exec app {options}
通过命令行开关options
指定多个选项。总的来说,命令行开关的指定形式分为 单参数 和 双参数。
5.1.1 单参数
单参数就是采用=
将开关的名称和值通过如下方式进行指定。
- {name}={value}
- {prefix}{name}={value}
目前支持的前缀包括/
、--
和-
三种。前缀-
需要使用命令行开关映射。
示例:
exec app architecture=x64
exec app /architecture=x64
exec app --architecture=x64
5.1.2 双参数
双参数就是使用两个参数分别定义开关的名称和值。使用如下方式进行指定。
- {prefix}{name} {value}
示例:
exec app /architecture x64
exec app --architecture x64
5.1.3 开关映射
命令行开关映射(Switch Mapping) 是指开关名称的全名和缩写之间的映射关系。如果使用-
作为前缀,则必须使用映射后的开关名称。
如果我们同时把architecture
映射为a
和arch
:
exec app -a=x64
exec app -arch=x64
exec app -a x64
exec app -arch x64
5.2 实现类
CommandLineConfigurationSource
public class CommandLineConfigurationSource : IConfigurationSource
{
public IDictionary<string, string> SwitchMappings { get; set; }
public IEnumerable<string> Args { get; set; }
public IConfigurationProvider Build(IConfigurationBuilder builder)
=> new CommandLineConfigurationProvider(this.Args, this.SwitchMappings);
}
Args
: 原始命令行参数的字符串数组。SwitchMappings
: 命令行开关映射的集合。
CommandLineConfigurationProvider
public class CommandLineConfigurationProvider : ConfigurationProvider
{
public CommandLineConfigurationProvider(IEnumerable<string> args, IDictionary<string, string> switchMappings = null);
public override void Load()
{
...
}
}
IConfigurationBuilder扩展
public static class CommandLineConfigurationExtensions
{
public static IConfigurationBuilder AddCommandLine(this IConfigurationBuilder configurationBuilder, string[] args);
public static IConfigurationBuilder AddCommandLine(this IConfigurationBuilder configurationBuilder, string[] args, IDictionary<string, string> switchMappings);
public static IConfigurationBuilder AddCommandLine(this IConfigurationBuilder builder, Action<CommandLineConfigurationSource> configureSource);
}
5.3 使用示例
static void Main(string[] args)
{
var mapping = new Dictionary<string, string>
{
["-a"] = "architecture",
["-arch"] = "architecture"
};
var config = new ConfigurationBuilder()
.AddCommandLine(args, mapping)
.Build();
Console.WriteLine($"architecture={config["architecture"]}");
}
以命令行的形式启动,效果如下:
./CmdConfigurationTutorial.exe -a x64
architecture=x64
6. 环境变量
核心类
6.1 Environment
环境变量的提取和维护可以通过静态类型Environment
来完成。
public static class Environment
{
public static string? GetEnvironmentVariable(string variable, EnvironmentVariableTarget target);
public static string? GetEnvironmentVariable(string variable);
public static IDictionary GetEnvironmentVariables(EnvironmentVariableTarget target);
public static IDictionary GetEnvironmentVariables();
public static void SetEnvironmentVariable(string variable, string? value, EnvironmentVariableTarget target);
public static void SetEnvironmentVariable(string variable, string? value);
}
GetEnvironmentVariable
: 获取指定名称的环境变量的值。GetEnvironmentVariables
: 获取所有的环境变量。SetEnvironmentVariable
: 环境变量的新增、修改和删除。如果参数value
为null或空字符串则删除变量。
在没有设置target
的情况下,默认为EnvironmentVariableTarget.Process
。
环境变量分为三类:当前系统、当前用户和当前进程。
public enum EnvironmentVariableTarget
{
Process = 0,
User = 1,
Machine = 2
}
6.2 实现类
EnvironmentVariablesConfigurationSource
public class EnvironmentVariablesConfigurationSource : IConfigurationSource
{
public string Prefix { get; set; }
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new EnvironmentVariablesConfigurationProvider(this.Prefix);
}
}
Prefix
: 如果设置了Prefix
属性,系统只选择相应前缀的环境变量。
EnvironmentVariablesConfigurationProvider
public class EnvironmentVariablesConfigurationProvider : ConfigurationProvider
{
public EnvironmentVariablesConfigurationProvider(string prefix)
{
this._prefix = (prefix ?? string.Empty);
}
public override void Load()
{
this.Load(Environment.GetEnvironmentVariables());
}
internal void Load(IDictionary envVariables)
{
Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (DictionaryEntry dictionaryEntry in from entry in envVariables.Cast<DictionaryEntry>()
where ((string)entry.Key).StartsWith(this._prefix, StringComparison.OrdinalIgnoreCase)
select entry)
{
string key = ((string)dictionaryEntry.Key).Substring(this._prefix.Length);
dictionary[key] = (string)dictionaryEntry.Value;
}
base.Data = dictionary;
}
...
}
IConfigurationBuilder扩展
public static class EnvironmentVariablesExtensions
{
public static IConfigurationBuilder AddEnvironmentVariables(this IConfigurationBuilder configurationBuilder);
public static IConfigurationBuilder AddEnvironmentVariables(this IConfigurationBuilder configurationBuilder, string prefix);
public static IConfigurationBuilder AddEnvironmentVariables(this IConfigurationBuilder builder, Action<EnvironmentVariablesConfigurationSource> configureSource);
}
7. IConfigraton对象
IConfiguration
对象也可以作为数据源,这样方便开发者将多个配置系统组合在一起使用。
核心类
ChainedConfigurationSource
public class ChainedConfigurationSource : IConfigurationSource
{
public IConfiguration Configuration { get; set; }
public bool ShouldDisposeConfiguration { get; set; }
public IConfigurationProvider Build(IConfigurationBuilder builder);
}
ShouldDisposeConfiguration
属性决定:当承载当前数据源的ChainedConfigurationProvider
对象释放时,数据源Configuration
是否应该随之释放。
如果指定的IConfiguration
对象的生命周期由创建的ChainedConfigurationSource
控制,那么ShouldDisposeConfiguration
属性应该设置为true
;如果IConfiguration
对象的释放由其他对象负责,就应该设置为false
。
ChainedConfigurationProvider
public class ChainedConfigurationProvider : IConfigurationProvider, IDisposable
{
private readonly IConfiguration _config;
private readonly bool _shouldDisposeConfig;
public ChainedConfigurationProvider(ChainedConfigurationSource source)
{
this._config = source.Configuration;
this._shouldDisposeConfig = source.ShouldDisposeConfiguration;
}
public bool TryGet(string key, out string value)
{
value = this._config[key];
return !string.IsNullOrEmpty(value);
}
public void Set(string key, string value)
{
this._config[key] = value;
}
public IChangeToken GetReloadToken()
{
return this._config.GetReloadToken();
}
public void Load(){}
}
IConfigurationBuilder扩展
public static class ChainedBuilderExtensions
{
public static IConfigurationBuilder AddConfiguration(this IConfigurationBuilder configurationBuilder, IConfiguration config);
public static IConfigurationBuilder AddConfiguration(this IConfigurationBuilder configurationBuilder, IConfiguration config,
bool shouldDisposeConfiguration);
}
8. User Secrets
参考