You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# Web API返回带方括号的JSON是否合规?需修改控制器吗?

问题解答

1. 返回格式是否有效?

这个JSON格式本身语法上是有效的,但从REST API的设计语义来看不合理:你这个接口是通过唯一的SerialNumber查询单台设备信息,却返回数组(哪怕只有一个元素),会让调用方额外做“从数组取第一个元素”的冗余操作,不符合接口的预期用途。

2. 当前代码的核心问题

  • 严重SQL注入风险:直接把SerialNumber拼接进SQL条件(比如"[SerialNumber]='" + SerialNumber + "'"),如果传入恶意字符(例如' OR 1=1 --),会直接篡改查询逻辑,导致数据泄露或破坏。
  • 重复数据库查询:对同一个SerialNumber执行了9次几乎完全相同的数据库查询(仅字段不同),会大幅增加数据库负载,拖慢接口响应速度。
  • 返回类型语义不符:明明是查询单个设备,却返回IEnumerable<VijilconOn>数组,和接口实际功能不匹配。
  • 冗余代码:用Enumerable.Range(1,1)生成单个元素完全没必要,直接创建对象即可。
  • 错误处理不规范:设备不存在时,仅通过Action字段返回提示,没有返回对应的HTTP状态码(比如404 Not Found),不符合REST规范,调用方无法通过状态码快速判断结果。

3. 必须修改控制器逻辑,优化方案如下

优化后的控制器代码

[HttpGet(Name = "VijilconOn")]
public IActionResult Get(string serialNumber, string ipAddress)
{
    // 一次性查询所有需要的设备字段,避免重复查询和SQL注入
    var deviceInfo = Functions.GetDeviceInfoBySerialNumber(serialNumber);

    if (deviceInfo == null)
    {
        // 设备不存在时返回404状态码,符合REST规范
        return NotFound(new VijilconOn
        {
            SerialNumber = serialNumber,
            IPAddress = ipAddress,
            Action = "Device not found"
        });
    }

    // 组装返回对象
    var result = new VijilconOn
    {
        SerialNumber = serialNumber,
        IPAddress = Functions.RecordIPAddress(serialNumber, ipAddress).ToString(),
        HeartbeatWait = deviceInfo.HeartbeatWait,
        AlertSoundTime = deviceInfo.AlertSoundTime,
        FullBatteryLevel = deviceInfo.FullBatteryLevel,
        MiddleBatteryLevel = deviceInfo.MiddleBatteryLevel,
        LowBatteryLevel = deviceInfo.LowBatteryLevel,
        CriticalBatteryLevel = deviceInfo.CriticalBatteryLevel,
        TooHardMovement = deviceInfo.TooHardMovement,
        UnitID = deviceInfo.FriendlyName.ToString(),
        ReportPhone = deviceInfo.ReportPhone.ToString(),
        Action = Functions.CheckIn(serialNumber, "ON").ToString()
    };

    // 返回单个对象而非数组
    return Ok(result);
}

配套修改Functions类,新增参数化查询方法

// 新增封装设备查询结果的实体
public class DeviceInfo
{
    public int HeartbeatWait { get; set; }
    public int AlertSoundTime { get; set; }
    public int FullBatteryLevel { get; set; }
    public int MiddleBatteryLevel { get; set; }
    public int LowBatteryLevel { get; set; }
    public int CriticalBatteryLevel { get; set; }
    public int TooHardMovement { get; set; }
    public object FriendlyName { get; set; }
    public object ReportPhone { get; set; }
}

// 参数化查询,避免SQL注入,一次性获取所有字段
public static DeviceInfo GetDeviceInfoBySerialNumber(string serialNumber)
{
    string sql = @"SELECT HeartbeatWait, AlertSoundTime, FullBatteryLevel, 
                      MiddleBatteryLevel, LowBatteryLevel, CriticalBatteryLevel, 
                      TooHardMovement, FriendlyName, ReportPhone 
                   FROM Devices 
                   WHERE SerialNumber = @SerialNumber";

    // 示例用ADO.NET实现,建议改用EF Core等ORM框架简化操作
    using (var conn = new SqlConnection("你的数据库连接字符串"))
    {
        conn.Open();
        using (var cmd = new SqlCommand(sql, conn))
        {
            cmd.Parameters.AddWithValue("@SerialNumber", serialNumber);
            using (var reader = cmd.ExecuteReader())
            {
                if (reader.Read())
                {
                    return new DeviceInfo
                    {
                        HeartbeatWait = reader.GetInt32(reader.GetOrdinal("HeartbeatWait")),
                        AlertSoundTime = reader.GetInt32(reader.GetOrdinal("AlertSoundTime")),
                        FullBatteryLevel = reader.GetInt32(reader.GetOrdinal("FullBatteryLevel")),
                        MiddleBatteryLevel = reader.GetInt32(reader.GetOrdinal("MiddleBatteryLevel")),
                        LowBatteryLevel = reader.GetInt32(reader.GetOrdinal("LowBatteryLevel")),
                        CriticalBatteryLevel = reader.GetInt32(reader.GetOrdinal("CriticalBatteryLevel")),
                        TooHardMovement = reader.GetInt32(reader.GetOrdinal("TooHardMovement")),
                        FriendlyName = reader["FriendlyName"],
                        ReportPhone = reader["ReportPhone"]
                    };
                }
                return null;
            }
        }
    }
}

额外建议

  • 遵循REST命名规范:参数名用小驼峰(如serialNumber而非SerialNumber),接口路径建议改为/api/devices/{serialNumber}这类更符合语义的格式。
  • 改用ORM框架(如Entity Framework Core):进一步简化数据库操作,彻底规避SQL注入风险,提升代码可维护性。
  • 全局错误处理:添加全局异常过滤器,统一处理数据库异常等情况,返回标准化的错误响应。

内容的提问来源于stack exchange,提问作者Matt Schwartz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 14:37:36