下面我将展示如何从config.json文件读取配置并转换为强类型的Settings对象,使用.NET 8和System.Text.Json。
首先创建表示配置结构的模型类:
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
}
{
"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"
}
]
}
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);
}
}
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}")
};
}
}
在之前的ModbusTcpHelper类中添加以下方法以支持更多数据类型:
///
/// 读取标准寄存器值
///
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;
}
可以添加更详细的配置验证:
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);
如果需要支持更复杂的配置,如:
示例扩展配置:
{
"BatchReads": [
{
"Name": "SensorGroup1",
"StartAddress": 22,
"Count": 4,
"MapsTo": ["Temperature", "Humidity", "Pressure", "Voltage"]
}
]
}
这种架构提供了灵活的配置方式,同时保持了类型安全和良好的错误处理。
示例:
{
"appSettings": {
"name": "数据采集系统",
"version": "1.2.0",
"debugMode": false,
"sampleRate": 1000
},
"modbus": {
"ip": "192.168.1.202",
"port": 502,
"timeout": 3000
}
}
示例:
<configuration>
<appSettings>
<name>数据采集系统name>
<version>1.2.0version>
<debugMode>falsedebugMode>
<sampleRate>1000sampleRate>
appSettings>
<modbus>
<ip>192.168.1.202ip>
<port>502port>
<timeout>3000timeout>
modbus>
configuration>
示例:
appSettings:
name: "数据采集系统"
version: "1.2.0"
debugMode: false
sampleRate: 1000
modbus:
ip: "192.168.1.202"
port: 502
timeout: 3000
示例:
[appSettings]
name=数据采集系统
version=1.2.0
debugMode=false
sampleRate=1000
[modbus]
ip=192.168.1.202
port=502
timeout=3000
示例:
APP_NAME=数据采集系统
APP_VERSION=1.2.0
MODBUS_IP=192.168.1.202
MODBUS_PORT=502
示例:通常不可读,如Windows注册表、Protocol Buffers等
JSON采用键值对结构和缩进格式,比XML更简洁:
{
"user": {
"name": "张三",
"age": 30,
"active": true
}
}
对比XML:
<user>
<name>张三name>
<age>30age>
<active>trueactive>
user>
支持多种数据类型:
{
"devices": [
{
"id": 1,
"type": "sensor",
"registers": [40001, 40002, 40003]
},
{
"id": 2,
"type": "actuator",
"enabled": true
}
]
}
虽然标准JSON不支持注释,但可以通过以下方式实现:
{
"//note": "这是设备配置",
"device": {
"ip": "192.168.1.100",
"//status": "active|inactive",
"status": "active"
}
}
特性 | JSON | XML | YAML | INI | 环境变量 | 二进制 |
---|---|---|---|---|---|---|
可读性 | ★★★★★ | ★★★☆☆ | ★★★★★ | ★★★☆☆ | ★★☆☆☆ | ☆☆☆☆☆ |
数据结构 | ★★★★★ | ★★★★★ | ★★★★★ | ★★☆☆☆ | ★☆☆☆☆ | ★★★★★ |
跨平台支持 | ★★★★★ | ★★★★★ | ★★★★☆ | ★★★☆☆ | ★★★★★ | ★★★☆☆ |
工具生态 | ★★★★★ | ★★★★★ | ★★★★☆ | ★★★☆☆ | ★★★☆☆ | ★★☆☆☆ |
解析性能 | ★★★★★ | ★★★☆☆ | ★★★☆☆ | ★★★★★ | ★★★★★ | ★★★★★ |
适合场景 | 通用 | 复杂结构 | 人工维护 | 简单配置 | 容器化 | 高性能 |
{
"logging": {
"level": "debug",
"path": "/var/log/app.log"
},
"database": {
"connectionString": "Server=db;Database=appdb",
"timeout": 30
}
}
{
"deviceId": "SN-12345",
"sensors": [
{
"type": "temperature",
"address": 40001,
"unit": "°C"
},
{
"type": "humidity",
"address": 40003,
"scale": 0.1
}
]
}
{
"endpoints": {
"api": "https://api.example.com/v1",
"auth": "https://auth.example.com"
},
"retryPolicy": {
"maxAttempts": 3,
"delay": 1000
}
}
使用有意义的键名:
// 不好
"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作为配置文件格式具有以下核心优势:
对于大多数应用场景,特别是需要表达结构化数据、跨平台使用或与现代Web技术栈集成的场景,JSON都是配置文件的最佳选择。