NetCore 入门 (四) : 配置数据源

1. 介绍

一般来说,定义一种配置源,需要经过如下三个步骤:

  1. [必须] 实现IConfigurationSource接口
  2. [必须] 实现IConfigurationProvider接口
  3. [可选] 在IConfigurationBuilder接口上提供Add{Source}扩展方法

配置源列表

ASP.NET Core框架提供了如下几种配置源:

Microsoft.Extensions.Configuration

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 核心类

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毫秒。如果ReloadOnChangetrue,一旦配置文件发生变化,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设置为trueFileConfigurationProvider对象会认为当前异常是可以被忽略的,程序可以继续执行。否则异常对象还是会抛出来。

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 核心类

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映射为aarch

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

参考

  1. .NET Core程序中使用User Secrets存储敏感数据
  2. ASP.NET Core 优雅的在开发环境保存机密(User Secrets)
posted @ 2022-08-27 15:14  renzhsh  阅读(547)  评论(0编辑  收藏  举报