Skip to Content
Week 05Day 3 - 配置管理

Day 3 - 配置管理

建议用时:240-300 分钟

你将学会什么

  • 什么是配置,为什么不要把可变值写死在代码里
  • appsettings.json 通常放什么
  • 如何用配置类承载配置
  • 如何从 JSON 文件读取配置
  • 如何给配置设置默认值和校验规则
  • 如何用环境变量覆盖配置
  • 为什么密码、Token、连接字符串不能随便提交到源码里

配置管理的核心是:会变化的值离开代码,业务代码只依赖配置对象。路径、开关、超时时间、接口地址、日志级别都属于配置。

本页固定顺序

  1. 先学第一部分:弄懂今天最小、最重要的知识,并运行短例子。
  2. 再学第二部分:把刚学的知识组合成一个完整例子。
  3. 然后做第三部分:自己跟着敲,再完成重复训练和每日小测。
  4. 最后做第四部分:先独立完成作业,再用完整答案检查。

学习衔接

上一页学习的是“依赖注入”,今天继续学习“配置管理”。先使用上一页已经会的写法,再只增加今天这个新知识点;如果前置内容还不能独立敲出,先回上一页复习,不要硬跳。

今天的最低通过线

第一次学习不要求背完整页。完成下面 3 项,就可以继续:

  • 能用自己的话说明“配置管理”解决什么问题。
  • 把第一部分的短例子亲手敲完,并确认每个例子都能运行。
  • 不看完整答案完成第三部分至少前 3 个例子,再主动改一个值观察结果。

第一部分:先学原理和最小知识

这一部分从最小知识开始。先读解释,再把紧跟着的短例子敲一遍。配置管理不是背 appsettings.json,而是知道哪些值应该离开代码。

1. 什么是配置

配置就是程序运行时需要用到、但可能会变化的参数。

常见配置:

配置例子
文件路径products.txt
接口地址https://api.example.com
超时时间10
功能开关EnableLog = true
日志级别InformationWarning
连接字符串数据库地址、账号信息

2. 为什么不要写死在代码里

如果到处写:

"products.txt" 10 "https://api.example.com"

会出现问题:

  1. 改配置要改代码。
  2. 多个地方可能改漏。
  3. 开发、测试、生产很难用不同配置。
  4. 密码、Token 可能被提交到代码仓库。

配置文件的作用是把变化集中管理。

3. 什么是 appsettings.json

appsettings.json 是 .NET 项目里常见的配置文件名。

它通常长这样:

{ "DataFile": "products.txt", "EnableLog": true, "TimeoutSeconds": 10 }

文件名不是魔法。真正重要的是:

配置放在代码外面,程序启动时读取。

4. 什么是配置类

配置类是用来承载配置的类。

例如:

AppOptions

它的属性对应配置项:

DataFile EnableLog TimeoutSeconds

好处是:业务代码不用到处拿字符串 key,而是使用强类型属性。

5. 什么是 Options 模式

Options 模式就是把配置绑定成一个类,然后把这个类交给需要它的服务。

简化理解:

JSON 配置 -> AppOptions 对象 -> ProductService 使用 AppOptions

真实框架里经常会看到 IOptions<AppOptions>。本页先用普通 AppOptions 对象理解这个思想。

6. 默认值有什么用

配置可能缺失。

配置类可以给默认值:

TimeoutSeconds = 5 EnableLog = false

如果 JSON 里没写这些字段,就使用默认值。

默认值不能替代校验。关键配置仍然要检查。

7. 配置校验是什么

配置校验就是程序启动时检查配置是否合理。

例如:

  1. 文件路径不能为空。
  2. 超时时间必须大于 0。
  3. 接口地址必须是合法 URL。
  4. 连接字符串不能缺失。

越早发现配置错误,越容易定位问题。

8. 环境变量是什么

环境变量是操作系统提供的配置来源。

它适合放:

  1. 不同环境的配置。
  2. 部署平台传入的参数。
  3. 不想写进文件的敏感值。

C# 读取环境变量:

Environment.GetEnvironmentVariable("PRODUCT_DATA_FILE")

9. 敏感配置怎么处理

敏感配置包括:

  1. 数据库密码。
  2. API Token。
  3. 私钥。
  4. 支付密钥。
  5. 短信平台密钥。

不要把这些写进公开仓库。

练习项目可以写假值,但真实项目要使用环境变量、用户机密或部署平台的密钥管理。

配置管理常用 API 速查

需求写法说明
创建配置构建器new ConfigurationBuilder()配置入口
设置基础路径.SetBasePath(...)找配置文件的位置
加载 JSON.AddJsonFile("appsettings.json")常见配置文件
加载环境变量.AddEnvironmentVariables()部署时覆盖配置
构建配置.Build()得到 IConfiguration
读取字符串config["App:Name"]用冒号读层级
读取数字config.GetValue<int>("App:Port")转成指定类型
绑定对象config.GetSection("App").Get<AppOptions>()配置转对象

配置文件例子:

{ "App": { "Name": "ProductApp", "MaxItems": 100 } }

读取例子:

string? name = configuration["App:Name"]; int maxItems = configuration.GetValue<int>("App:MaxItems");

配置适合放会变化的值,不适合放业务规则代码。

第二部分:把知识组合成完整例子

前面已经学过最小知识。现在把它们组合起来,先读懂执行顺序,再完整敲一遍。今天最终要能从 JSON 文件读取配置,并把配置传给服务。

先看效果:读取 appsettings.json

using System.Text.Json; string path = "appsettings.json"; await File.WriteAllTextAsync(path, """ { "DataFile": "products.txt", "EnableLog": true, "TimeoutSeconds": 10 } """); string json = await File.ReadAllTextAsync(path); AppOptions options = JsonSerializer.Deserialize<AppOptions>(json) ?? new AppOptions(); options.Validate(); ProductService service = new ProductService(options); service.PrintSettings(); class AppOptions { public string DataFile { get; set; } = "data.txt"; public bool EnableLog { get; set; } = false; public int TimeoutSeconds { get; set; } = 5; public void Validate() { if (string.IsNullOrWhiteSpace(DataFile)) { throw new InvalidOperationException("DataFile 配置不能为空"); } if (TimeoutSeconds <= 0) { throw new InvalidOperationException("TimeoutSeconds 必须大于 0"); } } } class ProductService { private readonly AppOptions _options; public ProductService(AppOptions options) { _options = options; } public void PrintSettings() { Console.WriteLine($"数据文件: {_options.DataFile}"); Console.WriteLine($"启用日志: {_options.EnableLog}"); Console.WriteLine($"超时时间: {_options.TimeoutSeconds} 秒"); } }

这个例子完整做了四件事:

  1. 准备配置文件。
  2. 读取 JSON。
  3. 反序列化成配置对象。
  4. 把配置对象传给服务使用。

第三部分:跟着敲代码

从这里开始动手。每个例子都是完整代码,可以直接放进 Program.cs 运行。

动手前先做这 3 件事

  1. 打开一个控制台项目。
  2. 每次只保留一个例子的代码,运行通过后再换下一个。
  3. 每个例子都改一次配置值,再运行观察输出变化。

例子 1:硬编码的问题

string dataFile = "products.txt"; bool enableLog = true; int timeoutSeconds = 10; Console.WriteLine($"数据文件: {dataFile}"); Console.WriteLine($"启用日志: {enableLog}"); Console.WriteLine($"超时时间: {timeoutSeconds}");

这个例子能跑,但配置散落在代码里。项目变大后不好管理。

例子 2:集中到配置对象

AppOptions options = new AppOptions { DataFile = "products.txt", EnableLog = true, TimeoutSeconds = 10 }; Console.WriteLine($"数据文件: {options.DataFile}"); Console.WriteLine($"启用日志: {options.EnableLog}"); Console.WriteLine($"超时时间: {options.TimeoutSeconds}"); class AppOptions { public string DataFile { get; set; } = "data.txt"; public bool EnableLog { get; set; } public int TimeoutSeconds { get; set; } = 5; }

先把散落的配置集中成对象,这是 Options 模式的基础。

例子 3:写入配置文件

string json = """ { "DataFile": "products.txt", "EnableLog": true, "TimeoutSeconds": 10 } """; await File.WriteAllTextAsync("appsettings.json", json); Console.WriteLine("配置文件已写入");

运行后会生成 appsettings.json

例子 4:从 JSON 读取配置对象

using System.Text.Json; await File.WriteAllTextAsync("appsettings.json", """ { "DataFile": "products.txt", "EnableLog": true, "TimeoutSeconds": 10 } """); string json = await File.ReadAllTextAsync("appsettings.json"); AppOptions options = JsonSerializer.Deserialize<AppOptions>(json) ?? new AppOptions(); Console.WriteLine($"数据文件: {options.DataFile}"); Console.WriteLine($"启用日志: {options.EnableLog}"); Console.WriteLine($"超时时间: {options.TimeoutSeconds}"); class AppOptions { public string DataFile { get; set; } = "data.txt"; public bool EnableLog { get; set; } public int TimeoutSeconds { get; set; } = 5; }

JsonSerializer.Deserialize<AppOptions> 会把 JSON 转成 AppOptions 对象。

例子 5:配置默认值

using System.Text.Json; await File.WriteAllTextAsync("appsettings.json", """ { "DataFile": "products.txt" } """); string json = await File.ReadAllTextAsync("appsettings.json"); AppOptions options = JsonSerializer.Deserialize<AppOptions>(json) ?? new AppOptions(); Console.WriteLine($"数据文件: {options.DataFile}"); Console.WriteLine($"启用日志: {options.EnableLog}"); Console.WriteLine($"超时时间: {options.TimeoutSeconds}"); class AppOptions { public string DataFile { get; set; } = "data.txt"; public bool EnableLog { get; set; } = false; public int TimeoutSeconds { get; set; } = 5; }

JSON 只写了 DataFile,其他属性会使用类里的默认值。

例子 6:配置校验

AppOptions options = new AppOptions { DataFile = "", TimeoutSeconds = 0 }; try { options.Validate(); Console.WriteLine("配置有效"); } catch (InvalidOperationException ex) { Console.WriteLine($"配置错误: {ex.Message}"); } class AppOptions { public string DataFile { get; set; } = "data.txt"; public int TimeoutSeconds { get; set; } = 5; public void Validate() { if (string.IsNullOrWhiteSpace(DataFile)) { throw new InvalidOperationException("DataFile 不能为空"); } if (TimeoutSeconds <= 0) { throw new InvalidOperationException("TimeoutSeconds 必须大于 0"); } } }

配置错误应该尽早暴露,而不是等业务运行到一半才失败。

例子 7:服务使用配置对象

AppOptions options = new AppOptions { DataFile = "products.txt", EnableLog = true }; ProductService service = new ProductService(options); service.Save("键盘"); class AppOptions { public string DataFile { get; set; } = "data.txt"; public bool EnableLog { get; set; } } class ProductService { private readonly AppOptions _options; public ProductService(AppOptions options) { _options = options; } public void Save(string name) { File.AppendAllText(_options.DataFile, name + Environment.NewLine); if (_options.EnableLog) { Console.WriteLine($"已保存商品: {name}"); } } }

业务服务不再写死文件路径,而是从配置对象里拿。

例子 8:环境变量覆盖配置

AppOptions options = new AppOptions { DataFile = "products.txt" }; Environment.SetEnvironmentVariable("PRODUCT_DATA_FILE", "products-dev.txt"); string? dataFileFromEnv = Environment.GetEnvironmentVariable("PRODUCT_DATA_FILE"); if (!string.IsNullOrWhiteSpace(dataFileFromEnv)) { options.DataFile = dataFileFromEnv; } Console.WriteLine($"最终数据文件: {options.DataFile}"); class AppOptions { public string DataFile { get; set; } = "data.txt"; }

环境变量可以覆盖配置文件里的值,适合不同运行环境。

例子 9:嵌套配置

using System.Text.Json; await File.WriteAllTextAsync("appsettings.json", """ { "Api": { "BaseUrl": "https://api.example.com", "TimeoutSeconds": 10 }, "Feature": { "EnableLog": true } } """); string json = await File.ReadAllTextAsync("appsettings.json"); AppOptions options = JsonSerializer.Deserialize<AppOptions>(json) ?? new AppOptions(); Console.WriteLine(options.Api.BaseUrl); Console.WriteLine(options.Api.TimeoutSeconds); Console.WriteLine(options.Feature.EnableLog); class AppOptions { public ApiOptions Api { get; set; } = new ApiOptions(); public FeatureOptions Feature { get; set; } = new FeatureOptions(); } class ApiOptions { public string BaseUrl { get; set; } = ""; public int TimeoutSeconds { get; set; } = 5; } class FeatureOptions { public bool EnableLog { get; set; } }

配置多了以后,可以按模块拆成嵌套对象。

常见错误和修法

错误为什么错修法
配置值写死在代码里环境变化要改代码放进 appsettings.json 或环境变量
配置文件没复制到输出目录运行时读不到文件检查 .csproj 的复制设置
key 名拼错读取到默认值或空值集中定义配置路径或使用 Options 类
数字配置不校验错误配置会导致运行异常读取后检查范围
敏感信息提交到仓库有安全风险密码、密钥用环境变量或安全存储

小白重复敲写训练

配置训练要同时改配置文件和读取代码。

训练 1:最小 JSON 配置

{ "AppName": "Product Manager", "PageSize": 20 }

手敲到 appsettings.json,故意漏一个逗号,观察 JSON 错误后再修复。

训练 2:读取配置值

using Microsoft.Extensions.Configuration; var config = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .Build(); Console.WriteLine(config["AppName"]); Console.WriteLine(config.GetValue<int>("PageSize"));

改动任务:增加 DataFile 配置并读取。

训练 3:绑定成对象

var options = new AppOptions(); config.Bind(options); Console.WriteLine($"{options.AppName} - {options.PageSize}"); class AppOptions { public string AppName { get; set; } = ""; public int PageSize { get; set; } }

第二遍把属性名故意写错,观察为什么拿不到值。

每日小测

做完本页后,用这 5 题检查是否真的掌握。

1. 判断题

本页的目标不是只把代码运行起来,还要能说清楚“为什么这样写”。

答案:对。能运行只是第一步,能解释原理、常用操作和常见错误,才说明本页内容进入了可复用能力。

2. 填空题

本页主题是:配置管理。今天至少要掌握的 3 个点是:

1. 什么是配置,为什么不要把可变值写死在代码里 2. `appsettings.json` 通常放什么 3. 如何用配置类承载配置

答案:以上 3 点必须能用自己的代码跑通,不能只停留在阅读。

3. 流程题

遇到本页相关功能时,先按什么顺序处理?

答案:先看完整例子,确认最终效果;再读原理和名词;然后跟着第三部分从空项目敲代码;最后对照作业答案检查。

4. 找错误题

如果本页代码运行失败,第一步应该做什么?

答案:先看终端或 IDE 里的第一条错误,找到文件名和行号;不要同时改很多地方。再回到本页的“常见错误和修法”表格,对照错误类型逐项排查。

5. 改需求题

在本页完整例子跑通后,至少改一个小需求。

可选改法:

  • 改一个字段名称。
  • 多加一个校验条件。
  • 多输出一行结果。
  • 把固定数据改成用户输入。
  • 把一次处理改成多条数据处理。

答案标准:修改后能重新运行,并能说明这次修改影响了哪一段逻辑。重点检查:什么是配置,为什么不要把可变值写死在代码里。

上位机专项练习

设备地址、采集周期和报警阈值应该放进配置,不应该散落在代码里。

下面 3 个例子都要亲手敲。先运行原代码,再完成每个例子后面的改动任务。

专项例子 1:appsettings.json 设备配置

{ "Device": { "Name": "PLC-01", "IpAddress": "192.168.1.10", "PollInterval": 1000 } }

运行结果或界面效果:

修改配置即可更换设备地址

改动任务: 把采集周期改成 500。

专项例子 2:读取配置对象

class DeviceOptions { public string Name { get; set; } = ""; public int PollInterval { get; set; } } DeviceOptions options = configuration.GetSection("Device").Get<DeviceOptions>()!; Console.WriteLine($"{options.Name}: {options.PollInterval} ms");

运行结果或界面效果:

PLC-01: 1000 ms

改动任务: 增加 IpAddress 并输出。

专项例子 3:检查错误配置

int interval = 100; if (interval < 200) { Console.WriteLine("采集周期不能小于 200 ms"); } else { Console.WriteLine("配置有效"); }

运行结果或界面效果:

采集周期不能小于 200 ms

改动任务: 测试 200 和 1000。

第四部分:作业完整答案

这一部分给出当天作业的完整答案。建议先照着敲一遍,再修改 JSON 里的配置值验证。

作业 1:读取基础配置

要求:

  1. 写入 appsettings.json
  2. 配置包含 DataFileEnableLogTimeoutSeconds
  3. 读取并转成 AppOptions
  4. 输出配置值。

完整答案

using System.Text.Json; await File.WriteAllTextAsync("appsettings.json", """ { "DataFile": "products.txt", "EnableLog": true, "TimeoutSeconds": 10 } """); string json = await File.ReadAllTextAsync("appsettings.json"); AppOptions options = JsonSerializer.Deserialize<AppOptions>(json) ?? new AppOptions(); Console.WriteLine($"数据文件: {options.DataFile}"); Console.WriteLine($"启用日志: {options.EnableLog}"); Console.WriteLine($"超时时间: {options.TimeoutSeconds}"); class AppOptions { public string DataFile { get; set; } = "data.txt"; public bool EnableLog { get; set; } public int TimeoutSeconds { get; set; } = 5; }

作业 2:配置校验

要求:

  1. DataFile 不能为空。
  2. TimeoutSeconds 必须大于 0。
  3. 配置错误时输出错误提示。

完整答案

AppOptions options = new AppOptions { DataFile = "", TimeoutSeconds = 0 }; try { options.Validate(); Console.WriteLine("配置正确"); } catch (InvalidOperationException ex) { Console.WriteLine($"配置错误: {ex.Message}"); } class AppOptions { public string DataFile { get; set; } = "data.txt"; public int TimeoutSeconds { get; set; } = 5; public void Validate() { if (string.IsNullOrWhiteSpace(DataFile)) { throw new InvalidOperationException("DataFile 不能为空"); } if (TimeoutSeconds <= 0) { throw new InvalidOperationException("TimeoutSeconds 必须大于 0"); } } }

作业 3:服务使用配置

要求:

  1. ProductService 通过构造函数接收 AppOptions
  2. 保存商品时使用 DataFile
  3. EnableLog 为 true 时输出日志。

完整答案

AppOptions options = new AppOptions { DataFile = "products.txt", EnableLog = true }; ProductService service = new ProductService(options); service.Save("键盘"); service.Save("鼠标"); class AppOptions { public string DataFile { get; set; } = "data.txt"; public bool EnableLog { get; set; } } class ProductService { private readonly AppOptions _options; public ProductService(AppOptions options) { _options = options; } public void Save(string name) { File.AppendAllText(_options.DataFile, name + Environment.NewLine); if (_options.EnableLog) { Console.WriteLine($"保存商品: {name}"); } } }

作业 4:环境变量覆盖配置

要求:

  1. 默认 DataFileproducts.txt
  2. 设置环境变量 PRODUCT_DATA_FILE
  3. 如果环境变量有值,就覆盖默认配置。

完整答案

AppOptions options = new AppOptions { DataFile = "products.txt" }; Environment.SetEnvironmentVariable("PRODUCT_DATA_FILE", "products-local.txt"); string? fromEnv = Environment.GetEnvironmentVariable("PRODUCT_DATA_FILE"); if (!string.IsNullOrWhiteSpace(fromEnv)) { options.DataFile = fromEnv; } Console.WriteLine($"最终 DataFile: {options.DataFile}"); class AppOptions { public string DataFile { get; set; } = "data.txt"; }

本页最后要记住

  1. 配置是会变化的运行参数。
  2. 会变化的值不要散落在代码里。
  3. appsettings.json 是常见配置文件。
  4. 配置类让配置变成强类型对象。
  5. 默认值能处理缺失配置,但关键配置还要校验。
  6. 环境变量适合覆盖不同环境的配置。
  7. 密码、Token、真实连接字符串不要提交到源码里。