文章目录
下面我将展示如何从config.json文件读取配置并转换为强类型的Settings对象,使用.NET 8和System.Text.Json。
1. 定义配置模型类
首先创建表示配置结构的模型类:
public class ModbusSettings
{
public string IpAddress { get; set; }
public int Port { get; set; }
public byte SlaveId { get; set; }
public int TimeoutMs { get; set; } = 5000;
public List<RegisterMap> RegisterMaps { get; set; }
}
public class RegisterMap
{
public string Name { get; set; }
public ushort Address { get; set; }
public DataType DataType { get; set; }
public float ScalingFactor { get; set; } = 1.0f;
}
public enum DataType
{
UInt16,
Int16,
UInt32,
Int32,
Float
}
2. 创建示例config.json文件
{
"IpAddress": "192.168.1.202",
"Port": 4196,
"SlaveId": 40,
"TimeoutMs": 3000,
"RegisterMaps": [
{
"Name": "Temperature",
"Address": 22,
"DataType": "Float",
"ScalingFactor": 0.1
},
{
"Name": "Pressure",
"Address": 26,
"DataType": "Float"
}
]
}
3. 实现配置读取器
using System.Text.Json;
using System.Text.Json.Serialization;
public static class ConfigLoader
{
private static readonly JsonSerializerOptions _options = new()
{
PropertyNameCaseInsensitive = true,
Converters = { new JsonStringEnumConverter() },
AllowTrailingCommas = true,
ReadCommentHandling = JsonCommentHandling.Skip
};
public static ModbusSettings LoadConfig(string filePath = "config.json")
{
try
{
if (!File.Exists(filePath))
{
throw new FileNotFoundException($"配置文件 {filePath} 不存在");
}
string json = File.ReadAllText(filePath);
var settings = JsonSerializer.Deserialize<ModbusSettings>(json, _options);
if (settings == null)
{
throw new InvalidOperationException("配置文件内容为空或格式不正确");
}
// 验证必要配置
if (string.IsNullOrWhiteSpace(settings.IpAddress))
{
throw new InvalidDataException("IP地址不能为空");
}
if (settings.Port <= 0 || settings.Port > 65535)
{
throw new InvalidDataException("端口号必须在1-65535范围内");
}
return settings;
}
catch (JsonException ex)
{
throw new InvalidOperationException($"配置文件解析失败: {ex.Message}", ex);
}
}
// 可选:保存配置的方法
public static void SaveConfig(ModbusSettings settings, string filePath = "config.json")
{
var options = new JsonSerializerOptions
{
WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Converters = { new JsonStringEnumConverter() }
};
string json = JsonSerializer.Serialize(settings, options);
File.WriteAllText(filePath, json);
}
}
4. 使用示例
class Program
{
static async Task Main()
{
try
{
// 加载配置
var settings = ConfigLoader.LoadConfig();
Console.WriteLine($"成功加载配置: {settings.IpAddress}:{settings.Port}");
// 使用配置初始化Modbus帮助类
var modbusHelper = new ModbusTcpHelper(settings.IpAddress, settings.Port);
await modbusHelper.ConnectAsync(settings.TimeoutMs);
// 读取配置中定义的寄存器
foreach (var map in settings.RegisterMaps)
{
try
{
// 根据数据类型读取数据
object value = map.DataType switch
{
DataType.Float => await ReadFloatRegister(modbusHelper, settings.SlaveId, map),
_ => await ReadStandardRegister(modbusHelper, settings.SlaveId, map)
};
// 应用缩放因子
if (value is float floatValue && map.ScalingFactor != 1.0f)
{
value = floatValue * map.ScalingFactor;
}
Console.WriteLine($"{map.Name} ({map.Address}): {value}");
}
catch (Exception ex)
{
Console.WriteLine($"读取 {map.Name} 失败: {ex.Message}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"发生错误: {ex.Message}");
}
}
private static async Task<object> ReadFloatRegister(ModbusTcpHelper helper, byte slaveId, RegisterMap map)
{
// 浮点数需要读取2个寄存器(4字节)
float[] values = await helper.ReadFloatRegistersAsync(slaveId, map.Address, 2);
return values[0];
}
private static async Task<object> ReadStandardRegister(ModbusTcpHelper helper, byte slaveId, RegisterMap map)
{
// 读取单个寄存器
ushort[] values = await helper.ReadRegistersAsync(slaveId, map.Address, 1);
return map.DataType switch
{
DataType.UInt16 => values[0],
DataType.Int16 => (short)values[0],
_ => throw new NotSupportedException($"不支持的数据类型: {map.DataType}")
};
}
}
5. 扩展ModbusTcpHelper类
在之前的ModbusTcpHelper类中添加以下方法以支持更多数据类型:
/// <summary>
/// 读取标准寄存器值
/// </summary>
public async Task<ushort[]> ReadRegistersAsync(byte slaveId, ushort startAddress, ushort registerCount)
{
byte[] request = new byte[6];
request[0] = slaveId;
request[1] = 0x03; // 功能码: 读保持寄存器
request[2] = (byte)(startAddress >> 8);
request[3] = (byte)(startAddress & 0xFF);
request[4] = (byte)(registerCount >> 8);
request[5] = (byte)(registerCount & 0xFF);
byte[] response = await SendRequestAsync(request);
// 提取寄存器数据(每个寄存器2字节)
ushort[] registers = new ushort[registerCount];
for (int i = 0; i < registerCount; i++)
{
int offset = 3 + i * 2;
registers[i] = (ushort)((response[offset] << 8) | response[offset + 1]);
}
return registers;
}
6. 配置验证增强版
可以添加更详细的配置验证:
public class ModbusSettingsValidator
{
public static void Validate(ModbusSettings settings)
{
if (settings == null)
throw new ArgumentNullException(nameof(settings));
if (string.IsNullOrWhiteSpace(settings.IpAddress))
throw new InvalidDataException("IP地址不能为空");
if (!IPAddress.TryParse(settings.IpAddress, out _))
throw new InvalidDataException("IP地址格式不正确");
if (settings.Port < 1 || settings.Port > 65535)
throw new InvalidDataException("端口号必须在1-65535范围内");
if (settings.SlaveId < 1 || settings.SlaveId > 247)
throw new InvalidDataException("从站ID必须在1-247范围内");
if (settings.TimeoutMs < 100)
throw new InvalidDataException("超时时间不能小于100ms");
if (settings.RegisterMaps == null || settings.RegisterMaps.Count == 0)
throw new InvalidDataException("至少需要配置一个寄存器映射");
foreach (var map in settings.RegisterMaps)
{
if (string.IsNullOrWhiteSpace(map.Name))
throw new InvalidDataException("寄存器名称不能为空");
if (map.Address > 65535)
throw new InvalidDataException($"寄存器地址 {map.Name} 超出范围");
if (map.ScalingFactor == 0)
throw new InvalidDataException($"缩放因子 {map.Name} 不能为零");
}
}
}
// 在ConfigLoader中使用
var settings = JsonSerializer.Deserialize<ModbusSettings>(json, _options);
ModbusSettingsValidator.Validate(settings);
7. 处理特殊需求
如果需要支持更复杂的配置,如:
- 自定义字节序:在RegisterMap中添加ByteOrder属性
- 位操作:支持读取线圈和离散输入
- 批量读取优化:配置中定义哪些寄存器可以批量读取
示例扩展配置:
{
"BatchReads": [
{
"Name": "SensorGroup1",
"StartAddress": 22,
"Count": 4,
"MapsTo": ["Temperature", "Humidity", "Pressure", "Voltage"]
}
]
}
这种架构提供了灵活的配置方式,同时保持了类型安全和良好的错误处理。
配置文件种类及其比较:JSON配置文件的优势
一、常见配置文件类型及示例
1. JSON (JavaScript Object Notation)
示例:
{
"appSettings": {
"name": "数据采集系统",
"version": "1.2.0",
"debugMode": false,
"sampleRate": 1000
},
"modbus": {
"ip": "192.168.1.202",
"port": 502,
"timeout": 3000
}
}
2. XML (eXtensible Markup Language)
示例:
<configuration>
<appSettings>
<name>数据采集系统</name>
<version>1.2.0</version>
<debugMode>false</debugMode>
<sampleRate>1000</sampleRate>
</appSettings>
<modbus>
<ip>192.168.1.202</ip>
<port>502</port>
<timeout>3000</timeout>
</modbus>
</configuration>
3. YAML (YAML Ain’t Markup Language)
示例:
appSettings:
name: "数据采集系统"
version: "1.2.0"
debugMode: false
sampleRate: 1000
modbus:
ip: "192.168.1.202"
port: 502
timeout: 3000
4. INI (Initialization File)
示例:
[appSettings]
name=数据采集系统
version=1.2.0
debugMode=false
sampleRate=1000
[modbus]
ip=192.168.1.202
port=502
timeout=3000
5. 环境变量
示例:
APP_NAME=数据采集系统
APP_VERSION=1.2.0
MODBUS_IP=192.168.1.202
MODBUS_PORT=502
6. 二进制格式
示例:通常不可读,如Windows注册表、Protocol Buffers等
二、JSON配置文件的优势
1. 可读性强
JSON采用键值对结构和缩进格式,比XML更简洁:
{
"user": {
"name": "张三",
"age": 30,
"active": true
}
}
对比XML:
<user>
<name>张三</name>
<age>30</age>
<active>true</active>
</user>
2. 数据结构丰富
支持多种数据类型:
- 对象(嵌套结构)
- 数组
- 字符串
- 数字
- 布尔值
- null
{
"devices": [
{
"id": 1,
"type": "sensor",
"registers": [40001, 40002, 40003]
},
{
"id": 2,
"type": "actuator",
"enabled": true
}
]
}
3. 跨平台兼容性好
- 所有现代编程语言都内置JSON支持
- Web应用天然支持
- 移动端和嵌入式系统也能轻松处理
4. 工具生态完善
- 验证工具:JSON Schema验证
- 格式化工具:VS Code等编辑器内置支持
- 转换工具:可轻松转换为其他格式
- 在线工具:各种JSON在线解析和美化工具
5. 性能优异
- 解析速度比XML快
- 比YAML更高效
- 体积通常比XML小30%-50%
6. 现代开发标准
- REST API的标准数据格式
- NoSQL数据库(如MongoDB)使用类似JSON的BSON
- 前端框架(React/Vue等)直接支持
7. 支持注释(通过变通方式)
虽然标准JSON不支持注释,但可以通过以下方式实现:
{
"//note": "这是设备配置",
"device": {
"ip": "192.168.1.100",
"//status": "active|inactive",
"status": "active"
}
}
三、各类型配置文件对比表
特性 | JSON | XML | YAML | INI | 环境变量 | 二进制 |
---|---|---|---|---|---|---|
可读性 | ★★★★★ | ★★★☆☆ | ★★★★★ | ★★★☆☆ | ★★☆☆☆ | ☆☆☆☆☆ |
数据结构 | ★★★★★ | ★★★★★ | ★★★★★ | ★★☆☆☆ | ★☆☆☆☆ | ★★★★★ |
跨平台支持 | ★★★★★ | ★★★★★ | ★★★★☆ | ★★★☆☆ | ★★★★★ | ★★★☆☆ |
工具生态 | ★★★★★ | ★★★★★ | ★★★★☆ | ★★★☆☆ | ★★★☆☆ | ★★☆☆☆ |
解析性能 | ★★★★★ | ★★★☆☆ | ★★★☆☆ | ★★★★★ | ★★★★★ | ★★★★★ |
适合场景 | 通用 | 复杂结构 | 人工维护 | 简单配置 | 容器化 | 高性能 |
四、JSON配置的典型应用场景
1. 应用程序配置
{
"logging": {
"level": "debug",
"path": "/var/log/app.log"
},
"database": {
"connectionString": "Server=db;Database=appdb",
"timeout": 30
}
}
2. IoT设备配置
{
"deviceId": "SN-12345",
"sensors": [
{
"type": "temperature",
"address": 40001,
"unit": "°C"
},
{
"type": "humidity",
"address": 40003,
"scale": 0.1
}
]
}
3. 网络服务配置
{
"endpoints": {
"api": "https://api.example.com/v1",
"auth": "https://auth.example.com"
},
"retryPolicy": {
"maxAttempts": 3,
"delay": 1000
}
}
五、JSON配置的最佳实践
使用有意义的键名:
// 不好 "t": 30 // 好 "temperature": 30
合理组织层次结构:
{ "modbus": { "tcp": { "ip": "192.168.1.100", "port": 502 }, "rtu": { "port": "COM3", "baudRate": 9600 } } }
添加配置版本控制:
{ "configVersion": "1.1", "settings": { // ... } }
为重要配置添加默认值:
{ "timeout": 5000, // 默认5秒 "retries": 3 // 默认3次 }
使用JSON Schema验证:
{ "$schema": "./config-schema.json", // 实际配置内容 }
六、何时不选择JSON
虽然JSON有很多优点,但在以下情况可能需要考虑其他方案:
- 需要注释:考虑YAML
- 极简配置:考虑INI或环境变量
- 高性能场景:考虑二进制格式
- 人工编辑频繁:考虑YAML(更友好的格式)
七、总结
JSON作为配置文件格式具有以下核心优势:
- 完美的平衡性:在可读性、功能性和性能之间取得最佳平衡
- 语言无关性:几乎所有现代编程语言都原生支持
- 层次化结构:能清晰表达复杂配置关系
- 扩展性强:支持数组、嵌套对象等复杂数据结构
- 工具链完善:从编辑器支持到验证工具一应俱全
对于大多数应用场景,特别是需要表达结构化数据、跨平台使用或与现代Web技术栈集成的场景,JSON都是配置文件的最佳选择。