导航菜单
首页 >  netERR  > 【.NET Core框架】配置(Configuration)

【.NET Core框架】配置(Configuration)

简介

.netcore中的配置模块可以将你的配置文件自动读取成一个树状结构(逻辑上是树状,实际上是扁平化的),这样你就可以方便快捷的获取配置数据了。可使用多种类型数据源(json、内存、xml、ini、command、env...),还可以自定义配置源;支持多环境版本、如果多次添加相同的配置,后添加的会覆盖之前添加的;热加载,修改配置文件后可不重启项目,重新将文件加载到内存;

github: https://github.com/aspnet/Configuration/

树状结构(逻辑上)

当配置模块完成配置构建后会返回一个树状结构,这个树状结构我们称之为IConfiguration(IConfigurationRoot和IConfigurationSection都继承自IConfiguration),它的根节点是IConfigurationRoot、子节点是IConfigurationSection。子节点里封装了三个属性(Key、Path、Value),这种树状结构和注册表的组织形式很像。

涉及到的nuget包

Microsoft.Extensions.Configuration.Abstractions: 抽象定义;Microsoft.Extensions.Configuration:配置模块顶层实现(包含从内存中提取配置);Microsoft.Extensions.Configuration.Json:从json中提取配置Microsoft.Extensions.Configuration.Xml:从xml中提取配置Microsoft.Extensions.Configuration.EnvironmentVariables:从环境变量中提取配置;Microsoft.Extensions.Configuration.CommandLine:从命令行中提取配置;Microsoft.Extensions.Configuration.Ini:从ini文件中提取配置;Microsoft.Extensions.Configuration.Binder:将配置数据绑定到对象;

添加数据源public static IHostBuilder CreateHostBuilder(string[] args) =>Host.CreateDefaultBuilder(args).ConfigureAppConfiguration((context, config) =>{var env = context.HostingEnvironment;config.AddJsonFile("connectionstring.json", optional: true, reloadOnChange: true) .AddJsonFile($"connectionstring.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);});

optional:bool类型,指示该文件是否是可选的。如果该参数为false,但是指定的文件又不存在,则会报错。reloadOnChange:bool类型,指示该文件发生更改时,是否要重新加载配置。除了json配置源还可以添加如下类型的配置源:

xmlconfig.AddXmlFile("appsettings.xml", optional: true, reloadOnChange: true);iniconfig.AddIniFile("appsettings.ini", optional: true, reloadOnChange: true);命令行config.AddCommandLine(args);

有三种设置命令行参数的方式:使用=:

dotnet run Book:Name="Command line book name"

使用/:

dotnet run /Book:Name "Command line book name"

使用--:

dotnet WebApplication5.dll --Book:Name "Command line book name"

交换映射该功能是针对命令行配置参数进行key映射的,如你可以将n映射为Name,要求:交换映射key必须以-或--开头。当使用-开头时,命令行参数书写时也要以-开头,当使用--开头时,命令行参数书写时可以以--或/开头。交换映射字典中的key不区分大小写,不能包含重复key。如不能同时出现-n和-N,但可以同时出现-n和--n接下来我们来映射一下:

public static IHostBuilder CreateHostBuilder(string[] args) =>Host.CreateDefaultBuilder(args).ConfigureAppConfiguration((context, config) =>{var switchMappings = new Dictionary{["--bn"] = "Book:Name",["-ba0"] = "Book:Authors:0",["--ba1"] = "Book:Authors:1",["--bmr"] = "Book:Bookmark:Remarks"};config.AddCommandLine(args, switchMappings);});

然后以命令行命令启动:

dotnet run --bn "Command line book name" -ba0 "Command line book author A" /ba1 "Command line book author B" --bmr="Command line bookmark remarks"环境变量// 添加前缀为 My_ 的环境变量config.AddEnvironmentVariables(prefix: "My_");

在添加环境变量时,通过指定参数prefix,只读取限定前缀的环境变量。不过在读取环境变量时,会将前缀删除。如果不指定参数prefix,那么会读取所有环境变量。当创建默认通用主机(Host)时,默认就已经添加了前缀为DOTNET_的环境变量,加载应用配置时,也添加了未限定前缀的环境变量。另外,在 ASP.NET Core 中,配置 Web主机时,默认添加了前缀为ASPNETCORE_的环境变量。需要注意的是,由于环境变量的分层键:并不受所有平台支持,而双下划线()是全平台支持的,所以要使用双下划线()来代替冒号(:),如下:

//Book__Name == Book.Nameset Book__Name "book name"内存config.AddInMemoryCollection(new Dictionary{["Book:Name"] = "Memmory book name",["Book:Authors:0"] = "Memory book author A",["Book:Authors:1"] = "Memory book author B",["Book:Bookmark:Remarks"] = "Memory bookmark remarks"});

.net core框架已经为我们添加了配置文件appsettings.json、appsettings.{EnvironmentName}.json,所以不需要重新添加

读取配置弱类型读取Configuration["Logging:Default:t1"]//按层级取值,返回值是字符串类型Configuration.GetSection("Ips").Value//返回值是字符串类型强类型读取(绑定)

虽然我们可以从IConfiguration中轻松的取出配置值,但是我们更倾向于将其转换成一个POCO对象,以面向对象的方式来使用配置,我们将这个转换过程称为配置绑定。绑定相关的部分扩展方法定义在NuGet包“Microsoft.Extensions.Configuration.Binder”中

Settings settings = Configuration.Get();//将Configuration转为Settings对象string[] ips = Configuration.GetSection("Ips").Get();//将Ips节点转为数组LogLevel logLevel = Configuration.GetSection("Logging:LogLevel").Get();//节点转为对象int CACHE_TIME = configuration.GetValue("CACHE_TIME", 20);//20是默认值,需要安装 Microsoft.Extensions.Configuration.Binder包

Bind:将Configuration绑定现有对象

Logging logging = new Logging();Configuration.GetSection("Logging").Bind(logging);自定义绑定转换器[TypeConverter(typeof(PointConvertor))]public class Point{public double X { set; get; }public double Y { set; get; }}public class PointConvertor : TypeConverter{public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType){return sourceType == typeof(string);}public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value){try{string str = value.ToString();var arr = str.Split(',');var x = double.Parse(arr[0]);var y = double.Parse(arr[1]);return new Point(){X = x,Y = y};}catch (Exception ex){Console.WriteLine($"转换出错:{ex?.Message}");return null;}}}public class Person{public Point pos { set; get; }}class Program{public static void Main(string[] args){var conf = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary(){["root:person:pos"] = "113.456784,27.234562"}).Build();var person = conf.GetSection("root:person").Get();}}绑定到字典var conf = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary(){["root:appname"] = "测试应用",["root:appversion"] = "v1.0.0",["root:person:小明:name"] = "小明",["root:person:小明:age"] = "28",["root:person:小明:id"] = "2",["root:person:小红:name"] = "小红",["root:person:小红:age"] = "24",["root:person:小红:id"] = "3",["root:student:0:id"] = "4",["root:student:0:name"] = "张三",["root:student:0:age"] = "10",["root:student:1:id"] = "5",["root:student:1:name"] = "李四",["root:student:1:age"] = "11"}).Build();var dics = conf.GetSection("root:student").Get();string json = JsonConvert.SerializeObject(dics);//{// "0":{"name":"张三","id":4,"age":10},// "1":{"name":"李四","id":5,"age":11}//}var dics2 = conf.GetSection("root:person").Get();string json2 = JsonConvert.SerializeObject(dics2);//{// "小明":{"name":"小明","id":2,"age":28},// "小红":{"name":"小红","id":3,"age":24}//}核心对象IConfiguration:配置信息最终会转换成一个IConfiguration对象供应用程序使用IConfigurationBuilder:IConfigurationBuilder是IConfiguration对象的构建者IConfigurationSource:IConfigurationSource代表配置数据的来源,它会注册到IConfigurationBuilder对象上IConfigurationProvider:向IConfiguration提供数据,IConfiguration内部包含IConfigurationProvider对象

以上对象的关系如图:

以上接口以及其他一些基础类型均定义在NuGet包“Microsoft.Extensions.Configuration.Abstractions”中。对这些接口的默认实现,则大多定义在“Microsoft.Extensions.Configuration”这个NuGet包中

IConfiguration

一个IConfiguration对象表示配置树的某个配置节点。表示根节点的IConfiguration对象与表示其它配置节点是不同的,配置模型采用不同的接口来表示它们。根节点使用IConfigurationRoot接口表示,其他节点使用IConfigurationSection接口表示,IConfigurationRoot和IConfigurationSection接口都是IConfiguration的继承者。

public interface IConfiguration{IEnumerable GetChildren();IConfigurationSection GetSection(string key);//key:相对于当前配置节的路径 IChangeToken GetReloadToken();string this[string key] { get; set; }}IConfigurationRoot

Reload方法实现对配置数据的重新加载。IConfigurationRoot对象表示的配置树的根,所以也代表了整棵配置树,如果它被重新加载了,意味着整棵配置树承载的所有配置数据均被重新加载了。IConfigurationRoot:

public interface IConfigurationRoot : IConfiguration{void Reload();}

ConfigurationRoot:

public class ConfigurationRoot : IConfigurationRoot, IDisposable{private readonly IList _providers;private readonly IList _changeTokenRegistrations;private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();/// The s for this configuration.public ConfigurationRoot(IList providers){if (providers == null){throw new ArgumentNullException(nameof(providers));}_providers = providers;_changeTokenRegistrations = new List(providers.Count);foreach (IConfigurationProvider p in providers){p.Load();_changeTokenRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged()));}}public IEnumerable 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;}set{if (!_providers.Any()){throw new InvalidOperationException(SR.Error_NoSources);}foreach (IConfigurationProvider provider in _providers){provider.Set(key, value);}}}public IEnumerable GetChildren() => this.GetChildrenImplementation(null);public IChangeToken GetReloadToken() => _changeToken;public IConfigurationSection GetSection(string key)=> new ConfigurationSection(this, key);public void Reload(){foreach (IConfigurationProvider provider in _providers){provider.Load();}RaiseChanged();}private void RaiseChanged(){ConfigurationReloadToken previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());previousToken.OnReload();}public void Dispose(){// dispose change token registrationsforeach (IDisposable registration in _changeTokenRegistrations){registration.Dispose();}// dispose providersforeach (IConfigurationProvider provider in _providers){(provider as IDisposable)?.Dispose();}}}IConfigurationSection

Key用来唯一标识多个具有相同父节点的ConfigurationSection对象Path表示当前配置节点在配置树中的路径Value表示配置节点承载的配置数据,只有配置树的叶子结点对应的IConfigurationSection对象才具有值IConfigurationSection:

public interface IConfigurationSection : IConfiguration{string Path { get; }string Key { get; }string Value { get; set; }}

ConfigurationSection:

public class ConfigurationSection : IConfigurationSection{private readonly IConfigurationRoot _root;private readonly string _path;private string _key;public ConfigurationSection(IConfigurationRoot root, string path){if (root == null){throw new ArgumentNullException(nameof(root));}if (path == null){throw new ArgumentNullException(nameof(path));}_root = root;_path = path;}/// /// 获取到此Section的完整路径/// public string Path => _path;/// /// 获取此Section在其父节点中占用的键。/// public string Key{get{if (_key == null){_key = ConfigurationPath.GetSectionKey(_path);}return _key;}}/// /// Gets or sets the section value./// public string Value{get{return _root[Path];}set{_root[Path] = value;}}public string this[string key]{get{return _root[ConfigurationPath.Combine(Path, key)];}set{_root[ConfigurationPath.Combine(Path, key)] = value;}}public IConfigurationSection GetSection(string key) => _root.GetSection(ConfigurationPath.Combine(Path, key));public IEnumerable GetChildren() => _root.GetChildrenImplementation(Path);public IChangeToken GetReloadToken() => _root.GetReloadToken();}IConfigurationProvider

IConfigurationProvider对象的目的在于将配置从原始结构转换成配置字典,配置数据的加载通过调用IConfigurationProvider的Load方法来完成。IConfigurationProvider的GetChildKeys方法用于获取某个指定配置节点(对应于parentPath参数)的所有子节点的Key。当IConfiguration的GetChildren方法被调用时,注册的所有IConfigurationSource对应的IConfigurationProvider的GetChildKeys方法会被调用。这个方法的第一个参数earlierKeys代表的Key来源于其他IConfigurationProvider,当解析出当前IConfigurationProvider提供的Key后,该方法需要对它们合并到earlierKeys集合中,合并后结果将作为方法的返回值。值得一提的是,返回的Key的集合是经过排序的。

public interface IConfigurationProvider{void Load();void Set(string key, string value);bool TryGet(string key, out string value);IEnumerable GetChildKeys(IEnumerable earlierKeys, string parentPath); IChangeToken GetReloadToken();}

ConfigurationProvider:每种类型的配置源都具有对应的IConfigurationProvider实现,它们一般不会直接实现接口IConfigurationProvider,而会选择继承另一个名为ConfigurationProvider的抽象类。这个基类把配置源里面的数据都放在了一个字典(IDictionary Data)中,这样凡是需要获取配置数据的时候就会遍历这个字典,找到后就返回。

public abstract class ConfigurationProvider : IConfigurationProvider{private ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken();protected ConfigurationProvider(){Data = new Dictionary(StringComparer.OrdinalIgnoreCase);}protected IDictionary Data { get; set; }public virtual bool TryGet(string key, out string value)=> Data.TryGetValue(key, out value);public virtual void Set(string key, string value)=> Data[key] = value;/// /// Loads (or reloads) the data for this provider./// public virtual void Load(){ }//返回此提供程序拥有的键的列表。public virtual IEnumerable GetChildKeys(IEnumerable earlierKeys,string parentPath){string prefix = parentPath == null ? string.Empty : parentPath + ConfigurationPath.KeyDelimiter;return Data.Where(kv => kv.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).Select(kv => Segment(kv.Key, prefix.Length)).Concat(earlierKeys).OrderBy(k => k, ConfigurationKeyComparer.Instance);}private static string Segment(string key, int prefixLength){int indexOf = key.IndexOf(ConfigurationPath.KeyDelimiter, prefixLength, StringComparison.OrdinalIgnoreCase);return indexOf < 0 ? key.Substring(prefixLength) : key.Substring(prefixLength, indexOf - prefixLength);}public IChangeToken GetReloadToken(){return _reloadToken;}protected void OnReload(){ConfigurationReloadToken previousToken = Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken());previousToken.OnReload();}public override string ToString() => $"{GetType().Name}";}IConfigurationSource

IConfiurationSource代表配置源,它为IConfigurationBuilder提供原始的配置数据,每种不同类型的配置源都具有一个对应的IConfigurationSource实现。由于针对原始配置数据的读取实现在相应的IConfigurationProvider中,所以IConfigurationSource所起的作用在于提供相应的IConfigurationProvider。

public interface IConfigurationSource{IConfigurationProvider Build(IConfigurationBuilder builder);}

MemoryConfigurationSource:

//内存中的配置源public class MemoryConfigurationSource : IConfigurationSource{public IEnumerable InitialData{get;set;}public IConfigurationProvider Build(IConfigurationBuilder builder){return new MemoryConfigurationProvider(this);}}IConfigurationBuilder

它通过IConfigurationSource创建IConfiguration对象IConfigurationBuilder:

public interface IConfigurationBuilder{IEnumerable Sources { get; }Dictionary Properties { get; }IConfigurationBuilder Add(IConfigurationSource source);IConfigurationRoot Build();}

ConfigurationBuilder:这个类负责搜集配置源,将配置源Build后获取到provider,最后将这些provider放进新创建的IConfigurationRoot中

public class ConfigurationBuilder : IConfigurationBuilder{public IList Sources{get;} = new List();public IDictionary Properties{get;} = new Dictionary();public IConfigurationBuilder Add(IConfigurationSource source){if (source == null){throw new ArgumentNullException("source");}Sources.Add(source);return this;}public IConfigurationRoot Build(){List list = new List();foreach (IConfigurationSource source in Sources){IConfigurationProvider item = source.Build(this);list.Add(item);}return new ConfigurationRoot(list);}}案例1、遍历数组节点{"Configs": [ {"Name": "n1" }]}private void LoadConfigs(IConfiguration configuration){var configs = configuration.GetSection("Configs");var configCount = configs.GetChildren().Count();for (int i = 0; i < configCount; i++){var config = configs.GetSection(i.ToString());string name = config.GetValue("Name");}}}2、自定义数据源

数据源是一个加密的文件

ConfigureAppConfiguration((hostingContext, config) =>{config.AddCryptoFile(Path.Combine(AppContext.BaseDirectory, "appsettings.json.crypto"), true);});

ConfigurationBuilderExtension

public static class ConfigurationBuilderExtension{public static IConfigurationBuilder AddCryptoFile(this IConfigurationBuilder configurationBuilder, string path, bool reloadOnChange){configurationBuilder.Add(new CryptoFileConfigurationSource(path, reloadOnChange));return configurationBuilder;}}

CryptoFileConfigurationSource

public class CryptoFileConfigurationSource : IConfigurationSource{/// /// 路径/// public string Path { get; set; }/// /// 是否启用热加载/// public bool ReloadOnChange { get; set; }public CryptoFileConfigurationSource(string path, bool reloadOnChange){Path = path;ReloadOnChange = reloadOnChange;}public IConfigurationProvider Build(IConfigurationBuilder builder){return new CryptoFileConfigurationProvider(this);}}

CryptoFileConfigurationProvider

public class CryptoFileConfigurationProvider : ConfigurationProvider{private static ConcurrentDictionary CONFIG_CACHE = new ConcurrentDictionary();private CryptoFileConfigurationSource _source = null;private string _random = string.Empty;public CryptoFileConfigurationProvider(CryptoFileConfigurationSource source){this._source = source;string fileName = IOHelper.GetFileNameNoPath(_source.Path);string filePath = _source.Path.Replace(fileName, string.Empty);IFileProvider fileProvider = new PhysicalFileProvider(filePath);if (_source.ReloadOnChange){ChangeToken.OnChange( () => fileProvider.Watch(fileName), () => { Load(); });}else{Load();}}public override void Load(){if (!IOHelper.IsExistFilePath(_source.Path)){throw new ArgumentException($"{_source.Path}路径不存在");}string cryptoContent = IOHelper.GetFileContent(_source.Path);//解密cryptoContent,获取jsonstring json = AESCryptoHelper.Decrypt(cryptoContent);byte[] bytes = Encoding.UTF8.GetBytes(json);MemoryStream ms = new MemoryStream(bytes);this.Data = JsonConfigurationFileParser.Parse(ms);}}

JsonConfigurationFileParser

internal class JsonConfigurationFileParser{private JsonConfigurationFileParser() { }private readonly IDictionary _data = new SortedDictionary(StringComparer.OrdinalIgnoreCase);private readonly Stack _context = new Stack();private string _currentPath;private JsonTextReader _reader;public static IDictionary Parse(Stream input)=> new JsonConfigurationFileParser().ParseStream(input);private IDictionary 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());break;case JTokenType.Array:VisitArray(token.Value());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());break;default:throw new Exception("format error");}}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 Exception("format error");}_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());}}

参考:https://www.cnblogs.com/artech/p/inside-asp-net-core-05-09.htmlhttps://blog.csdn.net/u010476739/article/details/105856032

相关推荐: