读取配置文件到Settings对象的完整实现

发布于:2025-06-15 ⋅ 阅读:(16) ⋅ 点赞:(0)

在这里插入图片描述

在这里插入图片描述

下面我将展示如何从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. 处理特殊需求

如果需要支持更复杂的配置,如:

  1. 自定义字节序:在RegisterMap中添加ByteOrder属性
  2. 位操作:支持读取线圈和离散输入
  3. 批量读取优化:配置中定义哪些寄存器可以批量读取

示例扩展配置:

{
  "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配置的最佳实践

  1. 使用有意义的键名

    // 不好
    "t": 30
    
    // 好
    "temperature": 30
    
  2. 合理组织层次结构

    {
      "modbus": {
        "tcp": {
          "ip": "192.168.1.100",
          "port": 502
        },
        "rtu": {
          "port": "COM3",
          "baudRate": 9600
        }
      }
    }
    
  3. 添加配置版本控制

    {
      "configVersion": "1.1",
      "settings": {
        // ...
      }
    }
    
  4. 为重要配置添加默认值

    {
      "timeout": 5000,  // 默认5秒
      "retries": 3      // 默认3次
    }
    
  5. 使用JSON Schema验证

    {
      "$schema": "./config-schema.json",
      // 实际配置内容
    }
    

六、何时不选择JSON

虽然JSON有很多优点,但在以下情况可能需要考虑其他方案:

  1. 需要注释:考虑YAML
  2. 极简配置:考虑INI或环境变量
  3. 高性能场景:考虑二进制格式
  4. 人工编辑频繁:考虑YAML(更友好的格式)

七、总结

JSON作为配置文件格式具有以下核心优势:

  1. 完美的平衡性:在可读性、功能性和性能之间取得最佳平衡
  2. 语言无关性:几乎所有现代编程语言都原生支持
  3. 层次化结构:能清晰表达复杂配置关系
  4. 扩展性强:支持数组、嵌套对象等复杂数据结构
  5. 工具链完善:从编辑器支持到验证工具一应俱全

对于大多数应用场景,特别是需要表达结构化数据、跨平台使用或与现代Web技术栈集成的场景,JSON都是配置文件的最佳选择。


网站公告

今日签到

点亮在社区的每一天
去签到