结构化配置
本篇将记录.Net Core里颇有特色的结构化配置的使用方法。
相比较之前通过Web.Config或者App.Config配置文件里使用xml节点定义配置内容的方式,.Net Core在配置系统上发生了很大的变化,具有了配置源多样化、更加轻量、扩展性更好的特点。
第一部分、基于键值对的配置
如果要使用结构化配置,需要先引用 "Microsoft.Extensions.Configuration": "1.0.0" nuget包。下面通过一个.Net Core控制台程序演示
一、单层结构的配置
1 using Microsoft.Extensions.Configuration; 2 using Microsoft.Extensions.Configuration.Memory; 3 using System; 4 using System.Collections.Generic; 5 6 namespace DiApplicationTest 7 { 8 public class Program 9 { 10 public static void Main(string[] args) 11 { 12 IDictionary<string, string> source = new Dictionary<string, string>() 13 { 14 ["Ele1"] = "value1", 15 ["Ele2"] = "value2" 16 }; 17 18 IConfiguration config = new ConfigurationBuilder().Add(new MemoryConfigurationSource() { InitialData = source }).Build(); 19 Console.WriteLine($"Ele1: {config["Ele1"]}"); 20 Console.WriteLine($"Ele2: {config["Ele2"]}"); 21 22 Console.ReadLine(); 23 } 24 } 25 }
启动调试,查看cmd窗口输出内容
上面的例子里,将一个字典作为配置源读入到config配置对象里,通过键可以读取到对应的值。
二、多层结构的配置
在大多数情况下,项目里的配置都是多层结构的,也可以称为是结构化的。在上面的例子的基础上稍加修改,实现结构化的配置读取。
1 using Microsoft.Extensions.Configuration; 2 using Microsoft.Extensions.Configuration.Memory; 3 using System; 4 using System.Collections.Generic; 5 6 namespace DiApplicationTest 7 { 8 public class Program 9 { 10 public static void Main(string[] args) 11 { 12 IDictionary<string, string> source = new Dictionary<string, string>() 13 { 14 ["Ele1"] = "value1", 15 ["Ele2:Sub1"] = "value2.1", 16 ["Ele2:Sub2"] = "value2.2" 17 }; 18 19 IConfiguration config = new ConfigurationBuilder().Add(new MemoryConfigurationSource() { InitialData = source }).Build(); 20 Console.WriteLine($"Ele1: {config["Ele1"]}"); 21 Console.WriteLine($"Ele2.Sub1: {config.GetSection("Ele2")["Sub1"]}"); 22 Console.WriteLine($"Ele2.Sub2: {config.GetSection("Ele2")["Sub2"]}"); 23 24 Console.ReadLine(); 25 } 26 } 27 }
启动调试,查看cmd窗口输出内容
在这个例子里,字典内容描述了一个具有两层结构的配置,第一层有Ele1和Ele2两个节点,分别对应一个字符内容和一个复合内容,第二层有Sub1和Sub2两个节点,同时挂在Ele2节点下,组成了一个复合结构。
注意看字典项里的Key,因为字典里的数据是扁平化的,为了能清晰描述节点之间的上下层关系,需要通过“:”符号来标识。
config对象通过GetSection方法来获取当前节点的某个下级节点内容。
第二部分、其他配置来源
除了上面例子里展示的配置源来自内存内容,也可以来自Xml文件、JSON文件或者数据库等。因为在.Net Core里使用JSON文件描述配置内容很常见,下面就展示如何读取JSON配置文件。
先添加如下两个nuget包,支持从json文件读取内容
"Microsoft.Extensions.Configuration.FileExtensions": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.0.0",
添加 appsettings.json 文件
1 { 2 "Ele1": "value1", 3 "Ele2": { 4 "Sub1": "value2.1", 5 "Sub2": "value2.2" 6 } 7 }
修改Main函数内容
1 using Microsoft.Extensions.Configuration; 2 using System; 3 using System.IO; 4 5 namespace DiApplicationTest 6 { 7 public class Program 8 { 9 public static void Main(string[] args) 10 { 11 IConfigurationBuilder builder = new ConfigurationBuilder(); 12 builder.SetBasePath(Directory.GetCurrentDirectory()); 13 builder.AddJsonFile("appsettings.json"); 14 15 IConfiguration config = builder.Build(); 16 Console.WriteLine($"Ele1: {config["Ele1"]}"); 17 Console.WriteLine($"Ele2.Sub1: {config.GetSection("Ele2")["Sub1"]}"); 18 Console.WriteLine($"Ele2.Sub2: {config.GetSection("Ele2")["Sub2"]}"); 19 20 Console.ReadLine(); 21 } 22 } 23 }
启动调试,查看cmd窗口输入内容,与上面的例子内容一致。
这个例子将原来在字典里的内容转换成Json格式化的内容存储在持久化文件里。通过指定配置源,同样可以读取内容。
第三部分、Options对象映射
当配置文件内容较多时,通过config的Key获取对应的配置项的值变得比较繁琐。.Net Core的配置系统采用了一种叫“Options Pattern”的模式使配置内容与有着对应结构的对象进行映射,这种对象就叫做Options对象。
下面将简单演示一下这种映射方式。
首先添加如下nuget包引用,使用此模式的相关接口和类都在这个包里。同时还需要引入DI容器。
"Microsoft.Extensions.DependencyInjection": "1.0.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0",
添加两个具有层级结构的Options类
1 public class ConfigOptions 2 { 3 public string Ele1 { get; set; } 4 public SubConfigOptions Ele2 { get; set; } 5 } 6 7 public class SubConfigOptions 8 { 9 public string Sub1 { get; set; } 10 public string Sub2 { get; set; } 11 }
修改Main函数内容
1 public static void Main(string[] args) 2 { 3 // 创建DI容器,注册Options Pattern服务 4 IServiceCollection services = new ServiceCollection(); 5 services.AddOptions(); 6 7 // 读取配置文件 8 IConfigurationBuilder builder = new ConfigurationBuilder(); 9 builder.SetBasePath(Directory.GetCurrentDirectory()); 10 builder.AddJsonFile("appsettings.json"); 11 IConfiguration config = builder.Build(); 12 13 // 通过注册的服务获取最终映射的配置对象 14 IServiceProvider serviceProvider = services.Configure<ConfigOptions>(config).BuildServiceProvider(); 15 ConfigOptions options = serviceProvider.GetService<IOptions<ConfigOptions>>().Value; 16 17 Console.WriteLine($"Ele1: {options.Ele1}"); 18 Console.WriteLine($"Ele2.Sub1: {options.Ele2.Sub1}"); 19 Console.WriteLine($"Ele2.Sub2: {options.Ele2.Sub2}"); 20 21 Console.ReadLine(); 22 }
启动调试,查看cmd窗口输入内容,与上面的例子内容一致。
在定义Options对象结构时,对象内的属性名称要与对应层级的配置Key的值保持一致,层级关系也要与配置内容的层级结构保持一致。
通过调用 services.AddOptions() 方法注册Options Pattern服务。将配置内容注册到容器里,来获取对应的服务Provider对象。通过调用GetService方法获得对应的真实服务对象,即带有事先定义的Options类型的泛型接口IOptions,接口的Value值就是配置内容映射的Options对象。
第四部分、Asp.Net Core里的配置管理
在这个系列之前使用的例子里添加配置管理相关的功能,来修改初始化日志级别的代码,同时添加一些自定义配置。
先添加配置相关的nuget包
"Microsoft.Extensions.Configuration": "1.0.0", "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.0.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0"
添加appsettings.json文件
1 { 2 // 日志配置 3 "Logging": { 4 "LogLevel": { 5 "Microsoft": "Warning" 6 } 7 }, 8 // 自定义配置 9 "CfgContent": { 10 "Ele1": "value1", 11 "Ele2": { 12 "Sub1": "value2.1", 13 "Sub2": "value2.2" 14 } 15 } 16 }
添加上个例子里的 ConfigOptions.cs、SubConfigOptions.cs 类,修改 Startup.cs 的内容,添加配置相关代码
1 using System.IO; 2 using Microsoft.AspNetCore.Builder; 3 using Microsoft.Extensions.Configuration; 4 using Microsoft.Extensions.DependencyInjection; 5 using Microsoft.Extensions.Logging; 6 7 namespace WebApiFrame 8 { 9 public class Startup 10 { 11 public IConfiguration Configuration { get; } 12 13 public Startup() 14 { 15 var builder = new ConfigurationBuilder() 16 .SetBasePath(Directory.GetCurrentDirectory()) 17 .AddJsonFile("appsettings.json"); 18 19 Configuration = builder.Build(); 20 } 21 22 public void ConfigureServices(IServiceCollection services) 23 { 24 services.AddOptions(); 25 services.Configure<ConfigOptions>(Configuration.GetSection("CfgContent")); 26 27 // 注入MVC框架 28 services.AddMvc(); 29 } 30 31 public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) 32 { 33 // 添加日志支持 34 loggerFactory.AddConsole(Configuration.GetSection("Logging")); 35 loggerFactory.AddDebug(); 36 37 // 添加NLog日志支持 38 //loggerFactory.AddNLog(); 39 40 // 添加MVC中间件 41 app.UseMvc(); 42 } 43 } 44 }
修改 DemoController.cs 内容
1 using System.Threading.Tasks; 2 using Microsoft.AspNetCore.Http; 3 using Microsoft.AspNetCore.Mvc; 4 using Microsoft.Extensions.Options; 5 6 namespace WebApiFrame 7 { 8 [Route("[controller]")] 9 public class DemoController : Controller 10 { 11 private ConfigOptions _cfgContent { get; } 12 13 public DemoController(IOptions<ConfigOptions> options) 14 { 15 _cfgContent = options.Value; 16 } 17 18 [HttpGet("index")] 19 public async Task Index() 20 { 21 HttpContext.Response.ContentType = "text/html"; 22 await HttpContext.Response.WriteAsync($"<span>Ele1: {_cfgContent.Ele1}</span><br />"); 23 await HttpContext.Response.WriteAsync($"<span>Ele2.Sub1: {_cfgContent.Ele2.Sub1}</span><br />"); 24 await HttpContext.Response.WriteAsync($"<span>Ele2.Sub2: {_cfgContent.Ele2.Sub2}</span><br />"); 25 } 26 } 27 }
启动调试,访问地址 http://localhost:5000/demo/index ,页面显示配置内容
从这个例子里可以看到,读取 appsettings.json 配置文件内容并分成两部分,其中一部分配置Logging作为Logger的配置内容用于日志初始化,另外一部分CfgContent配置内容注册到DI容器,在控制器的构造函数里通过IOptions泛型类型的参数注入,这样控制器就可以读取配置文件的内容。