zoukankan      html  css  js  c++  java
  • netcore3.0 IConfiguration配置源码解析(二)

    上一篇主要讲到netcore配置的基本原理,这篇文章主要分析下netcore有哪些具体的配置源

    一、 环境变量:EnvironmentVariablesConfigurationSource和EnvironmentVariablesConfigurationProvider

      该配置源主要获取系统的环境变量配置,很简单的实现。

    public class EnvironmentVariablesConfigurationSource : IConfigurationSource
        {
            /// <summary>
            /// A prefix used to filter environment variables.
            /// </summary>
            public string Prefix { get; set; }
    
            /// <summary>
            /// Builds the <see cref="EnvironmentVariablesConfigurationProvider"/> for this source.
            /// </summary>
            /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
            /// <returns>A <see cref="EnvironmentVariablesConfigurationProvider"/></returns>
            public IConfigurationProvider Build(IConfigurationBuilder builder)
            {
                return new EnvironmentVariablesConfigurationProvider(Prefix);
            }
        }

      EnvironmentVariablesConfigurationSource类的实现很简单,有个前缀的Prefix 属性,然后构建EnvironmentVariablesConfigurationProvider  

    public class EnvironmentVariablesConfigurationProvider : ConfigurationProvider
        {
            private const string MySqlServerPrefix = "MYSQLCONNSTR_";
            private const string SqlAzureServerPrefix = "SQLAZURECONNSTR_";
            private const string SqlServerPrefix = "SQLCONNSTR_";
            private const string CustomPrefix = "CUSTOMCONNSTR_";
    
            private const string ConnStrKeyFormat = "ConnectionStrings:{0}";
            private const string ProviderKeyFormat = "ConnectionStrings:{0}_ProviderName";
    
            private readonly string _prefix;
    
            /// <summary>
            /// Initializes a new instance.
            /// </summary>
            public EnvironmentVariablesConfigurationProvider() : this(string.Empty)
            { }
    
            /// <summary>
            /// Initializes a new instance with the specified prefix.
            /// </summary>
            /// <param name="prefix">A prefix used to filter the environment variables.</param>
            public EnvironmentVariablesConfigurationProvider(string prefix)
            {
                _prefix = prefix ?? string.Empty;
            }
    
            /// <summary>
            /// Loads the environment variables.
            /// </summary>
            public override void Load()
            {
                Load(Environment.GetEnvironmentVariables());
            }
    
            internal void Load(IDictionary envVariables)
            {
                var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
    
                var filteredEnvVariables = envVariables
                    .Cast<DictionaryEntry>()
                    .SelectMany(AzureEnvToAppEnv)
                    .Where(entry => ((string)entry.Key).StartsWith(_prefix, StringComparison.OrdinalIgnoreCase));
    
                foreach (var envVariable in filteredEnvVariables)
                {
                    var key = ((string)envVariable.Key).Substring(_prefix.Length);
                    data[key] = (string)envVariable.Value;
                }
    
                Data = data;
            }
    
            private static string NormalizeKey(string key)
            {
                return key.Replace("__", ConfigurationPath.KeyDelimiter);
            }
    
            private static IEnumerable<DictionaryEntry> AzureEnvToAppEnv(DictionaryEntry entry)
            {
                var key = (string)entry.Key;
                var prefix = string.Empty;
                var provider = string.Empty;
    
                if (key.StartsWith(MySqlServerPrefix, StringComparison.OrdinalIgnoreCase))
                {
                    prefix = MySqlServerPrefix;
                    provider = "MySql.Data.MySqlClient";
                }
                else if (key.StartsWith(SqlAzureServerPrefix, StringComparison.OrdinalIgnoreCase))
                {
                    prefix = SqlAzureServerPrefix;
                    provider = "System.Data.SqlClient";
                }
                else if (key.StartsWith(SqlServerPrefix, StringComparison.OrdinalIgnoreCase))
                {
                    prefix = SqlServerPrefix;
                    provider = "System.Data.SqlClient";
                }
                else if (key.StartsWith(CustomPrefix, StringComparison.OrdinalIgnoreCase))
                {
                    prefix = CustomPrefix;
                }
                else
                {
                    entry.Key = NormalizeKey(key);
                    yield return entry;
                    yield break;
                }
    
                // Return the key-value pair for connection string
                yield return new DictionaryEntry(
                    string.Format(ConnStrKeyFormat, NormalizeKey(key.Substring(prefix.Length))),
                    entry.Value);
    
                if (!string.IsNullOrEmpty(provider))
                {
                    // Return the key-value pair for provider name
                    yield return new DictionaryEntry(
                        string.Format(ProviderKeyFormat, NormalizeKey(key.Substring(prefix.Length))),
                        provider);
                }
            }
        }

        EnvironmentVariablesConfigurationProvider继承了抽象类ConfigurationProvider,重写了Load方法

        该方法其实调用了Environment.GetEnvironmentVariables()方法获取系统的环境变量,把环境变量里面的数据加载到Data属性中。

        IConfigurationBuilder的扩展方法AddEnvironmentVariables就是添加环境变量数据源。

    二、 命令行配置:CommandLineConfigurationSource和CommandLineConfigurationProvider  

    public class CommandLineConfigurationSource : IConfigurationSource
        {
            /// <summary>
            /// Gets or sets the switch mappings.
            /// </summary>
            public IDictionary<string, string> SwitchMappings { get; set; }
    
            /// <summary>
            /// Gets or sets the command line args.
            /// </summary>
            public IEnumerable<string> Args { get; set; }
    
            /// <summary>
            /// Builds the <see cref="CommandLineConfigurationProvider"/> for this source.
            /// </summary>
            /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
            /// <returns>A <see cref="CommandLineConfigurationProvider"/></returns>
            public IConfigurationProvider Build(IConfigurationBuilder builder)
            {
                return new CommandLineConfigurationProvider(Args, SwitchMappings);
            }
        }

      其中属性Args就是命令行传递过来的数据,命令行参数的数据格式:

      key1=value1 --key2=value2 /key3=value3 --key4 value4 /key5 value5

           这种格式都可以把配置键值对解析出来

           SwitchMappings属性是把命令行的key转换成其他key的名称

      扩展方法AddCommandLine添加命令行的配置源

    三、文件配置源FileConfigurationSource和FileConfigurationProvider

    public abstract class FileConfigurationSource : IConfigurationSource
        {
            /// <summary>
            /// Used to access the contents of the file.
            /// </summary>
            public IFileProvider FileProvider { get; set; }
    
            /// <summary>
            /// The path to the file.
            /// </summary>
            public string Path { get; set; }
    
            /// <summary>
            /// Determines if loading the file is optional.
            /// </summary>
            public bool Optional { get; set; }
    
            /// <summary>
            /// Determines whether the source will be loaded if the underlying file changes.
            /// </summary>
            public bool ReloadOnChange { get; set; }
    
            /// <summary>
            /// Number of milliseconds that reload will wait before calling Load.  This helps
            /// avoid triggering reload before a file is completely written. Default is 250.
            /// </summary>
            public int ReloadDelay { get; set; } = 250;
    
            /// <summary>
            /// Will be called if an uncaught exception occurs in FileConfigurationProvider.Load.
            /// </summary>
            public Action<FileLoadExceptionContext> OnLoadException { get; set; }
    
            /// <summary>
            /// Builds the <see cref="IConfigurationProvider"/> for this source.
            /// </summary>
            /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
            /// <returns>A <see cref="IConfigurationProvider"/></returns>
            public abstract IConfigurationProvider Build(IConfigurationBuilder builder);
    
            /// <summary>
            /// Called to use any default settings on the builder like the FileProvider or FileLoadExceptionHandler.
            /// </summary>
            /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
            public void EnsureDefaults(IConfigurationBuilder builder)
            {
                FileProvider = FileProvider ?? builder.GetFileProvider();
                OnLoadException = OnLoadException ?? builder.GetFileLoadExceptionHandler();
            }
    
            /// <summary>
            /// If no file provider has been set, for absolute Path, this will creates a physical file provider 
            /// for the nearest existing directory.
            /// </summary>
            public void ResolveFileProvider()
            {
                if (FileProvider == null && 
                    !string.IsNullOrEmpty(Path) &&
                    System.IO.Path.IsPathRooted(Path))
                {
                    var directory = System.IO.Path.GetDirectoryName(Path);
                    var pathToFile = System.IO.Path.GetFileName(Path);
                    while (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
                    {
                        pathToFile = System.IO.Path.Combine(System.IO.Path.GetFileName(directory), pathToFile);
                        directory = System.IO.Path.GetDirectoryName(directory);
                    }
                    if (Directory.Exists(directory))
                    {
                        FileProvider = new PhysicalFileProvider(directory);
                        Path = pathToFile;
                    }
                }
            }
    
        }
    public abstract class FileConfigurationProvider : ConfigurationProvider, IDisposable
        {
            private readonly IDisposable _changeTokenRegistration;
    
            /// <summary>
            /// Initializes a new instance with the specified source.
            /// </summary>
            /// <param name="source">The source settings.</param>
            public FileConfigurationProvider(FileConfigurationSource source)
            {
                if (source == null)
                {
                    throw new ArgumentNullException(nameof(source));
                }
                Source = source;
    
                if (Source.ReloadOnChange && Source.FileProvider != null)
                {
                    _changeTokenRegistration = ChangeToken.OnChange(
                        () => Source.FileProvider.Watch(Source.Path),
                        () => {
                            Thread.Sleep(Source.ReloadDelay);
                            Load(reload: true);
                        });
                }
            }
    
            /// <summary>
            /// The source settings for this provider.
            /// </summary>
            public FileConfigurationSource Source { get; }
            
            /// <summary>
            /// Generates a string representing this provider name and relevant details.
            /// </summary>
            /// <returns> The configuration name. </returns>
            public override string ToString()
                => $"{GetType().Name} for '{Source.Path}' ({(Source.Optional ? "Optional" : "Required")})";
    
            private void Load(bool reload)
            {
                var file = Source.FileProvider?.GetFileInfo(Source.Path);
                if (file == null || !file.Exists)
                {
                    if (Source.Optional || reload) // Always optional on reload
                    {
                        Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
                    }
                    else
                    {
                        var error = new StringBuilder($"The configuration file '{Source.Path}' was not found and is not optional.");
                        if (!string.IsNullOrEmpty(file?.PhysicalPath))
                        {
                            error.Append($" The physical path is '{file.PhysicalPath}'.");
                        }
                        HandleException(ExceptionDispatchInfo.Capture(new FileNotFoundException(error.ToString())));
                    }
                }
                else
                {
                    // Always create new Data on reload to drop old keys
                    if (reload)
                    {
                        Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
                    }
                    using (var stream = file.CreateReadStream())
                    {
                        try
                        {
                            Load(stream);
                        }
                        catch (Exception e)
                        {
                            HandleException(ExceptionDispatchInfo.Capture(e));
                        }
                    }
                }
                // REVIEW: Should we raise this in the base as well / instead?
                OnReload();
            }
    
            /// <summary>
            /// Loads the contents of the file at <see cref="Path"/>.
            /// </summary>
            /// <exception cref="FileNotFoundException">If Optional is <c>false</c> on the source and a
            /// file does not exist at specified Path.</exception>
            public override void Load()
            {
                Load(reload: false);
            }
    
            /// <summary>
            /// Loads this provider's data from a stream.
            /// </summary>
            /// <param name="stream">The stream to read.</param>
            public abstract void Load(Stream stream);
    
            private void HandleException(ExceptionDispatchInfo info)
            {
                bool ignoreException = false;
                if (Source.OnLoadException != null)
                {
                    var exceptionContext = new FileLoadExceptionContext
                    {
                        Provider = this,
                        Exception = info.SourceException
                    };
                    Source.OnLoadException.Invoke(exceptionContext);
                    ignoreException = exceptionContext.Ignore;
                }
                if (!ignoreException)
                {
                    info.Throw();
                }
            }
    
            /// <inheritdoc />
            public void Dispose() => Dispose(true);
    
            /// <summary>
            /// Dispose the provider.
            /// </summary>
            /// <param name="disposing"><c>true</c> if invoked from <see cref="IDisposable.Dispose"/>.</param>
            protected virtual void Dispose(bool disposing)
            {
                _changeTokenRegistration?.Dispose();
            }
        }

        这两个都是抽象类,具体的实现后面介绍

        FileConfigurationSource中有个IFileProvider类型的FileProvider属性,这又涉及到netcore的文件系统,后面再开个系列文章介绍,可以简单的知道该FileProvider属性是获取对应的配置文件

        Path属性是文件的路径,Optional属性是文件是否可选的,如果是false,文件不存在时,会抛出异常。

          IConfigurationBuilder有个扩展方法GetFileProvider,该方法从IConfigurationBuilder的Properties属性中获取key为“FileProviderKey”对应的值转换成IFileProvider,当我们要自定义IConfigurationBuilder的IFileProvider实现时,可以从这里扩展,

        系统默认使用PhysicalFileProvider作为IFileProvider  

        

    public FileConfigurationProvider(FileConfigurationSource source)
            {
                if (source == null)
                {
                    throw new ArgumentNullException(nameof(source));
                }
                Source = source;
    
                if (Source.ReloadOnChange && Source.FileProvider != null)
                {
                    _changeTokenRegistration = ChangeToken.OnChange(
                        () => Source.FileProvider.Watch(Source.Path),
                        () => {
                            Thread.Sleep(Source.ReloadDelay);
                            Load(reload: true);
                        });
                }
            }
      FileConfigurationProvider构造函数里面监听配置文件是否修改,如果修改了会重新加载配置
      
    FileConfigurationProvider的Load方法会读取文件流,再调用其抽象方法Load(Stream stream)解析文件流的数据存储到Data属性中
      针对File的IConfigurationBuilder扩展方法其实蛮多的,如SetFileProvider可以设置SetFileProvider的IFileProvider,SetBasePath可以设置文件的路径

    四、 Json文件的配置源:JsonConfigurationSource和FileConfigurationProvider  
    public class JsonConfigurationSource : FileConfigurationSource
        {
            /// <summary>
            /// Builds the <see cref="JsonConfigurationProvider"/> for this source.
            /// </summary>
            /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
            /// <returns>A <see cref="JsonConfigurationProvider"/></returns>
            public override IConfigurationProvider Build(IConfigurationBuilder builder)
            {
                EnsureDefaults(builder);
                return new JsonConfigurationProvider(this);
            }
        }
    public class JsonConfigurationProvider : FileConfigurationProvider
        {
            /// <summary>
            /// Initializes a new instance with the specified source.
            /// </summary>
            /// <param name="source">The source settings.</param>
            public JsonConfigurationProvider(JsonConfigurationSource source) : base(source) { }
    
            /// <summary>
            /// Loads the JSON data from a stream.
            /// </summary>
            /// <param name="stream">The stream to read.</param>
            public override void Load(Stream stream)
            {
                try
                {
                    Data = JsonConfigurationFileParser.Parse(stream);
                }
                catch (JsonException e)
                {
                    throw new FormatException(Resources.Error_JSONParseError, e);
                }
            }
        }

      这两个对象实现很简单,主要核心是JsonConfigurationFileParser类,用来解析流

     该类用到了微软的自带json解析器JsonDocument用来解析Json对象
    internal class JsonConfigurationFileParser
        {
            private JsonConfigurationFileParser() { }
    
            private readonly IDictionary<string, string> _data = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            private readonly Stack<string> _context = new Stack<string>();
            private string _currentPath;
    
            public static IDictionary<string, string> Parse(Stream input)
                => new JsonConfigurationFileParser().ParseStream(input);
    
            private IDictionary<string, string> ParseStream(Stream input)
            {
                _data.Clear();
    
                var jsonDocumentOptions = new JsonDocumentOptions
                {
                    CommentHandling = JsonCommentHandling.Skip,
                    AllowTrailingCommas = true,
                };
    
                using (var reader = new StreamReader(input))
                using (JsonDocument doc = JsonDocument.Parse(reader.ReadToEnd(), jsonDocumentOptions))
                {
                    if (doc.RootElement.ValueKind != JsonValueKind.Object)
                    {
                        throw new FormatException(Resources.FormatError_UnsupportedJSONToken(doc.RootElement.ValueKind));
                    }
                    VisitElement(doc.RootElement);
                }
    
                return _data;
            }
    
            private void VisitElement(JsonElement element) {
                foreach (var property in element.EnumerateObject())
                {
                    EnterContext(property.Name);
                    VisitValue(property.Value);
                    ExitContext();
                }
            }
    
            private void VisitValue(JsonElement value)
            {
                switch (value.ValueKind) {
                    case JsonValueKind.Object:
                        VisitElement(value);
                        break;
    
                    case JsonValueKind.Array:
                        var index = 0;
                        foreach (var arrayElement in value.EnumerateArray()) {
                            EnterContext(index.ToString());
                            VisitValue(arrayElement);
                            ExitContext();
                            index++;
                        }
                        break;
    
                    case JsonValueKind.Number:
                    case JsonValueKind.String:
                    case JsonValueKind.True:
                    case JsonValueKind.False:
                    case JsonValueKind.Null:
                        var key = _currentPath;
                        if (_data.ContainsKey(key))
                        {
                            throw new FormatException(Resources.FormatError_KeyIsDuplicated(key));
                        }
                        _data[key] = value.ToString();
                        break;
    
                    default:
                        throw new FormatException(Resources.FormatError_UnsupportedJSONToken(value.ValueKind));
                }
            }
    
            private void EnterContext(string context)
            {
                _context.Push(context);
                _currentPath = ConfigurationPath.Combine(_context.Reverse());
            }
    
            private void ExitContext()
            {
                _context.Pop();
                _currentPath = ConfigurationPath.Combine(_context.Reverse());
            }
        }

      JsonStreamConfigurationSource和JsonStreamConfigurationProvider

    public class JsonStreamConfigurationSource : StreamConfigurationSource
        {
            /// <summary>
            /// Builds the <see cref="JsonStreamConfigurationProvider"/> for this source.
            /// </summary>
            /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
            /// <returns>An <see cref="JsonStreamConfigurationProvider"/></returns>
            public override IConfigurationProvider Build(IConfigurationBuilder builder)
                => new JsonStreamConfigurationProvider(this);
        }
    public class JsonStreamConfigurationProvider : StreamConfigurationProvider
        {
            /// <summary>
            /// Constructor.
            /// </summary>
            /// <param name="source">The <see cref="JsonStreamConfigurationSource"/>.</param>
            public JsonStreamConfigurationProvider(JsonStreamConfigurationSource source) : base(source) { }
    
            /// <summary>
            /// Loads json configuration key/values from a stream into a provider.
            /// </summary>
            /// <param name="stream">The json <see cref="Stream"/> to load configuration data from.</param>
            public override void Load(Stream stream)
            {
                Data = JsonConfigurationFileParser.Parse(stream);
            }
        }

      JsonStreamConfigurationSource继承StreamConfigurationSource,里面有个Stream类型的Stream属性,设置对应的流

      JsonStreamConfigurationProvider继承StreamConfigurationProvider,重写了Load方法,其实还是通过调用JsonConfigurationFileParser类来解析流

      IConfigurationBuilder的扩展方法AddJsonFile:可以指定IFileProvider、Json的文件路径、是否可选的、是否重新加载配置。扩展方法AddJsonStream传递流构建JsonStreamConfigurationSource

    五、 KeyPerFileConfigurationSource和KeyPerFileConfigurationProvider
      
    public class KeyPerFileConfigurationSource : IConfigurationSource
        {
            /// <summary>
            /// Constructor;
            /// </summary>
            public KeyPerFileConfigurationSource()
                => IgnoreCondition = s => IgnorePrefix != null && s.StartsWith(IgnorePrefix);
    
            /// <summary>
            /// The FileProvider whos root "/" directory files will be used as configuration data.
            /// </summary>
            public IFileProvider FileProvider { get; set; }
    
            /// <summary>
            /// Files that start with this prefix will be excluded.
            /// Defaults to "ignore.".
            /// </summary>
            public string IgnorePrefix { get; set; } = "ignore.";
    
            /// <summary>
            /// Used to determine if a file should be ignored using its name.
            /// Defaults to using the IgnorePrefix.
            /// </summary>
            public Func<string, bool> IgnoreCondition { get; set; }
    
            /// <summary>
            /// If false, will throw if the directory doesn't exist.
            /// </summary>
            public bool Optional { get; set; }
    
            /// <summary>
            /// Builds the <see cref="KeyPerFileConfigurationProvider"/> for this source.
            /// </summary>
            /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
            /// <returns>A <see cref="KeyPerFileConfigurationProvider"/></returns>
            public IConfigurationProvider Build(IConfigurationBuilder builder)
                => new KeyPerFileConfigurationProvider(this);
        }
    public class KeyPerFileConfigurationProvider : ConfigurationProvider
        {
            KeyPerFileConfigurationSource Source { get; set; }
    
            /// <summary>
            /// Initializes a new instance.
            /// </summary>
            /// <param name="source">The settings.</param>
            public KeyPerFileConfigurationProvider(KeyPerFileConfigurationSource source)
                => Source = source ?? throw new ArgumentNullException(nameof(source));
    
            private static string NormalizeKey(string key)
                => key.Replace("__", ConfigurationPath.KeyDelimiter);
    
            private static string TrimNewLine(string value)
                => value.EndsWith(Environment.NewLine)
                    ? value.Substring(0, value.Length - Environment.NewLine.Length)
                    : value;
    
            /// <summary>
            /// Loads the docker secrets.
            /// </summary>
            public override void Load()
            {
                var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
    
                if (Source.FileProvider == null)
                {
                    if (Source.Optional)
                    {
                        Data = data;
                        return;
                    }
    
                    throw new DirectoryNotFoundException("A non-null file provider for the directory is required when this source is not optional.");
                }
    
                var directory = Source.FileProvider.GetDirectoryContents("/");
                if (!directory.Exists && !Source.Optional)
                {
                    throw new DirectoryNotFoundException("The root directory for the FileProvider doesn't exist and is not optional.");
                }
    
                foreach (var file in directory)
                {
                    if (file.IsDirectory)
                    {
                        continue;
                    }
    
                    using (var stream = file.CreateReadStream())
                    using (var streamReader = new StreamReader(stream))
                    {
                        if (Source.IgnoreCondition == null || !Source.IgnoreCondition(file.Name))
                        {
                            data.Add(NormalizeKey(file.Name), TrimNewLine(streamReader.ReadToEnd()));
                        }
                    }
                }
    
                Data = data;
            }
    
            private string GetDirectoryName()
                => Source.FileProvider?.GetFileInfo("/")?.PhysicalPath ?? "<Unknown>";
    
            /// <summary>
            /// Generates a string representing this provider name and relevant details.
            /// </summary>
            /// <returns> The configuration name. </returns>
            public override string ToString()
                => $"{GetType().Name} for files in '{GetDirectoryName()}' ({(Source.Optional ? "Optional" : "Required")})";
        }

      上面实现的是读取目录下面的文件,以文件名为key,文件内容为value的配置源

      扩展方法AddKeyPerFile添加KeyPerFileConfigurationSource

    六、 NewtonsoftJsonConfigurationSource和NewtonsoftJsonConfigurationProvider

    public class NewtonsoftJsonConfigurationSource : FileConfigurationSource
        {
            /// <summary>
            /// Builds the <see cref="NewtonsoftJsonConfigurationProvider"/> for this source.
            /// </summary>
            /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
            /// <returns>A <see cref="NewtonsoftJsonConfigurationProvider"/></returns>
            public override IConfigurationProvider Build(IConfigurationBuilder builder)
            {
                EnsureDefaults(builder);
                return new NewtonsoftJsonConfigurationProvider(this);
            }
        }
    public class NewtonsoftJsonConfigurationProvider : FileConfigurationProvider
        {
            /// <summary>
            /// Initializes a new instance with the specified source.
            /// </summary>
            /// <param name="source">The source settings.</param>
            public NewtonsoftJsonConfigurationProvider(NewtonsoftJsonConfigurationSource source) : base(source) { }
    
            /// <summary>
            /// Loads the JSON data from a stream.
            /// </summary>
            /// <param name="stream">The stream to read.</param>
            public override void Load(Stream stream)
            {
                try
                {
                    Data = NewtonsoftJsonConfigurationFileParser.Parse(stream);
                }
                catch (JsonReaderException e)
                {
                    string errorLine = string.Empty;
                    if (stream.CanSeek)
                    {
                        stream.Seek(0, SeekOrigin.Begin);
    
                        IEnumerable<string> fileContent;
                        using (var streamReader = new StreamReader(stream))
                        {
                            fileContent = ReadLines(streamReader);
                            errorLine = RetrieveErrorContext(e, fileContent);
                        }
                    }
    
                    throw new FormatException(Resources.FormatError_JSONParseError(e.LineNumber, errorLine), e);
                }
            }
    
            private static string RetrieveErrorContext(JsonReaderException e, IEnumerable<string> fileContent)
            {
                string errorLine = null;
                if (e.LineNumber >= 2)
                {
                    var errorContext = fileContent.Skip(e.LineNumber - 2).Take(2).ToList();
                    // Handle situations when the line number reported is out of bounds
                    if (errorContext.Count() >= 2)
                    {
                        errorLine = errorContext[0].Trim() + Environment.NewLine + errorContext[1].Trim();
                    }
                }
                if (string.IsNullOrEmpty(errorLine))
                {
                    var possibleLineContent = fileContent.Skip(e.LineNumber - 1).FirstOrDefault();
                    errorLine = possibleLineContent ?? string.Empty;
                }
                return errorLine;
            }
    
            private static IEnumerable<string> ReadLines(StreamReader streamReader)
            {
                string line;
                do
                {
                    line = streamReader.ReadLine();
                    yield return line;
                } while (line != null);
            }
        }

      这两个也是Json文件的配置源,和上面的差不多,不同的是解析Json数据流的方式不一样

      用到NewtonsoftJsonConfigurationFileParser类来解析json

      

    internal class NewtonsoftJsonConfigurationFileParser
        {
            private NewtonsoftJsonConfigurationFileParser() { }
    
            private readonly IDictionary<string, string> _data = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            private readonly Stack<string> _context = new Stack<string>();
            private string _currentPath;
    
            private JsonTextReader _reader;
    
            public static IDictionary<string, string> Parse(Stream input)
                => new NewtonsoftJsonConfigurationFileParser().ParseStream(input);
    
            private IDictionary<string, string> ParseStream(Stream input)
            {
                _data.Clear();
                _reader = new JsonTextReader(new StreamReader(input));
                _reader.DateParseHandling = DateParseHandling.None;
    
                var jsonConfig = JObject.Load(_reader);
    
                VisitJObject(jsonConfig);
    
                return _data;
            }
    
            private void VisitJObject(JObject jObject)
            {
                foreach (var property in jObject.Properties())
                {
                    EnterContext(property.Name);
                    VisitProperty(property);
                    ExitContext();
                }
            }
    
            private void VisitProperty(JProperty property)
            {
                VisitToken(property.Value);
            }
    
            private void VisitToken(JToken token)
            {
                switch (token.Type)
                {
                    case JTokenType.Object:
                        VisitJObject(token.Value<JObject>());
                        break;
    
                    case JTokenType.Array:
                        VisitArray(token.Value<JArray>());
                        break;
    
                    case JTokenType.Integer:
                    case JTokenType.Float:
                    case JTokenType.String:
                    case JTokenType.Boolean:
                    case JTokenType.Bytes:
                    case JTokenType.Raw:
                    case JTokenType.Null:
                        VisitPrimitive(token.Value<JValue>());
                        break;
    
                    default:
                        throw new FormatException(Resources.FormatError_UnsupportedJSONToken(
                            _reader.TokenType,
                            _reader.Path,
                            _reader.LineNumber,
                            _reader.LinePosition));
                }
            }
    
            private void VisitArray(JArray array)
            {
                for (int index = 0; index < array.Count; index++)
                {
                    EnterContext(index.ToString());
                    VisitToken(array[index]);
                    ExitContext();
                }
            }
    
            private void VisitPrimitive(JValue data)
            {
                var key = _currentPath;
    
                if (_data.ContainsKey(key))
                {
                    throw new FormatException(Resources.FormatError_KeyIsDuplicated(key));
                }
                _data[key] = data.ToString(CultureInfo.InvariantCulture);
            }
    
            private void EnterContext(string context)
            {
                _context.Push(context);
                _currentPath = ConfigurationPath.Combine(_context.Reverse());
            }
    
            private void ExitContext()
            {
                _context.Pop();
                _currentPath = ConfigurationPath.Combine(_context.Reverse());
            }
        }

      主要用到Newtonsoft.Json来解析json

      对应的扩展方法:AddNewtonsoftJsonFile

      NewtonsoftJsonStreamConfigurationSource和NewtonsoftJsonStreamConfigurationProvider

    public class NewtonsoftJsonStreamConfigurationSource : StreamConfigurationSource
        {
            /// <summary>
            /// Builds the <see cref="NewtonsoftJsonStreamConfigurationProvider"/> for this source.
            /// </summary>
            /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
            /// <returns>An <see cref="NewtonsoftJsonStreamConfigurationProvider"/></returns>
            public override IConfigurationProvider Build(IConfigurationBuilder builder)
                => new NewtonsoftJsonStreamConfigurationProvider(this);
        }
    public class NewtonsoftJsonStreamConfigurationProvider : StreamConfigurationProvider
        {
            /// <summary>
            /// Constructor.
            /// </summary>
            /// <param name="source">The source of configuration.</param>
            public NewtonsoftJsonStreamConfigurationProvider(NewtonsoftJsonStreamConfigurationSource source) : base(source) { }
    
            /// <summary>
            /// Loads json configuration key/values from a stream into a provider.
            /// </summary>
            /// <param name="stream">The json <see cref="Stream"/> to load configuration data from.</param>
            public override void Load(Stream stream)
            {
                Data = NewtonsoftJsonConfigurationFileParser.Parse(stream);
            }
        }

      对应的扩展方法:AddNewtonsoftJsonStream

    七、 UserSecrets:IConfigurationBuilder有个扩展方法AddUserSecrets,系统会自动生成机密文件,保存在本地电脑上

      创建的文件名为:secrets.json

      看下核心实现代码

    public static IConfigurationBuilder AddUserSecrets(this IConfigurationBuilder configuration, Assembly assembly, bool optional, bool reloadOnChange)
            {
                if (configuration == null)
                {
                    throw new ArgumentNullException(nameof(configuration));
                }
    
                if (assembly == null)
                {
                    throw new ArgumentNullException(nameof(assembly));
                }
    
                var attribute = assembly.GetCustomAttribute<UserSecretsIdAttribute>();
                if (attribute != null)
                {
                    return AddUserSecrets(configuration, attribute.UserSecretsId, reloadOnChange);
                }
    
                if (!optional)
                {
                    throw new InvalidOperationException(Resources.FormatError_Missing_UserSecretsIdAttribute(assembly.GetName().Name));
                }
    
                return configuration;
            }

      系统会查找指定程序集上的UserSecretsIdAttribute特性,可以设置UserSecretsId属性,该值为路径的一部分。

    [AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)]
        public class UserSecretsIdAttribute : Attribute
        {
            /// <summary>
            /// Initializes an instance of <see cref="UserSecretsIdAttribute" />.
            /// </summary>
            /// <param name="userSecretId">The user secrets ID.</param>
            public UserSecretsIdAttribute(string userSecretId)
            {
                if (string.IsNullOrEmpty(userSecretId))
                {
                    throw new ArgumentException(Resources.Common_StringNullOrEmpty, nameof(userSecretId));
                }
    
                UserSecretsId = userSecretId;
            }
    
            /// <summary>
            /// The user secrets ID.
            /// </summary>
            public string UserSecretsId { get; }
        }
    public static IConfigurationBuilder AddUserSecrets(this IConfigurationBuilder configuration, string userSecretsId, bool reloadOnChange)
            {
                if (configuration == null)
                {
                    throw new ArgumentNullException(nameof(configuration));
                }
    
                if (userSecretsId == null)
                {
                    throw new ArgumentNullException(nameof(userSecretsId));
                }
    
                return AddSecretsFile(configuration, PathHelper.GetSecretsPathFromSecretsId(userSecretsId), reloadOnChange);
            }

    解析secrets.json保存的文件路径

    public class PathHelper
        {
            internal const string SecretsFileName = "secrets.json";
    
            /// <summary>
            /// <para>
            /// Returns the path to the JSON file that stores user secrets.
            /// </para>
            /// <para>
            /// This uses the current user profile to locate the secrets file on disk in a location outside of source control.
            /// </para>
            /// </summary>
            /// <param name="userSecretsId">The user secret ID.</param>
            /// <returns>The full path to the secret file.</returns>
            public static string GetSecretsPathFromSecretsId(string userSecretsId)
            {
                if (string.IsNullOrEmpty(userSecretsId))
                {
                    throw new ArgumentException(Resources.Common_StringNullOrEmpty, nameof(userSecretsId));
                }
    
                var badCharIndex = userSecretsId.IndexOfAny(Path.GetInvalidFileNameChars());
                if (badCharIndex != -1)
                {
                    throw new InvalidOperationException(
                        string.Format(
                            Resources.Error_Invalid_Character_In_UserSecrets_Id,
                            userSecretsId[badCharIndex],
                            badCharIndex));
                }
    
                const string userSecretsFallbackDir = "DOTNET_USER_SECRETS_FALLBACK_DIR";
    
                // For backwards compat, this checks env vars first before using Env.GetFolderPath
                var appData = Environment.GetEnvironmentVariable("APPDATA");
                var root = appData                                                                   // On Windows it goes to %APPDATA%MicrosoftUserSecrets
                           ?? Environment.GetEnvironmentVariable("HOME")                             // On Mac/Linux it goes to ~/.microsoft/usersecrets/
                           ?? Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) 
                           ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)
                           ?? Environment.GetEnvironmentVariable(userSecretsFallbackDir);            // this fallback is an escape hatch if everything else fails
    
                if (string.IsNullOrEmpty(root))
                {
                    throw new InvalidOperationException("Could not determine an appropriate location for storing user secrets. Set the " + userSecretsFallbackDir + " environment variable to a folder where user secrets should be stored.");
                }
    
                return !string.IsNullOrEmpty(appData)
                    ? Path.Combine(root, "Microsoft", "UserSecrets", userSecretsId, SecretsFileName)
                    : Path.Combine(root, ".microsoft", "usersecrets", userSecretsId, SecretsFileName);
            }
        }

     右键项目有个“管理用户机密”选项,点击该选项后,vs会自动生成secrets.json的文件,可以把自己的一些配置信息写入到该文件中。该文件会保存在系统指定目录下,而不再项目目录下面,当提交项目代码时,该配置文件不会被提交,其他人也看不到,应该起到隔离的作用。

    八、 Xml文件配置源:XmlConfigurationSource和XmlConfigurationProvider

      解析Xml文件的配置信息

    public class XmlConfigurationSource : FileConfigurationSource
        {
            /// <summary>
            /// Builds the <see cref="XmlConfigurationProvider"/> for this source.
            /// </summary>
            /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
            /// <returns>A <see cref="XmlConfigurationProvider"/></returns>
            public override IConfigurationProvider Build(IConfigurationBuilder builder)
            {
                EnsureDefaults(builder);
                return new XmlConfigurationProvider(this);
            }
        }
     public class XmlConfigurationProvider : FileConfigurationProvider
        {
            /// <summary>
            /// Initializes a new instance with the specified source.
            /// </summary>
            /// <param name="source">The source settings.</param>
            public XmlConfigurationProvider(XmlConfigurationSource source) : base(source) { }
    
            internal XmlDocumentDecryptor Decryptor { get; set; } = XmlDocumentDecryptor.Instance;
    
            /// <summary>
            /// Loads the XML data from a stream.
            /// </summary>
            /// <param name="stream">The stream to read.</param>
            public override void Load(Stream stream)
            {
                Data = XmlStreamConfigurationProvider.Read(stream, Decryptor);
            }
        }

      XmlStreamConfigurationSource和XmlStreamConfigurationProvider

    public class XmlStreamConfigurationSource : StreamConfigurationSource
        {
            /// <summary>
            /// Builds the <see cref="XmlStreamConfigurationProvider"/> for this source.
            /// </summary>
            /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
            /// <returns>An <see cref="XmlStreamConfigurationProvider"/></returns>
            public override IConfigurationProvider Build(IConfigurationBuilder builder)
                => new XmlStreamConfigurationProvider(this);
        }
    /// <summary>
        /// An XML file based <see cref="IConfigurationProvider"/>.
        /// </summary>
        public class XmlStreamConfigurationProvider : StreamConfigurationProvider
        {
            private const string NameAttributeKey = "Name";
    
            /// <summary>
            /// Constructor.
            /// </summary>
            /// <param name="source">The <see cref="XmlStreamConfigurationSource"/>.</param>
            public XmlStreamConfigurationProvider(XmlStreamConfigurationSource source) : base(source) { }
    
            /// <summary>
            /// Read a stream of INI values into a key/value dictionary.
            /// </summary>
            /// <param name="stream">The stream of INI data.</param>
            /// <param name="decryptor">The <see cref="XmlDocumentDecryptor"/> to use to decrypt.</param>
            /// <returns>The <see cref="IDictionary{String, String}"/> which was read from the stream.</returns>
            public static IDictionary<string, string> Read(Stream stream, XmlDocumentDecryptor decryptor)
            {
                var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
    
                var readerSettings = new XmlReaderSettings()
                {
                    CloseInput = false, // caller will close the stream
                    DtdProcessing = DtdProcessing.Prohibit,
                    IgnoreComments = true,
                    IgnoreWhitespace = true
                };
    
                using (var reader = decryptor.CreateDecryptingXmlReader(stream, readerSettings))
                {
                    var prefixStack = new Stack<string>();
    
                    SkipUntilRootElement(reader);
    
                    // We process the root element individually since it doesn't contribute to prefix
                    ProcessAttributes(reader, prefixStack, data, AddNamePrefix);
                    ProcessAttributes(reader, prefixStack, data, AddAttributePair);
    
                    var preNodeType = reader.NodeType;
                    while (reader.Read())
                    {
                        switch (reader.NodeType)
                        {
                            case XmlNodeType.Element:
                                prefixStack.Push(reader.LocalName);
                                ProcessAttributes(reader, prefixStack, data, AddNamePrefix);
                                ProcessAttributes(reader, prefixStack, data, AddAttributePair);
    
                                // If current element is self-closing
                                if (reader.IsEmptyElement)
                                {
                                    prefixStack.Pop();
                                }
                                break;
    
                            case XmlNodeType.EndElement:
                                if (prefixStack.Any())
                                {
                                    // If this EndElement node comes right after an Element node,
                                    // it means there is no text/CDATA node in current element
                                    if (preNodeType == XmlNodeType.Element)
                                    {
                                        var key = ConfigurationPath.Combine(prefixStack.Reverse());
                                        data[key] = string.Empty;
                                    }
    
                                    prefixStack.Pop();
                                }
                                break;
    
                            case XmlNodeType.CDATA:
                            case XmlNodeType.Text:
                                {
                                    var key = ConfigurationPath.Combine(prefixStack.Reverse());
                                    if (data.ContainsKey(key))
                                    {
                                        throw new FormatException(Resources.FormatError_KeyIsDuplicated(key,
                                            GetLineInfo(reader)));
                                    }
    
                                    data[key] = reader.Value;
                                    break;
                                }
                            case XmlNodeType.XmlDeclaration:
                            case XmlNodeType.ProcessingInstruction:
                            case XmlNodeType.Comment:
                            case XmlNodeType.Whitespace:
                                // Ignore certain types of nodes
                                break;
    
                            default:
                                throw new FormatException(Resources.FormatError_UnsupportedNodeType(reader.NodeType,
                                    GetLineInfo(reader)));
                        }
                        preNodeType = reader.NodeType;
                        // If this element is a self-closing element,
                        // we pretend that we just processed an EndElement node
                        // because a self-closing element contains an end within itself
                        if (preNodeType == XmlNodeType.Element &&
                            reader.IsEmptyElement)
                        {
                            preNodeType = XmlNodeType.EndElement;
                        }
                    }
                }
                return data;
            }
    
            /// <summary>
            /// Loads XML configuration key/values from a stream into a provider.
            /// </summary>
            /// <param name="stream">The <see cref="Stream"/> to load ini configuration data from.</param>
            public override void Load(Stream stream)
            {
                Data = Read(stream, XmlDocumentDecryptor.Instance);
            }
    
            private static void SkipUntilRootElement(XmlReader reader)
            {
                while (reader.Read())
                {
                    if (reader.NodeType != XmlNodeType.XmlDeclaration &&
                        reader.NodeType != XmlNodeType.ProcessingInstruction)
                    {
                        break;
                    }
                }
            }
    
            private static string GetLineInfo(XmlReader reader)
            {
                var lineInfo = reader as IXmlLineInfo;
                return lineInfo == null ? string.Empty :
                    Resources.FormatMsg_LineInfo(lineInfo.LineNumber, lineInfo.LinePosition);
            }
    
            private static void ProcessAttributes(XmlReader reader, Stack<string> prefixStack, IDictionary<string, string> data,
                Action<XmlReader, Stack<string>, IDictionary<string, string>, XmlWriter> act, XmlWriter writer = null)
            {
                for (int i = 0; i < reader.AttributeCount; i++)
                {
                    reader.MoveToAttribute(i);
    
                    // If there is a namespace attached to current attribute
                    if (!string.IsNullOrEmpty(reader.NamespaceURI))
                    {
                        throw new FormatException(Resources.FormatError_NamespaceIsNotSupported(GetLineInfo(reader)));
                    }
    
                    act(reader, prefixStack, data, writer);
                }
    
                // Go back to the element containing the attributes we just processed
                reader.MoveToElement();
            }
    
            // The special attribute "Name" only contributes to prefix
            // This method adds a prefix if current node in reader represents a "Name" attribute
            private static void AddNamePrefix(XmlReader reader, Stack<string> prefixStack,
                IDictionary<string, string> data, XmlWriter writer)
            {
                if (!string.Equals(reader.LocalName, NameAttributeKey, StringComparison.OrdinalIgnoreCase))
                {
                    return;
                }
    
                // If current element is not root element
                if (prefixStack.Any())
                {
                    var lastPrefix = prefixStack.Pop();
                    prefixStack.Push(ConfigurationPath.Combine(lastPrefix, reader.Value));
                }
                else
                {
                    prefixStack.Push(reader.Value);
                }
            }
    
            // Common attributes contribute to key-value pairs
            // This method adds a key-value pair if current node in reader represents a common attribute
            private static void AddAttributePair(XmlReader reader, Stack<string> prefixStack,
                IDictionary<string, string> data, XmlWriter writer)
            {
                prefixStack.Push(reader.LocalName);
                var key = ConfigurationPath.Combine(prefixStack.Reverse());
                if (data.ContainsKey(key))
                {
                    throw new FormatException(Resources.FormatError_KeyIsDuplicated(key, GetLineInfo(reader)));
                }
    
                data[key] = reader.Value;
                prefixStack.Pop();
            }
        }

        扩展方法:AddXmlFile和AddXmlStream

    九、 Ini文件配置源:IniConfigurationSource和IniConfigurationProvider

    /// <summary>
        /// Represents an INI file as an <see cref="IConfigurationSource"/>.
        /// Files are simple line structures (<a href="https://en.wikipedia.org/wiki/INI_file">INI Files on Wikipedia</a>)
        /// </summary>
        /// <examples>
        /// [Section:Header]
        /// key1=value1
        /// key2 = " value2 "
        /// ; comment
        /// # comment
        /// / comment
        /// </examples>
        public class IniConfigurationSource : FileConfigurationSource
        {
            /// <summary>
            /// Builds the <see cref="IniConfigurationProvider"/> for this source.
            /// </summary>
            /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
            /// <returns>An <see cref="IniConfigurationProvider"/></returns>
            public override IConfigurationProvider Build(IConfigurationBuilder builder)
            {
                EnsureDefaults(builder);
                return new IniConfigurationProvider(this);
            }
        }
    /// <summary>
        /// An INI file based <see cref="ConfigurationProvider"/>.
        /// Files are simple line structures (<a href="https://en.wikipedia.org/wiki/INI_file">INI Files on Wikipedia</a>)
        /// </summary>
        /// <examples>
        /// [Section:Header]
        /// key1=value1
        /// key2 = " value2 "
        /// ; comment
        /// # comment
        /// / comment
        /// </examples>
        public class IniConfigurationProvider : FileConfigurationProvider
        {
            /// <summary>
            /// Initializes a new instance with the specified source.
            /// </summary>
            /// <param name="source">The source settings.</param>
            public IniConfigurationProvider(IniConfigurationSource source) : base(source) { }
    
            /// <summary>
            /// Loads the INI data from a stream.
            /// </summary>
            /// <param name="stream">The stream to read.</param>
            public override void Load(Stream stream)
                => Data = IniStreamConfigurationProvider.Read(stream);
        }

      IniStreamConfigurationSource和IniStreamConfigurationProvider

     /// <summary>
        /// Represents an INI file as an <see cref="IConfigurationSource"/>.
        /// Files are simple line structures (<a href="https://en.wikipedia.org/wiki/INI_file">INI Files on Wikipedia</a>)
        /// </summary>
        /// <examples>
        /// [Section:Header]
        /// key1=value1
        /// key2 = " value2 "
        /// ; comment
        /// # comment
        /// / comment
        /// </examples>
        public class IniStreamConfigurationSource : StreamConfigurationSource
        {
            /// <summary>
            /// Builds the <see cref="IniConfigurationProvider"/> for this source.
            /// </summary>
            /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
            /// <returns>An <see cref="IniConfigurationProvider"/></returns>
            public override IConfigurationProvider Build(IConfigurationBuilder builder)
                => new IniStreamConfigurationProvider(this);
        }
    public class IniStreamConfigurationProvider : StreamConfigurationProvider
        {
            /// <summary>
            /// Constructor.
            /// </summary>
            /// <param name="source">The <see cref="IniStreamConfigurationSource"/>.</param>
            public IniStreamConfigurationProvider(IniStreamConfigurationSource source) : base(source) { }
    
            /// <summary>
            /// Read a stream of INI values into a key/value dictionary.
            /// </summary>
            /// <param name="stream">The stream of INI data.</param>
            /// <returns>The <see cref="IDictionary{String, String}"/> which was read from the stream.</returns>
            public static IDictionary<string, string> Read(Stream stream)
            {
                var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
                using (var reader = new StreamReader(stream))
                {
                    var sectionPrefix = string.Empty;
    
                    while (reader.Peek() != -1)
                    {
                        var rawLine = reader.ReadLine();
                        var line = rawLine.Trim();
    
                        // Ignore blank lines
                        if (string.IsNullOrWhiteSpace(line))
                        {
                            continue;
                        }
                        // Ignore comments
                        if (line[0] == ';' || line[0] == '#' || line[0] == '/')
                        {
                            continue;
                        }
                        // [Section:header]
                        if (line[0] == '[' && line[line.Length - 1] == ']')
                        {
                            // remove the brackets
                            sectionPrefix = line.Substring(1, line.Length - 2) + ConfigurationPath.KeyDelimiter;
                            continue;
                        }
    
                        // key = value OR "value"
                        int separator = line.IndexOf('=');
                        if (separator < 0)
                        {
                            throw new FormatException(Resources.FormatError_UnrecognizedLineFormat(rawLine));
                        }
    
                        string key = sectionPrefix + line.Substring(0, separator).Trim();
                        string value = line.Substring(separator + 1).Trim();
    
                        // Remove quotes
                        if (value.Length > 1 && value[0] == '"' && value[value.Length - 1] == '"')
                        {
                            value = value.Substring(1, value.Length - 2);
                        }
    
                        if (data.ContainsKey(key))
                        {
                            throw new FormatException(Resources.FormatError_KeyIsDuplicated(key));
                        }
    
                        data[key] = value;
                    }
                }
                return data;
            }
    
            /// <summary>
            /// Loads INI configuration key/values from a stream into a provider.
            /// </summary>
            /// <param name="stream">The <see cref="Stream"/> to load ini configuration data from.</param>
            public override void Load(Stream stream)
            {
                Data = Read(stream);
            }
        }

      扩展方法:AddIniFile

    九、 Azure

      AzureKeyVaultConfigurationSource和AzureKeyVaultConfigurationProvider

      

    internal class AzureKeyVaultConfigurationSource : IConfigurationSource
        {
            private readonly AzureKeyVaultConfigurationOptions _options;
    
            public AzureKeyVaultConfigurationSource(AzureKeyVaultConfigurationOptions options)
            {
                _options = options;
            }
    
            /// <inheritdoc />
            public IConfigurationProvider Build(IConfigurationBuilder builder)
            {
                return new AzureKeyVaultConfigurationProvider(new KeyVaultClientWrapper(_options.Client), _options.Vault, _options.Manager, _options.ReloadInterval);
            }
        }
    public class AzureKeyVaultConfigurationOptions
        {
            /// <summary>
            /// Creates a new instance of <see cref="AzureKeyVaultConfigurationOptions"/>.
            /// </summary>
            public AzureKeyVaultConfigurationOptions()
            {
                Manager = DefaultKeyVaultSecretManager.Instance;
            }
    
            /// <summary>
            /// Creates a new instance of <see cref="AzureKeyVaultConfigurationOptions"/>.
            /// </summary>
            /// <param name="vault">Azure KeyVault uri.</param>
            /// <param name="clientId">The application client id.</param>
            /// <param name="certificate">The <see cref="X509Certificate2"/> to use for authentication.</param>
            public AzureKeyVaultConfigurationOptions(
                string vault,
                string clientId,
                X509Certificate2 certificate) : this()
            {
                KeyVaultClient.AuthenticationCallback authenticationCallback =
                    (authority, resource, scope) => GetTokenFromClientCertificate(authority, resource, clientId, certificate);
    
                Vault = vault;
                Client = new KeyVaultClient(authenticationCallback);
            }
    
            /// <summary>
            /// Creates a new instance of <see cref="AzureKeyVaultConfigurationOptions"/>.
            /// </summary>
            /// <param name="vault">The Azure KeyVault uri.</param>
            public AzureKeyVaultConfigurationOptions(string vault) : this()
            {
                var azureServiceTokenProvider = new AzureServiceTokenProvider();
                var authenticationCallback = new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback);
    
                Vault = vault;
                Client = new KeyVaultClient(authenticationCallback);
            }
    
            /// <summary>
            /// Creates a new instance of <see cref="AzureKeyVaultConfigurationOptions"/>.
            /// </summary>
            /// <param name="vault">The Azure KeyVault uri.</param>
            /// <param name="clientId">The application client id.</param>
            /// <param name="clientSecret">The client secret to use for authentication.</param>
            public AzureKeyVaultConfigurationOptions(
                string vault,
                string clientId,
                string clientSecret) : this()
            {
                if (clientId == null)
                {
                    throw new ArgumentNullException(nameof(clientId));
                }
                if (clientSecret == null)
                {
                    throw new ArgumentNullException(nameof(clientSecret));
                }
    
                KeyVaultClient.AuthenticationCallback authenticationCallback =
                    (authority, resource, scope) => GetTokenFromClientSecret(authority, resource, clientId, clientSecret);
    
                Vault = vault;
                Client = new KeyVaultClient(authenticationCallback);
            }
    
            /// <summary>
            /// Gets or sets the <see cref="KeyVaultClient"/> to use for retrieving values.
            /// </summary>
            public KeyVaultClient Client { get; set; }
    
            /// <summary>
            /// Gets or sets the vault uri.
            /// </summary>
            public string Vault { get; set; }
    
            /// <summary>
            /// Gets or sets the <see cref="IKeyVaultSecretManager"/> instance used to control secret loading.
            /// </summary>
            public IKeyVaultSecretManager Manager { get; set; }
    
            /// <summary>
            /// Gets or sets the timespan to wait between attempts at polling the Azure KeyVault for changes. <code>null</code> to disable reloading.
            /// </summary>
            public TimeSpan? ReloadInterval { get; set; }
    
            private static async Task<string> GetTokenFromClientCertificate(string authority, string resource, string clientId, X509Certificate2 certificate)
            {
                var authContext = new AuthenticationContext(authority);
                var result = await authContext.AcquireTokenAsync(resource, new ClientAssertionCertificate(clientId, certificate));
                return result.AccessToken;
            }
    
            private static async Task<string> GetTokenFromClientSecret(string authority, string resource, string clientId, string clientSecret)
            {
                var authContext = new AuthenticationContext(authority);
                var clientCred = new ClientCredential(clientId, clientSecret);
                var result = await authContext.AcquireTokenAsync(resource, clientCred);
                return result.AccessToken;
            }
        }
    internal class AzureKeyVaultConfigurationProvider : ConfigurationProvider, IDisposable
        {
            private readonly TimeSpan? _reloadInterval;
            private readonly IKeyVaultClient _client;
            private readonly string _vault;
            private readonly IKeyVaultSecretManager _manager;
            private Dictionary<string, LoadedSecret> _loadedSecrets;
            private Task _pollingTask;
            private readonly CancellationTokenSource _cancellationToken;
    
            /// <summary>
            /// Creates a new instance of <see cref="AzureKeyVaultConfigurationProvider"/>.
            /// </summary>
            /// <param name="client">The <see cref="KeyVaultClient"/> to use for retrieving values.</param>
            /// <param name="vault">Azure KeyVault uri.</param>
            /// <param name="manager">The <see cref="IKeyVaultSecretManager"/> to use in managing values.</param>
            /// <param name="reloadInterval">The timespan to wait in between each attempt at polling the Azure KeyVault for changes. Default is null which indicates no reloading.</param>
            public AzureKeyVaultConfigurationProvider(IKeyVaultClient client, string vault, IKeyVaultSecretManager manager, TimeSpan? reloadInterval = null)
            {
                _client = client ?? throw new ArgumentNullException(nameof(client));
                _vault = vault ?? throw new ArgumentNullException(nameof(vault));
                _manager = manager ?? throw new ArgumentNullException(nameof(manager));
                if (reloadInterval != null && reloadInterval.Value <= TimeSpan.Zero)
                {
                    throw new ArgumentOutOfRangeException(nameof(reloadInterval), reloadInterval, nameof(reloadInterval) + " must be positive.");
                }
    
                _pollingTask = null;
                _cancellationToken = new CancellationTokenSource();
                _reloadInterval = reloadInterval;
            }
    
            /// <summary>
            /// Load secrets into this provider.
            /// </summary>
            public override void Load() => LoadAsync().ConfigureAwait(false).GetAwaiter().GetResult();
    
            private async Task PollForSecretChangesAsync()
            {
                while (!_cancellationToken.IsCancellationRequested)
                {
                    await WaitForReload();
                    try
                    {
                        await LoadAsync();
                    }
                    catch (Exception)
                    {
                        // Ignore
                    }
                }
            }
    
            protected virtual async Task WaitForReload()
            {
                await Task.Delay(_reloadInterval.Value, _cancellationToken.Token);
            }
    
            private async Task LoadAsync()
            {
                var secretPage = await _client.GetSecretsAsync(_vault).ConfigureAwait(false);
                var allSecrets = new List<SecretItem>(secretPage.Count());
                do
                {
                    allSecrets.AddRange(secretPage.ToList());
                    secretPage = secretPage.NextPageLink != null ?
                        await _client.GetSecretsNextAsync(secretPage.NextPageLink).ConfigureAwait(false) :
                         null;
                } while (secretPage != null);
    
                var tasks = new List<Task<SecretBundle>>();
                var newLoadedSecrets = new Dictionary<string, LoadedSecret>();
                var oldLoadedSecrets = Interlocked.Exchange(ref _loadedSecrets, null);
    
                foreach (var secret in allSecrets)
                {
                    if (!_manager.Load(secret) || secret.Attributes?.Enabled != true)
                    {
                        continue;
                    }
    
                    var secretId = secret.Identifier.BaseIdentifier;
                    if (oldLoadedSecrets != null &&
                        oldLoadedSecrets.TryGetValue(secretId, out var existingSecret) &&
                        existingSecret.IsUpToDate(secret.Attributes.Updated))
                    {
                        oldLoadedSecrets.Remove(secretId);
                        newLoadedSecrets.Add(secretId, existingSecret);
                    }
                    else
                    {
                        tasks.Add(_client.GetSecretAsync(secretId));
                    }
                }
    
                await Task.WhenAll(tasks).ConfigureAwait(false);
    
                foreach (var task in tasks)
                {
                    var secretBundle = task.Result;
                    newLoadedSecrets.Add(secretBundle.SecretIdentifier.BaseIdentifier, new LoadedSecret(_manager.GetKey(secretBundle), secretBundle.Value, secretBundle.Attributes.Updated));
                }
    
                _loadedSecrets = newLoadedSecrets;
    
                // Reload is needed if we are loading secrets that were not loaded before or
                // secret that was loaded previously is not available anymore
                if (tasks.Any() || oldLoadedSecrets?.Any() == true)
                {
                    SetData(_loadedSecrets, fireToken: oldLoadedSecrets != null);
                }
    
                // schedule a polling task only if none exists and a valid delay is specified
                if (_pollingTask == null && _reloadInterval != null)
                {
                    _pollingTask = PollForSecretChangesAsync();
                }
            }
    
            private void SetData(Dictionary<string, LoadedSecret> loadedSecrets, bool fireToken)
            {
                var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
                foreach (var secretItem in loadedSecrets)
                {
                    data.Add(secretItem.Value.Key, secretItem.Value.Value);
                }
    
                Data = data;
                if (fireToken)
                {
                    OnReload();
                }
            }
    
            /// <inheritdoc/>
            public void Dispose()
            {
                _cancellationToken.Cancel();
            }
    
            private readonly struct LoadedSecret
            {
                public LoadedSecret(string key, string value, DateTime? updated)
                {
                    Key = key;
                    Value = value;
                    Updated = updated;
                }
    
                public string Key { get; }
                public string Value { get; }
                public DateTime? Updated { get; }
    
                public bool IsUpToDate(DateTime? updated)
                {
                    if (updated.HasValue != Updated.HasValue)
                    {
                        return false;
                    }
    
                    return updated.GetValueOrDefault() == Updated.GetValueOrDefault();
                }
            }
        }

      扩展方法:AddAzureKeyVault

    这篇主要讲了netcore下面的各种数据配置源:

    环境变量、命令行、Ini文件、json文件、Xml文件等,其实就是把对应的数据解析成键值对保存到Provider的Data属性中。


      
  • 相关阅读:
    Flume(二)Flume的Source类型
    Hadoop(四)HDFS的高级API操作
    Flume(一)Flume的基础介绍与安装
    MySQL 的索引优化
    CentOS7.5安装Mysql5.7.22
    Hadoop(三)HDFS读写原理与shell命令
    iOS NSDictionary JSON 相互转换
    iOS UICollectionView 在滚动时停在某个item位置上
    vim的基本使用方法
    初次使用git上传代码到github远程仓库
  • 原文地址:https://www.cnblogs.com/lanpingwang/p/12538507.html
Copyright © 2011-2022 走看看