浅析Asp.Net Core框架IConfiguration配置

目录

  • 一、建造者模式(Builder Pattern)
  • 二、核心接口与配置存储本质
  • 三、简易QueryString配置源实现
  • 四、宿主配置与应用配置
  • 五、文件配置源配置更新原理

一、建造者模式

为什么提建造者模式?在阅读.NET Core源码时,时常碰到IHostBuilder,IConfigurationBuilder,ILoggerBuilder等诸如此类带Builder名称的类/接口,起初专研时那是一头愣。知识不够,勤奋来凑,在了解到Builder模式后终于理解,明白这些Builder类是用来构建相对应类的对象,用完即毁别无他用。理解建造者模式,有助于阅读源码时发现核心接口/类,将文件分类,直指堡垒。详细建造者模式可参阅此篇文章:磁悬浮快线

二、核心接口与配置存储本质

在.NET Core中读取配置是通过IConfiguration接口,它存在于Microsoft.Extensions.Configuration.Abstractions项目中,如下图:

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默认提供了两个抽象类:FileConfigurationSourceFileConfigurationProvider

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 在文件变更时被重新赋值也未尝不可。

原文地址:https://www.cnblogs.com/GodX/p/14329675.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


数组的定义 Dim MyArray MyArray = Array(1‚5‚123‚12‚98) 可扩展数组 Dim MyArray() for i = 0 to 10
\'参数: \'code:要检测的代码 \'leixing:html或者ubb \'nopic:代码没有图片时默认值
演示效果: 代码下载: 点击下载
环境:winxp sp2 ,mysql5.0.18,mysql odbc 3.51 driver 表采用 myisam引擎。access 2003  不同的地方: 
其实说起AJAX的初级应用是非常简单的,通俗的说就是客户端(javascript)与服务端(asp或php等)脚本语言的数据交互。
<% ’判断文件名是否合法 Function isFilename(aFilename)  Dim sErrorStr,iNameLength,i  isFilename=TRUE
在调用的时候加入判断就行了. {aspcms:navlist type=0 } {if:[navlist:i]<6} < li><a href=\"[navlist:link]\" target=\"_top\">[navlist:name]</a> </li>
导航栏调用 {aspcms:navlist type=0}     <a href=\"[navlist:link]\">[navlist:name]</a>
1.引入外部文件: {aspcms:template src=infobar.html} 2.二级下拉菜单 <ul class=\"nav\">
downpic.asp页面:  <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">
Cookies是数据包,可以让网页具有记忆功能,在某台电脑上记忆一定的信息。Cookies的工作原理是,第一次由服务器端写入到客户端的系统中。以后每次访问这个网页,都是先由客户端将Cookies发送到服务器端,再由服务器端
很简单,在需要调用的地方用这种模式 {aspcms:content sort={aspcms:sortid} num=17 order=isrecommend}
网站系统使用ACCESS数据库时,查询时怎么比较日期和时间呢?为什么常常比较出来却是错误的呢?比如早的日期比迟的日期大?
str1=\"1235,12,23,34,123,21,56,74,1232\" str2=\"12\" 问题:如何判断str2是否存在str1中,要求准确找出12,不能找出str1中的1235、123、1232
实例为最新版本的kindeditor 4.1.5. 主要程序: <% Const sFileExt=\"jpg|gif|bmp|png\" Function ReplaceRemoteUrl(sHTML,sSaveFilePath,sFileExt)
用ASP实现搜索引擎的功能是一件很方便的事,可是,如何实现类似3721的智能搜索呢?比如,当在搜索条件框内输入“中国人民”时,自动从中提取“中国”、“人民”等关键字并在数据库内进行搜索。看完本文后,你就可以发
首先感谢ASPCMS官网注册用户xing0203的辛苦付出!一下为久忆YK网络转载原创作者xing0203的文章内容!为了让小白更加清楚的体验替换过程,久忆YK对原文稍作了修改!
数据库连接: <% set conn=server.createobject(\"adodb.connection\") conn.open \"driver={microsoft access driver (*.mdb)};dbq=\"&server.mappath(\"数据库名\")
第1步:修改plugins下的image/image.js 找到\'<input type=\"button\" class=\"ke-upload-button\" value=\"\' + lang.upload + \'\" />\',
asp函数: <% Const sFileExt=\"jpg|gif|bmp|png\" Function ReplaceRemoteUrl(sHTML,sSaveFilePath,sFileExt)