• ASP.NET Core 中的配置


    前言

    配置在我们开发过程中必不可少,ASP.NET中的配置在 Web.config 中。也可配置在如:JSON、XML、数据库等(但ASP.NET并没提供相应的模块和方法)。

    ASP.NET CoreWeb.config已经不存在了(但如果托管到 IIS 的时候可以使用 web.config 配置 IIS),

    而是用appsettings.jsonappsettings.(Development、Staging、Production).json配置文件

    (可以理解为ASP.NET中的Web.configWeb.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"]的写法对程序员来说肯定很反感,下面我们看下把配置文件绑定到对象中。

    1. 首先把 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; }
      }
      
    2. Startup.csConfigureServices方法添加代码:

      services.Configure<TestOptions>(Configuration);
      
    3. 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>

    1. IOptions<TestOptions> 的用法一样,区别就是依赖注入的生命周期为Scoped,所以没缓存,直接看代码:

      services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));

    2. IOptionsSnapshot<TOptions>IOptions<TOptions> 多了个方法TOptions Get(string name);

      Value属性其实就是调用了该方法,只是nameOptions.DefaultName

    3. IOptionsSnapshot<TOptions> 继承了 IOptions<TOptions> (这里有个疑问,上面的OptionsManager<TOptions>继承了IOptions<TOptions>, IOptionsSnapshot<TOptions>2 个接口,也许是为了清晰吧)。

    自定义配置文件(JSON)

    有的时候配置信息很多,想在单独的文件中配置,ASP.NET Core 提供了INIXMLJSON文件系统加载配置的方法。

    1. 首先在 Program.csCreateWebHostBuilder方法中添加如下代码:

      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);
              });
      }
      
    2. 创建setting.json

      {
              "Rookie": {
                  "Name": "DDD",
                  "Age": 12,
                  "Sex": "男"
              }
      }
      
    3. 在 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"] 方式,下面我们绑定到对象上

    1. 定义配置对象 Setting

    2. Startup.csConfigureServices方法添加代码:

      services.Configure<Setting>(Configuration.GetSection("Rookie"));

      GetSection方法:获得指定键的配置子部分

    3. 用法和上面一样

    不知道发现个情况没,我们setting.json的配置文件的信息也是存到_configuration中的,如果文件多了很可能会覆盖节点,下面我们换一种写法。

    1. 首先删除 Program.csCreateWebHostBuilder方法的代码:

      config.AddJsonFile("Config/setting.json", optional: false, reloadOnChange: true);

    2. 修改配置文件setting.json

      {
         "Name": "DDD",
         "Age": 12,
         "Sex": "男"
      }
      
    3. 在中Startup.csConfigureServices方法中添加:

      var config = new ConfigurationBuilder()
                   .SetBasePath(Directory.GetCurrentDirectory())
                   .AddJsonFile("Config/setting.json", optional: false, reloadOnChange: true)
                   .Build();
       services.Configure<Setting>(config);
      

    这样就不会影响 全局 的配置信息了

    其他方式

    1. 我们看下下面 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
              });
          }
      }
      

      个人感觉这种方式有点多余 ( ╯□╰ )

    2. IOptionsMonitorIOptionsFactoryIOptionsMonitorCache 方式:

      我们看如下代码

      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的文件,都会触发该事件

    写在最后

    写的有点乱希望不要见怪,本以为一个配置模块应该不会复杂,但看了源代码后发现里面的东西好多...
    
    本来还想写下是如何实现的,但感觉太长了就算了。
    
    最后还是希望大家以批判的角度来看,有任何问题可在留言区留言!
  • 相关阅读:
    HTTP Status 500
    HTTP Status 500
    HTTP Status 500
    测试错误ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console.问题的解决
    页面报错误:HTTP Status 500
    eclipse怎么设置在新建JSP文件的编码为UTF-8?
    linux 开启oracle监听
    linux 修改环境变量
    linux 修改oracle的字符集
    Cannot change version of project facet Dynamic Web Module to 2.5
  • 原文地址:https://www.cnblogs.com/dudd/p/9700927.html
Copyright © 2020-2023  润新知