浅析Asp.Net Core框架IConfiguration配置
目录
- 一、建造者模式(Builder Pattern)
- 二、核心接口与配置存储本质
- 三、简易QueryString配置源实现
- 四、宿主配置与应用配置
一、建造者模式
为什么提建造者模式?在阅读.NET Core源码时,时常碰到IHostBuilder,IConfigurationBuilder,ILoggerBuilder等诸如此类带Builder名称的类/接口,起初专研时那是一头愣。知识不够,勤奋来凑,在了解到Builder模式后终于理解,明白这些Builder类是用来构建相对应类的对象,用完即毁别无他用。理解建造者模式,有助于阅读源码时发现核心接口/类,将文件分类,直指堡垒。详细建造者模式可参阅此篇文章:磁悬浮快线
二、核心接口与配置存储本质
在.NET Core中读取配置是通过IConfiguration接口,它存在于Microsoft.Extensions.Configuration.Abstractions项目中,如下图:
IConfiguration:配置访问接口
IConfigurationProvider:配置提供者接口
IConfigurationSource:配置源接口
IConfigurationRoot:配置根接口,继承IConfiguration,维护着IConfigurationProvider集合及重新加载配置
IConfigurationBuilder:IConfigurationRoot接口实例的构造者接口
1.服务容器中IConfiguration实例注册(ConfigurationRoot)
/// <summary>
/// Represents the root of an <see cref="IConfiguration"/> hierarchy. => 配置根路径
/// </summary>
public interface IConfigurationRoot : IConfiguration
{
/// <summary>
/// Force the configuration values to be reloaded from the underlying <see cref="IConfigurationProvider"/>s. => 从配置源重新加载配置
/// </summary>
void Reload();
/// <summary>
/// The <see cref="IConfigurationProvider"/>s for this configuration. => 依赖的配置源集合
/// </summary>
IEnumerable<IConfigurationProvider> Providers { get; }
}
IConfigurationRoot(继承IConfiguration)维护着一个IConfigurationProvider集合列表,也就是我们的配置源。IConfiguration实例的创建并非通过new()方式,而是由IConfigurationBuilder来构建,实现了按需加载配置源,是建造者模式的充分体现。IConfigurationBuilder上的所有操作如:
HostBuilder.ConfigureAppConfiguration((context, builder) =>
{
builder.AddCommandLine(args); // 命令行配置源
builder.AddEnvironmentVariables(); // 环境配置源
builder.AddJsonFile("demo.json"); // json文件配置源
builder.AddInMemoryCollection(); // 内存配置源
// ...
})
皆是为IConfigurationRoot.Providers做准备,最后通过Build()方法生成ConfigurationRoot实例注册到服务容器
public class HostBuilder : IHostBuilder
{
private HostBuilderContext _hostBuilderContext;
// 配置构建 委托
private List<Action<HostBuilderContext, IConfigurationBuilder>> _configureAppConfigActions = new List<Action<HostBuilderContext, IConfigurationBuilder>>();
private IConfiguration _appConfiguration;
private void BuildAppConfiguration()
{
IConfigurationBuilder configBuilder = new ConfigurationBuilder();
foreach (Action<HostBuilderContext, IConfigurationBuilder> buildAction in _configureAppConfigActions)
{
buildAction(_hostBuilderContext, configBuilder);
}
_appConfiguration = configBuilder.Build(); // 调用Build()创建IConfiguration 实例 ConfigurationRoot
_hostBuilderContext.Configuration = _appConfiguration;
}
private void CreateServiceProvider()
{
var services = new ServiceCollection();
// register configuration as factory to make it dispose with the service provider
services.AddSingleton(_ => _appConfiguration); // 注册 IConfiguration - 单例
}
}
2.IConfiguration/IConfigurationSection读取配置与配置储存本质
程序中我们会通过如下方式获取配置值(当然还有绑定IOptions)
IConfiguration["key"]
IConfiguration.GetSection("key").Value
...
而IConfiguration注册的实例是ConfigurationRoot,代码如下,其索引器实现竟是倒序遍历配置源,获取配置值。原来当我们通过IConfiguration获取配置时,其实就是倒序遍历IConfigurationBuilder加载进来的配置源。
public class ConfigurationRoot : IConfigurationRoot, IDisposable
{
private readonly IList<IConfigurationProvider> _providers;
public IEnumerable<IConfigurationProvider> Providers => _providers;
public string this[string key]
{
get
{
// 倒序遍历配置源,获取到配置 就返回,这也是配置覆盖的根本原因,后添加的相同配置会覆盖前面的
for (int i = _providers.Count - 1; i >= 0; i--)
{
IConfigurationProvider provider = _providers[i];
if (provider.TryGet(key, out string value))
{
return value;
}
}
return null;
}
}
}
那么配置数据是以什么形式存储的呢?在Microsoft.Extensions.Configuration项目中,提供了一个IConfigurationProvider默认实现存储抽象类ConfigurationProvider,部分代码如下
/// <summary>
/// Base helper class for implementing an <see cref="IConfigurationProvider"/>
/// </summary>
public abstract class ConfigurationProvider : IConfigurationProvider
{
protected ConfigurationProvider()
{
Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// The configuration key value pairs for this provider.
/// </summary>
protected IDictionary<string, string> Data { get; set; }
public virtual bool TryGet(string key, out string value)
=> Data.TryGetValue(key, out value);
/// <summary>
/// 虚方法,供具体配置源重写,加载配置到 Data中
/// </summary>
public virtual void Load() { }
}
从上可知,所有加载到程序中的配置源,其本质还是存储在Provider里面一个类型为IDictionary<string, string> Data属性中。由此推论: 当通过IConfiguration获取配置时,就是通过各个Provider的Data读取!
三、简易QueryString配置源实现
要实现自定义的配置源,只需实现IConfigurationProvider,IConfigurationSource两个接口即可,这里通过一个QueryString格式的简易配置来演示。
1.queryString.config数据格式如下
server=localhost&port=3306&datasource=demo&user=root&password=123456&charset=utf8mb4
2.实现IConfigurationSource接口(QueryStringConfiguationSource)
public class QueryStringConfiguationSource : IConfigurationSource
{
public QueryStringConfiguationSource(string path)
{
Path = path;
}
/// <summary>
/// QueryString文件相对路径
/// </summary>
public string Path { get; }
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new QueryStringConfigurationProvider(this);
}
}
3.实现IConfigurationProvider接口(QueryStringConfiguationProvider)
public class QueryStringConfigurationProvider : ConfigurationProvider
{
public QueryStringConfigurationProvider(QueryStringConfiguationSource source)
{
Source = source;
}
public QueryStringConfiguationSource Source { get; }
/// <summary>
/// 重写Load方法,将自定义的配置解析到 Data 中
/// </summary>
public override void Load()
{
// server=localhost&port=3306&datasource=demo&user=root&password=123456&charset=utf8mb4 例子格式
string queryString = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, Source.Path));
string[] arrays = queryString.Split(new[] { "&" }, StringSplitOptions.RemoveEmptyEntries); // & 号分隔
foreach (var item in arrays)
{
string[] temps = item.Split(new[] { "=" }, StringSplitOptions.RemoveEmptyEntries); // = 号分隔
if (temps.Length != 2) continue;
Data.Add(temps[0], temps[1]);
}
}
}
4.IConfigurationBuilder配置源构建
public static class QueryStringConfigurationExtensions
{
/// <summary>
/// 默认文件名称 queryString.config
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IConfigurationBuilder AddQueryStringFile(this IConfigurationBuilder builder)
=> AddQueryStringFile(builder, "queryString.config");
public static IConfigurationBuilder AddQueryStringFile(this IConfigurationBuilder builder, string path)
=> builder.Add(new QueryStringConfiguationSource(path));
}
5.Program加载配置源
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(builder =>
{
// 加载QueryString配置源
builder.AddQueryStringFile();
//builder.AddQueryStringFile("queryString.config");
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
至此,自定义QueryString配置源实现完成,便可通过IConfiguration接口获取值,结果如下
IConfiguration["server"] => localhost
IConfiguration["datasource"] => demo
IConfiguration["charset"] => utf8mb4
...
四、宿主配置与应用配置
.NET Core官方已默认提供了:环境变量、命令行参数,Json、Ini等配置源,不过适用场景却应有不同。不妨可分为两类:一类是宿主配置源,一类是应用配置源
1.宿主配置源
宿主配置源:供IHost宿主启动时使用的配置源。环境变量、命令行参数就可归为这类,以IHostEnvironment为例
/// <summary>
/// 提供运行环境相关信息
/// </summary>
public interface IHostEnvironment
{
string EnvironmentName { get; set; }
string ApplicationName { get; set; }
string ContentRootPath { get; set; }
}
IHostEnvironment接口提供了当前应用运行环境相关信息,可以通过IsEnvironment()方法判断当前运行环境是Development还是Production、Staging。
public static bool IsEnvironment(this IHostEnvironment hostEnvironment, string environmentName)
{
if (hostEnvironment == null)
{
throw new ArgumentNullException(nameof(hostEnvironment));
}
return string.Equals(hostEnvironment.EnvironmentName, environmentName, StringComparison.OrdinalIgnoreCase);
}
hostEnvironment.EnvironmentName是什么?这就得益于它注册到服务容器时所赋的值:HostBuilder
public class HostBuilder:IHostBuilder
{
private void CreateHostingEnvironment()
{
_hostingEnvironment = new HostingEnvironment()
{
ApplicationName = _hostConfiguration[HostDefaults.ApplicationKey], // _hostConfiguration 类型是 IConfiguration
EnvironmentName = _hostConfiguration[HostDefaults.EnvironmentKey] ?? Environments.Production, // 当未配置环境时,默认Production环境,在使用vs开发启动时,lanuchSetting.json 配置了 环境变量:"ASPNETCORE_ENVIRONMENT": "Development"
ContentRootPath = ResolveContentRootPath(_hostConfiguration[HostDefaults.ContentRootKey], AppContext.BaseDirectory),
};
if (string.IsNullOrEmpty(_hostingEnvironment.ApplicationName))
{
// Note GetEntryAssembly returns null for the net4x console test runner.
_hostingEnvironment.ApplicationName = Assembly.GetEntryAssembly()?.GetName().Name;
}
}
}
由此可见,IHostEnvironment所提供的信息根由仍是从IConfiguration读取,而这些配置正是来自环境变量、命令行参数配置源。
2.应用配置源
应用配置源:供应用业务逻辑使用的配置源。Json、Ini、Xml以及自定义的QueryString等就可归为类。
文件配置源配置更新原理
对于文件配置源,.NET Core默认提供了两个抽象类:FileConfigurationSource 和 FileConfigurationProvider
public abstract class FileConfigurationProvider : ConfigurationProvider, IDisposable
{
private readonly IDisposable _changeTokenRegistration;
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; }
private void Load(bool reload)
{
IFileInfo 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); // Data 被重新创建新的实例赋值了
}
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); // Data 被重新创建新的实例赋值了
}
static Stream OpenRead(IFileInfo fileInfo)
{
if (fileInfo.PhysicalPath != null)
{
// The default physical file info assumes asynchronous IO which results in unnecessary overhead
// especally since the configuration system is synchronous. This uses the same settings
// and disables async IO.
return new FileStream(
fileInfo.PhysicalPath,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite,
bufferSize: 1,
FileOptions.SequentialScan);
}
return fileInfo.CreateReadStream();
}
using Stream stream = OpenRead(file);
try
{
Load(stream);
}
catch (Exception e)
{
HandleException(ExceptionDispatchInfo.Capture(e));
}
}
}
public override void Load()
{
Load(reload: false);
}
public abstract void Load(Stream stream);
}
所有基于文件配置源(如果要监控配置文件更新,如:appsetting.json)都应实现这个两个抽象类,尽管不懂ChangeToken是个什么东东,只需明白Provider.Data 在文件变更时被重新赋值也未尝不可。
浅析Asp.Net Core框架IConfiguration配置的更多相关文章
- 如何为ASP.NET Core的强类型配置对象添加验证
原文: Adding validation to strongly typed configuration objects in ASP.NET Core 作者: Andrew Lock 译文: La ...
- 一个Mini的ASP.NET Core框架的实现
一.ASP.NET Core Mini 在2019年1月的微软技术(苏州)俱乐部成立大会上,蒋金楠老师(大内老A)分享了一个名为“ASP.NET Core框架揭秘”的课程,他用不到200行的代码实现了 ...
- 200行代码,7个对象——让你了解ASP.NET Core框架的本质
2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘>的分享.在此次分享中,我按照ASP.NET Core自身的运行原理和设计 ...
- asp.net core 系列 10 配置configuration (上)
一. ASP.NET Core 中的配置概述 ASP.NET Core 中的应用配置是基于键值对,由configuration 程序提供. configuration 将从各种配置源提供程序操作键 ...
- ASP.NET Core 框架源码地址
ASP.NET Core 框架源码地址 https://github.com/dotnet/corefx 这个是.net core的 开源项目地址 https://github.com/aspnet ...
- 了解ASP.NET Core框架的本质
了解ASP.NET Core框架的本质 ASP.NET Core自身的运行原理和设计思想创建了一个 “迷你版” 的ASP.NET Core框架,并且利用这个 “极简” 的模拟框架阐述了ASP.NET ...
- 一步步完成“迷你版” 的ASP.NET Core框架
一 前言 Artech 分享了 200行代码,7个对象--让你了解ASP.NET Core框架的本质 . 用一个极简的模拟框架阐述了ASP.NET Core框架最为核心的部分. 这里一步步来完成这个迷 ...
- 200行代码,7个对象——让你了解ASP.NET Core框架的本质
原文:200行代码,7个对象--让你了解ASP.NET Core框架的本质 2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘&g ...
- (11)ASP.NET Core 中的配置一(Configuration)
1.前言 ASP.NET Core在应用程序上引入Microsoft.Extensions.Configuration配置,可以支持多种方式配置,包括命令行配置.环境变量配置.文件配置.内存配置,自定 ...
随机推荐
- matplotlib学习日记(五)-各种饼状图的绘制
(一)分裂式饼状图 import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np mpl.rcParams[& ...
- Centos7.3 离线环境下修改时间
运行以下命令 1.tzselect --命令确定时区 2.timedatectl set-timezone Asia/Shanghai --设置系统时区为上海 3.timedatectl set-n ...
- eclipse再见,android studio 新手入门教程(二)项目的导入
上一篇博客介绍了AS的一些常用设置方法,当工具调教妥当后,自然就要开始项目的开发啦.从零开始新建一个项目,这个简单,不必多说,这篇博客会分享我从旧平台eclipse导入项目到AS的过程,以及遇到的一些 ...
- Oracle中除数为0的两种解决办法(decode与nullif)
Oracle中Decode函数,语句DECODE(tag,''ZCGS'',0,1)=decode(''@corp-No@'',''6010'',1,0) decode(字段或字段的运算,值1,值2, ...
- matplotlib中subplots的用法
1.matplotlib中如果只画一张图的话,可以直接用pyplot,一般的做法是: import matplotlib.pyplot as plt plt.figure(figsize=(20,8) ...
- Redis+LUA整合使用
.前言 从本章节开始我们就开始讲解一些 Redis 的扩展应用了,之前讲的主从.哨兵和集群都相当重要,也许小公司用不到集群这么复杂的架构,但是也要了解各知识点的原理,只要了解了原理,无论什么时候是有, ...
- tabControl组件的吸顶效果
最开始,还没有使用better-scroll插件的时候,直接在class中设定了一定的position为sticky,设置一定的top达成了效果.但是,使用better-scroll组件后,这些属性就 ...
- spring boot 集成 Apache CXF 调用 .NET 服务端 WebService
1. pom.xml加入 cxf 的依赖 <dependency> <groupId>org.apache.cxf</groupId> <artifactId ...
- 原生redis命令
一. redis-cli 连接 redis 进入redis安装目录 cd /usr/local/bin 进入redis客户端 ./redis-cli -p 6379 -h 用于指定 ip -p 用于指 ...
- Lesson_strange_words3
can 电话耳机,(头戴式听筒) determine 决定 perform 执行 regular 常规的 duty 责任 compatible 兼容 preset 预置,预设 in either or ...