- A+
一个应用要运行起来,往往需要读取很多的预设好的配置信息,根据约定好的信息或方式执行一定的行为。
配置的本质就是软件运行的参数,在一个软件实现中需要的参数非常多,如果我们以 Hard Code(硬编码)的方式写在应用代码中,这样配置就会很乱,而且后续也不容易修改。乱而多,而且不容易修改,这就需要一个统一管理的地方,最常见的方式就是配置文件,这个也是开发人员非常熟悉的方式。
通过配置文件设置好软件应用运行的各种参数之后,我们在开发过程中需要能够读取到配置文件的内容,根据配置内容进行软件逻辑的判断,实现完善的软件行为逻辑。这一篇就是介绍 .NET Core 框架下怎么使用配置系统,这也是 .NET Core 下的基础设施之一。
1. 配置读取
配置读取是配置系统最基本的操作,几乎是每个开发人员都会进行的操作,一个开发人员可能不清楚配置系统是怎么实现的,配置文件是怎么解析的,但一定都做过读取配置信息的操作。.NET Core 框架下对于配置系统的使用最终暴露出来的接口是 ·IConfiguration·,它是供配置数据的统一视图,配置读取就通过这个接口的实现来进行。
默认创建的 ASP .NET Core 框架模板项目中默认有一个 appsettings.json 配置文件,这个也是 ASP .NET Core 中最常用的配置文件,在配置文件中添加多一个 Settings 节点,内容如下:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "Settings": { "key1": "value1", "key2": 1, "key3": true, "key4": { "subKey1": "value", "subKey2": 1 }, "items": [ "item1", "item2", "item3" ] } }
我们要读取配置文件中的内容,例如读取“AllowedHosts”对于的值,只需要将其注入到需要的服务类中即可使用,ASP.NET Core 模板项目中使用Web主机构建和管理应用,在使用主机默认配置的时候已经将 Iconfiguration 服务注册到依赖注入容器之中。
[ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { private readonly IConfiguration _configuration; public WeatherForecastController(IConfiguration configuration) { _configuration = configuration; } [HttpGet] public Task Get() { var allowedHosts = _configuration["AllowedHosts"]; Console.WriteLine(allowedHosts); // 配置键不区分大小写 var allowedHosts = _configuration["AllowedHosts"]; Console.WriteLine(allowedHosts); return Task.CompletedTask; } }
上面这种读取方式是索引器方式,最简单也是基本的方式,配置被加载到内存中是以键值对的方式存在的,我们可以通过配置键读取配置值,键是字符串,不区分大小写,读取出来的值都是字符串。
配置文件中配置值往往不止一层,就像上面 appsettings.json 文件中,Logging 节点下还有子节点,如果需要这种分层数据,可以使用 :
字符(英文冒号)分隔层次结构,例如获取上面配置键 Default 对于的配置值。
// 以 : 作为分隔符,表示层级结构 var defalutLogLevel = _configuration["Logging:LogLevel:Default"];
如果配置值是数组,需要读取数组中具体的某一个值,可以用该值在数组中的索引作为key,例如读取上面配置文件中的items数组中的 item2。
// 读取数组,可以用值在数组中的索引作为key var item2 = _configuration["Settings:items:1"];
这种方式读取配置有挺多不方便的地方,例如配置值是数值型的时候,需要我们直接转换,例如一次只能读取到一个配置值。微软通过 Microsoft.Extensions.Configuration.Binder 中的ConfigurationBinder 类提供了一些 IConfiguration 的静态方法,用于获取配置值时进行自动转换和绑定。
(1) GetValue
通过ConfigurationBinder中的GetValue扩展方法,一样可以通过配置键从配置系统中读取对于的配置值。该方法有多个重载,支持通过泛型的方式进行数据类型转换,并且支持设置默认值。
var defaultLogLevel2 = _configuration.GetValue<string>("Logging:LogLevel:Default"); // 配置信息中不包含 "Logging:LogLevel:Default" 这个Key时,以默认值 "Error" 返回 var defaultLogLevel3 = _configuration.GetValue<string>("Logging:LogLevel:Default", "Error");
(2) GetSection
这样子有些情况下仍然无法满足我们的需要,某一些情况下我们会需要直接读取配置中的一部分节点,例如直接读取上面配置中的 LogLevel 部分。IConfiguration 中的 GetSection 方法可以通过 Key 直接读取某一个子节点。该方法的返回值是 IConfigurationSection 类型,永远不会返回 null,IConfigurationSection 实际上是一个 IConfiguration 的派生接口,也就是说我们还可以从 IConfigurationSection 再去获取我们需要的具体的配置值。
var section = _configuration.GetSection("Settings:key4"); var defaultLogLevel4 = section["Default"];
(3) Get
上面讲到通过 GetSection 获取到了配置文件中的一部分子节点,但是那样仍然不方便,还是需要一个一个去读取具体的值。可以通过 ConfigurationBinder.Get 扩展方法,将配置以强类型的方式绑定到对象上。
首先需要定义一个类来接收配置文件中的节点信息
public class KeyOptions { public string subKey1 { get; set public int subKey2 { get; set; } }
然后通过以下方式进行绑定:
var keyOption1 = _configuration.GetSection("Settings:key4").Get<KeyOptions>();
(4) Bind
ConfigurationBinder.Bind 扩展方法与 Get 方法类似,也是用于将配置绑定为强类型对象,不过Bind的方法是绑定到一个已实例化的对象上,需要提供一个已存在的对象。
var keyOption2 = new KeyOptions(); _configuration.GetSection("Settings:key4").Bind(keyOption2);
(5) Exists
上面说过,GetSection 方法获取配置中的子节点,返回值永远不会为 null。如果我们传入了一个不存在的key,肯定是获取不到对于的值的,这种情况下还是需要判断对于的子节点到底是不是真正存在的,这时候可以使用 Exists 方法。
var section = _configuration.GetSection("settings"); var exist = section.Exists();
除此之外,还有一个 GetChildren 方法,无需参数,用于获取到当前配置节点的所有直接子节点的集合。
以上就是 .NET Core 体系下配置系统读取配置的基本介绍,涉及到的类型最主要的是 IConfiguration 接口,除此之外还有上面提到的 IConfigurationSection 接口,以及 IConfigurationRoot 接口。
IConfigurationRoot 表示配置的根节点,是IConfiguration的派生接口,以下为接口的定义:
public interface IConfigurationRoot: IConfiguration{ // 存放了当前应用程序的所有配置提供程序 IEnumerable<IConfigurationProvider> Providers { get; } // 强制从配置提供程序中重载配置 void Reload(); }
这里可以看到一个关键的属性 IEnumerable
IConfigurationSection 表示配置中的子节点,也是 IConfiguration 的派生接口,以下为接口的定义:
public interface IConfigurationSection: IConfiguration{ // 该子节点在其父节点中所表示的 key,即直接对应的key string Key { get; } // 该子节点在配置中的全路径(从根节点开始,到当前节点以:符号分隔的路径) string Path { get; } // 该子节点的 value。如果该子节点是叶子节点,则Value为该节点对于的值,若其下存在子节点,则其始终为 null string Value { get; set; } }
IConfigurationSection 接口通过以上三个属性,结合 IConfiguration中的 GetChildren 方法来完整地表示的一个子节点,而 Exists 方法判断节点是否为空,就是针对 IConfigurationSection 中的 Value 属性和 GetChildren 方法来进行的。
public static class ConfigurationExtensions{ public static bool Exists(thisIConfigurationSection section){ if(section == null) { returnfalse; } returnsection.Value != null || section.GetChildren().Any(); } }
参考文章:
ASP.NET Core 中的配置 | Microsoft Learn
配置 - .NET | Microsoft Learn
理解ASP.NET Core - 配置(Configuration)
ASP.NET Core 系列:
目录:ASP.NET Core 系列总结
上一篇:ASP.NET Core - 依赖注入(四)