前言
配置在我们开发过程中必不可少, ASP.NET 中的配置在 web.config 中. 也可配置在如: JSON,xml, 数据库等(但 ASP.NET 并没提供相应的模块和方法).
在 ASP.NET Core 中 Web.config 已经不存在了(但如果托管到 IIS 的时候可以使用 Web.config 配置 IIS),
而是用 appsettings.JSON 和 appsettings.(Development,Staging,Production).JSON 配置文件
(可以理解为 ASP.NET 中的 Web.config 和 Web.Release.config 的关系).
下面我们一起看下 ASP.NET Core 中的配置
基础用法
- HomeController.cs:
- [ApiController]
- public class HomeController : ControllerBase
- {
- private readonly IConfiguration _configuration;
- public HomeController(IConfiguration configuration)
- {
- _configuration = configuration;
- }
- [HttpGet("/")]
- public dynamic Index()
- {
- return JsonConvert.SerializeObject(new
- {
- ConnectionString = _configuration["ConnectionString"],
- Child1 = _configuration["Parent:Child1"],
- Grandchildren = _configuration["Parent:Child2:Grandchildren"]
- });
- }
- }
返回结果:
- {
- "ConnectionString": "data source=.;initial catalog=TEST;user id=sa",
- "Child1": "child",
- "Grandchildren": "grandchildren"
- }
对应 appsettings.JSON 的值:
- {
- "ConnectionString": "data source=.;initial catalog=TEST;user id=sa;password=123",
- "Parent": {
- "Child1": "child1",
- "Child2": "child2"
- }
- }
需要注意的:
键不区分大小写. 例如, ConnectionString 和 connectionstring 被视为等效键.
如果键名相同(包含所有加载的配置文件), 以最后一个值为准.
所以 appsettings.(Development,Staging,Production).JSON 会覆盖 appsettings.JSON 的配置信息.
多层级用: 来分割, 但如果是环境变量则存在跨平台问题 查看具体细节
上面这种写法是没缓存的即: appsettings.JSON 修改后, 获取的值会立即改变
绑定到对象 IOptions<TestOptions>
对于
_configuration["Parent:Child2:Grandchildren"]
的写法对程序员来说肯定很反感, 下面我们看下把配置文件绑定到对象中.
首先把 JSON 对象转换成对象
- public class TestOptions
- {
- public string ConnectionString { get; set; }
- public Parent Parent { get; set; }
- }
- public class Parent
- {
- public string Child1 { get; set; }
- public Child2 Child2 { get; set; }
- }
- public class Child2
- {
- public string GrandChildren { get; set; }
- }
在 Startup.cs 的 ConfigureServices 方法添加代码:
- services.Configure<TestOptions>(Configuration);
- HomeController.cs:
- [ApiController]
- public class HomeController : ControllerBase
- {
- private readonly IOptions<TestOptions> _options;
- public HomeController(IOptions<TestOptions> options)
- {
- _options = options;
- }
- [HttpGet("options")]
- public string Options()
- {
- return JsonConvert.SerializeObject(_options);
- }
- }
返回的结果如下:
- {
- "Value": {
- "ConnectionString": "data source=.;initial catalog=TEST;user id=sa",
- "Parent": {
- "Child1": "child",
- "Child2": {
- "GrandChildren": "grandchildren"
- }
- }
- }
- }
发现
如果我们修改 appsettings.JSON, 然后刷新页面, 会发现值并没用变
我们发现根节点是 Value
根据上面 2 个问题我们去看源代码:
首先找到这句:
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
我们去看 OptionsManager 方法:
- public class OptionsManager<TOptions> : IOptions<TOptions>, IOptionsSnapshot<TOptions> where TOptions : class, new()
- {
- private readonly IOptionsFactory<TOptions> _factory;
- // 注解: _cache 为 ConcurrentDictionary<string, Lazy<TOptions>>()
- // 不知道注意到上面注入方法没, 用的是 Singleton 单例, 所以更新配置文件后没及时更新
- private readonly OptionsCache<TOptions> _cache = new OptionsCache<TOptions>();
- public OptionsManager(IOptionsFactory<TOptions> factory)
- {
- _factory = factory;
- }
- /// <summary>
- /// TOptions 的默认配置实例
- /// 这里就是为什么根节点为 Value
- /// </summary>
- public TOptions Value
- {
- get
- {
- return Get(Options.DefaultName);
- }
- }
- /// <summary>
- /// 该方法在 IOptionsSnapshot 接口中
- /// </summary>
- public virtual TOptions Get(string name)
- {
- name = name ?? Options.DefaultName;
- // Store the options in our instance cache
- return _cache.GetOrAdd(name, () => _factory.Create(name));
- }
- }
绑定到对象 IOptionsSnapshot<TestOptions>
与 IOptions<TestOptions> 的用法一样, 区别就是依赖注入的生命周期为 Scoped, 所以没缓存, 直接看代码:
services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
IOptionsSnapshot<TOptions> 比 IOptions<TOptions> 多了个方法 TOptions Get(string name);
Value 属性其实就是调用了该方法, 只是 name 为 Options.DefaultName
IOptionsSnapshot<TOptions> 继承了 IOptions<TOptions> (这里有个疑问, 上面的 OptionsManager<TOptions > 继承了 IOptions<TOptions>, IOptionsSnapshot<TOptions>2 个接口, 也许是为了清晰吧).
自定义配置文件(JSON)
有的时候配置信息很多, 想在单独的文件中配置, ASP.NET Core 提供了 INI,xml,JSON 文件系统加载配置的方法.
首先在 Program.cs 的 CreateWebHostBuilder 方法中添加如下代码:
- public static IWebHostBuilder CreateWebHostBuilder(string[] args)
- {
- return WebHost.CreateDefaultBuilder(args)
- .ConfigureAppConfiguration((hostingContext, config) =>
- {
- // 设置根目录, 我们放在 /Config 文件夹下
- // 此种写法不会加载 `appsettings.JSON`
- // config.SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "Config"));
- config.SetBasePath(Directory.GetCurrentDirectory());
- // 添加 setting.JSON 配置文件
- //optional: 文件是否可选, 当为 false 时 如果文件不存在 程序启动不起来
- //reloadOnChange: 文件改变后是否重新加载
- config.AddJsonFile("Config/setting.JSON", optional: false, reloadOnChange: false);
- })
- .UseStartup<Startup>();
- }
我们看下 reloadOnChange 参数处理了哪些事情:
- if (Source.ReloadOnChange && Source.FileProvider != null)
- {
- ChangeToken.OnChange(
- // 监视文件 如果改变就执行 Load(true) 方法
- () => Source.FileProvider.Watch(Source.Path),
- () => {
- Thread.Sleep(Source.ReloadDelay);
- Load(reload: true);
- });
- }
创建 setting.JSON:
- {
- "Rookie": {
- "Name": "DDD",
- "Age": 12,
- "Sex": "男"
- }
- }
在 Controller 中:
- [Route("API/[controller]")]
- [ApiController]
- public class OptionsController : ControllerBase
- {
- private readonly IConfiguration _configuration;
- public OptionsController(IConfiguration configuration)
- {
- _configuration = configuration;
- }
- [HttpGet]
- public string Index()
- {
- return JsonConvert.SerializeObject(new
- {
- Name = _configuration["Rookie:Name"],
- Age = _configuration["Rookie:Age"],
- Sex = _configuration["Rookie:Sex"]
- });
- }
- }
自定义配置文件 (JSON) 绑定到对象
通过上面配置发现获取配置用的是
_configuration["Name"]
方式, 下面我们绑定到对象上
定义配置对象 Setting 类
在 Startup.cs 的 ConfigureServices 方法添加代码:
services.Configure<Setting>(Configuration.GetSection("Rookie"));
GetSection 方法: 获得指定键的配置子部分
用法和上面一样
不知道发现个情况没, 我们 setting.JSON 的配置文件的信息也是存到_configuration 中的, 如果文件多了很可能会覆盖节点, 下面我们换一种写法.
首先删除 Program.cs 中 CreateWebHostBuilder 方法的代码:
config.AddJsonFile("Config/setting.JSON", optional: false, reloadOnChange: true);
修改配置文件 setting.JSON:
- {
- "Name": "DDD",
- "Age": 12,
- "Sex": "男"
- }
在中 Startup.cs 的 ConfigureServices 方法中添加:
- var config = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("Config/setting.JSON", optional: false, reloadOnChange: true)
- .Build();
- services.Configure<Setting>(config);
这样就不会影响 全局 的配置信息了
其他方式
我们看下下面 2 个方式:
- [Route("[controller]")]
- [ApiController]
- public class HomeController : ControllerBase
- {
- private readonly IConfiguration _configuration;
- private readonly TestOptions _bindOptions = new TestOptions();
- private readonly Parent _parent = new Parent();
- public HomeController(IConfiguration configuration)
- {
- // 全部绑定
- _configuration.Bind(_bindOptions);
- // 部分绑定
- _configuration.GetSection("Parent").Bind(_parent);
- }
- [HttpGet("options")]
- public string Options()
- {
- return JsonConvert.SerializeObject(new
- {
- BindOptions = _bindOptions,
- Parent = _parent
- });
- }
- }
个人感觉这种方式有点多余 ( ╯□╰ )
IOptionsMonitor,IOptionsFactory,IOptionsMonitorCache 方式:
我们看如下代码
- services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
- services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
- services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
- services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
- services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));
这里只说下 IOptionsMonitor<>:
- [Route("[controller]")]
- [ApiController]
- public class HomeController : ControllerBase
- {
- private readonly IOptionsMonitor<TestOptions> _optionsMonitor;
- public HomeController(IOptionsMonitor<TestOptions> optionsMonitor
- , ILogger<HomeController> logger)
- {
- _optionsMonitor = optionsMonitor;
- _logger = logger;
- }
- [HttpGet("options")]
- public string Options()
- {
- // 这里有个变更委托
- _optionsMonitor.OnChange((options, name) =>
- {
- var info = JsonConvert.SerializeObject(new
- {
- Options = options,
- Name = name
- });
- _logger.LogInformation($"配置信息已更改:{info}");
- });
- return JsonConvert.SerializeObject(new
- {
- OptionsMoitor = _optionsMonitor
- });
- }
当我们修改配置文件后会看到日志信息:
info: WebApiSample.Controllers.HomeController[0]
配置信息已更改:{"Options":{"ConnectionString":"data source=.;initial catalog=TEST;user id=sa","Parent":{"Child1":"child","Child2":{"GrandChildren":"grandchildren"}}},"Name":"TestOptions"}
info: WebApiSample.Controllers.HomeController[0]
配置信息已更改:{"Options":{"ConnectionString":"data source=.;initial catalog=TEST;user id=sa","Parent":{"Child1":"child","Child2":{"GrandChildren":"grandchildren"}}},"Name":"TestOptions"}
info: WebApiSample.Controllers.HomeController[0]
配置信息已更改:{"Options":{"ConnectionString":"data source=.;initial catalog=TEST;user id=sa","Parent":{"Child1":"child","Child2":{"GrandChildren":"grandchildren"}}},"Name":"TestOptions"}
info: WebApiSample.Controllers.HomeController[0]
配置信息已更改:{"Options":{"ConnectionString":"data source=.;initial catalog=TEST;user id=sa","Parent":{"Child1":"child","Child2":{"GrandChildren":"grandchildren"}}},"Name":"TestOptions"}
不知道为什么会有这么多日志...
还有一点不管我们修改哪个日志文件, 只要是执行了 AddJsonFile 的文件, 都会触发该事件
写在最后
写的有点乱希望不要见怪, 本以为一个配置模块应该不会复杂, 但看了源代码后发现里面的东西好多...
本来还想写下是如何实现的, 但感觉太长了就算了.
最后还是希望大家以批判的角度来看, 有任何问题可在留言区留言!
来源: https://www.cnblogs.com/dudd/p/9700927.html