一.引言:为什么需要配置系统?
在软件开发中,读取配置而不是硬编码的设计被广泛使用 ------ 数据库连接、API 密钥、缓存策略、第三方服务地址......
.NET 提供了一套统一、可扩展的配置系统,广泛适用于:
- 
控制台程序
 - 
ASP.NET Core Web API / MVC
 - 
后台服务 / Worker
 - 
Blazor / MAUI / 云函数
 
本文基于.net8简单介绍一下.net配置系统的使用
文章的前面会使用控制台主动引入配置相关的包,然后介绍这些包的基本用法,根本目的是为了在Asp.Net WebApi中通过依赖注入的形式使用.
二.Donet强大的配置系统

<ItemGroup>
  <PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.6" />
  <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.6" />
</ItemGroup>
        创建控制台并引入上面两个包,前者是基础包,后者是基于前者的有关Json配置方面的拓展包.
显然不止有Json拓展包,donet广泛支持各种配资源:包括文件(json,xml,ini,yaml等),注册表,环境变量,命令行等等,还可以配置自定义配置源,只需要引入相应的包就可以了.
还可以跟踪配置的改变,可以按照优先级覆盖等等满足现代化开发的需求.

本文主要介绍使用Json作为配置源的选项.
三.控制台使用
            
            
              bash
              
              
            
          
          {
  "AppName": "TestConsoleApp",
  "ApiSettings": {
    "BaseUrl": "https://api.example.com",
    "Timeout": 30
  }
}
        
根目录创建一个json文件(别忘了右键属性将其设为如果较新则复制)
            
            
              cs
              
              
            
          
               static void Main(string[] args)
     {
         //先实例一个配置构建对象
         ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
         //配置json文件路径(根目录下,当然也可以放在某个目录下) optional(为真的话如果文件路径错误抛异常)
         //reloadOnChange为真则检测配置是否改变,改变重新加载
         configurationBuilder.AddJsonFile("config.json", optional: false, reloadOnChange: true);
         IConfigurationRoot configurationRoot = configurationBuilder.Build();
         //键的名称大小写不敏感
         string? appName = configurationRoot["appName"];           
         //可以使用冒号向下一个节点查询
         var baseUrl = configurationRoot["ApiSettings:baseUrl"];
         var timeout = configurationRoot.GetSection("ApiSettings:Timeout").Value;
         Console.WriteLine($"appName:{appName}");            
         Console.WriteLine($"baseUrl:{baseUrl}");
         Console.WriteLine($"timeout:{timeout}");           
         Console.ReadKey();
     }
        你或许注意到
var baseUrl = configurationRoot["ApiSettings:baseUrl"];
var timeout = configurationRoot.GetSection("ApiSettings:Timeout").Value;
这两种方案完成了一样的事,两者什么区别?
| 维度 | 索引器方式 ["A:B"] | 
GetSection("A:B").Value | 
|---|---|---|
| 📌 简洁性 | ✅ 更短,适合快速取值 | 稍长,写法冗长一些 | 
| 🔍 获取子对象 | ❌ 无法获取嵌套对象(只支持值) | ✅ 可获取子节并递归操作 | 
| 📦 支持绑定 | ❌ 不可绑定成类对象 | ✅ 可用 .Bind() 或 .Get<T>() | 
| 🧪 空值处理 | ✅ 直接为 null | ✅ .Value 也是 null,但可以先判断 Exists() | 
            
            
              cs
              
              
            
          
           internal class Program
 {
     static void Main(string[] args)
     {
         //先实例一个配置构建对象
         ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
         //配置json文件路径(根目录下,当然也可以放在某个目录下) optional(为真的话如果文件路径错误抛异常)
         //reloadOnChange为真则检测配置是否改变,改变重新加载
         configurationBuilder.AddJsonFile("config.json", optional: false, reloadOnChange: true);
         IConfigurationRoot configurationRoot = configurationBuilder.Build();
         //键的名称大小写不敏感
         string? appName = configurationRoot["appName"];
         var apiSetting = configurationRoot.GetSection("ApiSettings").Get<ApiSettings>();
         Console.WriteLine(apiSetting.BaseUrl);
         Console.WriteLine(apiSetting.Timeout);
     }
 }
 public class ApiSettings
 {
     public string BaseUrl { get; set; }
     public int Timeout { get; set; }
 }
        简单的说就是前者用起来很快捷,但是只能获取子节点的某个值.
而后者话,你可以声明一个实体来承载整个子节点(支持嵌套),利用Get<T>方法.
使用Get<T>方法需要引入Microsoft.Extensions.Configuration.Binder.
            
            
              cs
              
              
            
          
           internal class Program
 {
     static void Main(string[] args)
     {
         //先实例一个配置构建对象
         ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
         //配置json文件路径(根目录下,当然也可以放在某个目录下) optional(为真的话如果文件路径错误抛异常)
         //reloadOnChange为真则检测配置是否改变,改变重新加载
         configurationBuilder.AddJsonFile("config.json", optional: false, reloadOnChange: true);
         IConfigurationRoot configurationRoot = configurationBuilder.Build();
         var appConfig = configurationRoot.Get<AppConfig>();
         Console.WriteLine(appConfig.AppName);
         Console.WriteLine(appConfig.ApiSettings.BaseUrl);
         Console.WriteLine(appConfig.ApiSettings.Timeout);
     }
 }
 public class ApiSettings
 {
     public string BaseUrl { get; set; }
     public int Timeout { get; set; }
 }
 public class AppConfig
 {
     public string AppName { get; set; }
     public ApiSettings ApiSettings { get; set; }
 }
        既然能获取子节点的对象,那显然也能直接获取根节点对象.
            
            
              cs
              
              
            
          
              static void Main(string[] args)
    {
        //先实例一个配置构建对象
        ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
        //配置json文件路径(根目录下,当然也可以放在某个目录下) optional(为真的话如果文件路径错误抛异常)
        //reloadOnChange为真则检测配置是否改变,改变重新加载
        configurationBuilder.AddJsonFile("config.json", optional: false, reloadOnChange: true);
        IConfigurationRoot configurationRoot = configurationBuilder.Build();
        AppConfig appConfig=new AppConfig();
        configurationRoot.Bind(appConfig);
        Console.WriteLine(appConfig.AppName);
        Console.WriteLine(appConfig.ApiSettings.BaseUrl);
        Console.WriteLine(appConfig.ApiSettings.Timeout);
    }
}
public class ApiSettings
{
    public string BaseUrl { get; set; }
    public int Timeout { get; set; }
}
public class AppConfig
{
    public string AppName { get; set; }
    public ApiSettings ApiSettings { get; set; }
}
        不仅可以使用Get,还有一个重要方法是Bind,两者的区别是Get自动生成对象并将数据注入,而Bind需要你提供一个对象然后将数据注入.
Bind会在WebApi项目经常使用,Get一般是手动使用.
四.在WebApi项目通过依赖注入使用
使用WebApi模版创建的项目不需要手动引入上述提到的包,因为模版本身就已经帮我们准备好了.
在依赖项你也可以看到确实已经有这些包了,甚至相关的xml等包也都在.


            
            
              cs
              
              
            
          
           public static void Main(string[] args)
 {
     var builder = WebApplication.CreateBuilder(args);
     // Add services to the container.
     builder.Services.AddControllers();
     // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
     builder.Services.AddEndpointsApiExplorer();
     builder.Services.AddSwaggerGen();
     
     ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();          
     configurationBuilder.AddJsonFile("config.json", optional: false, reloadOnChange: true);
     IConfigurationRoot configurationRoot = configurationBuilder.Build();
     builder.Services.AddOptions().Configure<AppConfig>(configurationRoot.Bind);         
        前三句还是老样子,只是最后一句将配置注册到服务里面
            
            
              cs
              
              
            
          
           [ApiController]
 [Route("[controller]")]
 public class WeatherForecastController : ControllerBase
 {       
     private readonly ILogger<WeatherForecastController> _logger;
     private readonly IOptionsSnapshot<AppConfig> _optionsSnapshot;
     public WeatherForecastController(ILogger<WeatherForecastController> logger,IOptionsSnapshot<AppConfig> optionsSnapshot)
     {
         _logger = logger;
         _optionsSnapshot = optionsSnapshot;
     }    
     [HttpGet]
     [Route("GetConfig")]
     public ActionResult<AppConfig> GetConfig()
     {
         return Ok(_optionsSnapshot.Value);
     }
 }
        然后就可以通过构造器注入配置.
实际上会同时注册以下三种服务的支持
| 类型 | 生命周期 | 说明 | 
|---|---|---|
IOptions<AppConfig> | 
Singleton | 每次请求拿到的是同一个配置对象 | 
IOptionsSnapshot<AppConfig> | 
Scoped(每次请求一份) | 适合 Web API 中 per-request 读取配置 | 
IOptionsMonitor<AppConfig> | 
Singleton | 支持配置变更监听(如热更新) | 
你有三种选择,需要那种注入那种就行了.
            
            
              cs
              
              
            
          
           ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();          
 configurationBuilder.AddJsonFile("config.json", optional: false, reloadOnChange: true);
 IConfigurationRoot configurationRoot = configurationBuilder.Build();
 builder.Services.AddOptions().Configure<AppConfig>(configurationRoot.Bind)
     .Configure<ApiSettings>(configurationRoot.GetSection("ApiSettings").Bind);
        显然是可以配置多个的,比如刚才的AppConfig,如果你只关心它的某个子节点,也可以将子节点再注册成一个单独的配置.
前面我们手动构建了IConfigurationRoot configurationRoot 对象,但是在WebApi中我们不需要做这个工作,模版本身已经配置好了.
WebApi项目其实已经有了内置的顶级配置对象和对应的json配置文件

            
            
              cs
              
              
            
          
            public static void Main(string[] args)
  {
      var builder = WebApplication.CreateBuilder(args);
      // Add services to the container.
      builder.Services.AddControllers();
      // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
      builder.Services.AddEndpointsApiExplorer();
      builder.Services.AddSwaggerGen();
      //ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();          
      //configurationBuilder.AddJsonFile("config.json", optional: false, reloadOnChange: true);
      //IConfigurationRoot configurationRoot = configurationBuilder.Build();
      //builder.Services.AddOptions().Configure<AppConfig>(configurationRoot.Bind)
      //    .Configure<ApiSettings>(configurationRoot.GetSection("ApiSettings").Bind);
      builder.Services.Configure<AppConfig>(builder.Configuration.GetSection("AppConfig").Bind);
      builder.Services.Configure<ApiSettings>(builder.Configuration.GetSection("AppConfig:ApiSettings").Bind);
        这个builder.Configuration 就是系统为我们提供的配置对象

            
            
              cs
              
              
            
          
          {
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "AppConfig": {
    "AppName": "TestConsoleApp",
    "ApiSettings": {
      "BaseUrl": "https://api.example.com",
      "Timeout": 50
    }
  }
}
        所以我们的配置只需要写入appsetings.json就行了