特性参考 - Snet Docs

特性系统

命名空间: Snet.Model.attribute


概述

Snet 提供了 7 个自定义特性(Attribute),用于在协议定义、参数模型和 DTO 中声明元数据。这些特性驱动基于反射的 UI 生成(ParamModel)、协议自动分配、字节数组映射、输入验证和多语言描述。

+-----------------------------+
|     [Display]               | --> UI 表单生成(ParamModel 反射)
|     [Unit]                  | --> 物理单位显示("°C"、"V"、"A")
|     [Verify]                | --> 正则验证 + ReDoS 防护
|     [AutoAllocatingTag]     | --> 标记承载枚举判别器的属性(每类最多一个)
|     [AutoAllocating]        | --> 将枚举值映射到属性名称数组
|     [ByteStructure]         | --> 字节数组区间映射
|     [Multilingual]          | --> 枚举值/属性的中英文描述
+-----------------------------+

特性参考

1. DisplayAttribute -- UI 表单配置

标记属性以便通过 ParamModel 反射机制自动生成 UI 表单。控制可见性、可编辑性、是否必填、数据类别和提示文本。

[AttributeUsage(AttributeTargets.All)]
public class DisplayAttribute : Attribute
{
    public DisplayAttribute(bool Use, bool Show, bool MustFillIn, ParamModel.dataCate DataCate, string DetailsTips = null);
}
属性 类型 描述
Show bool true = 显示, false = 隐藏
Use bool true = 允许输入, false = 只读
MustFillIn bool true = 必填项
DetailsTips string 表单中展示的提示/帮助文本
DataCate dataCate UI 控件类型: text(文本框)或 select(下拉框)

使用示例:

public class DeviceParams
{
    [Display(Use: true, Show: true, MustFillIn: true, DataCate: dataCate.text, DetailsTips: "请输入设备 IP 地址")]
    public string IpAddress { get; set; }

    [Display(Use: true, Show: true, MustFillIn: false, DataCate: dataCate.select, DetailsTips: "选择通信协议")]
    public string Protocol { get; set; }

    [Display(Use: false, Show: false, MustFillIn: false, DataCate: dataCate.text)]
    public string InternalId { get; set; } // 隐藏在 UI 中
}

2. UnitAttribute -- 物理单位标签

标记属性的物理单位,用于界面展示(如 °CVArpmkPa)。

[AttributeUsage(AttributeTargets.All)]
public class UnitAttribute : Attribute
{
    public UnitAttribute(string Unit);
    public string Unit { get; set; }
}

使用示例:

public class SensorReading
{
    [Unit("°C")]
    [Display(true, true, false, dataCate.text, "温度值")]
    public double Temperature { get; set; }

    [Unit("V")]
    [Display(true, true, false, dataCate.text, "电压值")]
    public double Voltage { get; set; }

    [Unit("rpm")]
    public int MotorSpeed { get; set; }

    [Unit("kPa")]
    public double Pressure { get; set; }
}

ParamModel 反射系统读取 Unit 值并追加到渲染的表单字段标签中。


3. VerifyAttribute -- 正则验证(黑名单模式)

使用正则表达式验证字符串输入。采用黑名单模式: 如果正则表达式匹配成功,则验证失败。内置 ReDoS 防护,超时时间为 2 秒。

[AttributeUsage(AttributeTargets.All)]
public class VerifyAttribute : Attribute
{
    public VerifyAttribute(string pattern, string failTips);
    public string Pattern { get; set; }
    public string FailTips { get; set; }
    public bool Verify(string source, out string? failTips, bool ignoreCase = false);
}

核心设计: Verify 在输入安全(正则未匹配)时返回 true。此黑名单模式与传统正则验证逻辑有意相反。

使用示例:

public class UserInput
{
    // 拒绝疑似 SQL 注入的字符串
    [Verify(@"(\b(SELECT|DROP|INSERT|DELETE|UPDATE|ALTER)\b)|([';]|(--))",
        "输入内容包含潜在危险的 SQL 字符")]
    public string SearchQuery { get; set; }

    // 拒绝包含路径遍历序列的字符串
    [Verify(@"\.\./|\.\.\\|%2e%2e|%252e",
        "输入内容包含路径遍历模式")]
    public string FilePath { get; set; }
}

// 验证逻辑
var attr = new VerifyAttribute(@"<script|javascript:", "检测到 XSS 攻击尝试");
if (!attr.Verify(userInput, out var failTip))
{
    Console.WriteLine($"验证失败: {failTip}");
    // 处理无效输入
}

ignoreCase 参数(默认 false)控制是否启用大小写不敏感匹配。


4. AutoAllocatingAttribute -- 枚举到属性映射

将协议枚举值映射到属性名称数组。配合 AutoAllocatingTagAttribute 使用,通过 IArgs.GetAutoAllocatingArgs() 实现自动参数检索。

[AttributeUsage(AttributeTargets.All)]
public class AutoAllocatingAttribute : Attribute
{
    public AutoAllocatingAttribute(string[] propertyNameArray);
    public string[] PropertyNameArray { get; set; }
}

使用示例:

public enum ReadCommand
{
    [AutoAllocating(new[] { nameof(DeviceParams.Temperature), nameof(DeviceParams.Humidity) })]
    EnvironmentalData,      // 环境数据

    [AutoAllocating(new[] { nameof(DeviceParams.Voltage), nameof(DeviceParams.Current), nameof(DeviceParams.Power) })]
    ElectricalData,         // 电气数据

    [AutoAllocating(new[] { nameof(DeviceParams.StatusFlags) })]
    Status                  // 状态
}

5. AutoAllocatingTagAttribute -- 判别器标记

标识类中唯一一个承载枚举值的属性,该枚举用于自动分配。每个类最多一个

[AttributeUsage(AttributeTargets.All)]
public class AutoAllocatingTagAttribute : Attribute
{
    public AutoAllocatingTagAttribute(Type enumType);
    public Type EnumType { get; set; }
}

使用示例(配合 AutoAllocatingAttribute):

public class DeviceParams
{
    // 此属性是自动分配的判别器
    [AutoAllocatingTag(typeof(ReadCommand))]
    public ReadCommand Command { get; set; }

    public double Temperature { get; set; }
    public double Humidity { get; set; }
    public double Voltage { get; set; }
    public double Current { get; set; }
    public double Power { get; set; }
    public byte StatusFlags { get; set; }
}

// 运行时自动分配解析属性:
// Command = EnvironmentalData -> 检索 [Temperature, Humidity]
// Command = ElectricalData    -> 检索 [Voltage, Current, Power]
// Command = Status            -> 检索 [StatusFlags]

6. ByteStructureAttribute -- 字节数组映射

将字节数组中的区间映射到结构化字段。用于处理原始通信缓冲区时的协议级字节解析。

[AttributeUsage(AttributeTargets.All)]
public class ByteStructureAttribute : Attribute
{
    public ByteStructureAttribute(int index, int length, DataType dataType);  // 显式长度
    public ByteStructureAttribute(int index, DataType dataType);              // 长度默认为 1

    public int Index { get; }
    public int Length { get; }
    public DataType DataType { get; }
    public byte[] Copy(byte[] source, int offset = 0, bool end = false);
}
参数 描述
index 起始字节位置(从 0 开始)
length 要读取的字节数(单参数重载中默认为 1)
dataType 用于解释的 DataType 枚举值
offset 输出数组中的额外填充字节数
end true 时,数据从目标数组的位置 0 开始(忽略 offset 的填充方向)

Copy 方法:

  • 验证 Index + Length <= source.Length;源数据不足时抛出 ArgumentException
  • 创建新的 byte[Length + offset] 数组。
  • source[Index..]Length 个字节拷贝到目标数组。

使用示例:

public class ModbusFrame
{
    [ByteStructure(0, 1, DataType.Byte)]
    public byte SlaveAddress { get; set; }    // 位置 0,1 字节

    [ByteStructure(1, 1, DataType.Byte)]
    public byte FunctionCode { get; set; }    // 位置 1,1 字节

    [ByteStructure(2, 2, DataType.Int16)]
    public short RegisterValue { get; set; }  // 位置 2-3,2 字节

    [ByteStructure(4, 4, DataType.Float)]
    public float AnalogValue { get; set; }    // 位置 4-7,4 字节
}

// 提取特定字节区间
var attr = new ByteStructureAttribute(2, 4, DataType.Float);
byte[] rawFrame = new byte[] { 0x01, 0x03, 0x42, 0x48, 0x00, 0x00 };
try
{
    byte[] floatBytes = attr.Copy(rawFrame);      // 拷贝字节 [2..5]
    byte[] padded = attr.Copy(rawFrame, offset: 2); // 输出 [0,0,0x42,0x48,0x00,0x00]
}
catch (ArgumentException ex)
{
    Console.WriteLine($"缓冲区长度不足: {ex.Message}");
}

7. MultilingualAttribute -- 中英文描述

为属性、枚举值、类等任何目标提供中英文文本。配合 LanguageTypeILanguage 接口实现运行时本地化。

[AttributeUsage(AttributeTargets.All)]
public class MultilingualAttribute : Attribute
{
    public MultilingualAttribute(string zh, string en);
    public string Zh { get; set; }
    public string En { get; set; }
    public string? Get(LanguageType language);
}

Get 方法返回:

  • LanguageType.zh 时返回 Zh
  • LanguageType.en 时返回 En
  • 无法识别的语言类型返回 null

使用示例:

// 用于枚举值
public enum AlarmLevel
{
    [Multilingual("正常", "Normal")]
    Normal,

    [Multilingual("警告", "Warning")]
    Warning,

    [Multilingual("严重", "Critical")]
    Critical,

    [Multilingual("紧急", "Emergency")]
    Emergency
}

// 用于类属性
public class PumpConfig
{
    [Multilingual("泵名称", "Pump Name")]
    public string Name { get; set; }

    [Multilingual("运行状态", "Running Status")]
    public bool IsRunning { get; set; }

    [Multilingual("额定功率", "Rated Power")]
    [Unit("kW")]
    public double RatedPower { get; set; }
}

// 运行时查找
var attr = (MultilingualAttribute)Attribute.GetCustomAttribute(
    typeof(AlarmLevel).GetField("Warning"),
    typeof(MultilingualAttribute)
);
string label = attr.Get(LanguageType.zh); // "警告"
string label = attr.Get(LanguageType.en); // "Warning"

特性组合

各特性被设计为可在同一目标上组合使用。常见组合模式:

public class AnalogInput
{
    [Multilingual("温度", "Temperature")]
    [Unit("°C")]
    [Display(true, true, false, dataCate.text, "当前温度读数")]
    [ByteStructure(0, 4, DataType.Float)]
    public float Temperature { get; set; }

    [Multilingual("电压", "Voltage")]
    [Unit("V")]
    [Display(true, true, false, dataCate.text, "输入电压")]
    [Verify(@"^[^<>&'""]+$", "电压值包含无效字符")]
    [ByteStructure(4, 4, DataType.Float)]
    public float Voltage { get; set; }

    [Multilingual("报警状态", "Alarm Status")]
    [ByteStructure(8, 1, DataType.Byte)]
    public byte AlarmStatus { get; set; }
}

DataType 枚举参考

ByteStructureAttribute.DataType 使用:

类别 枚举值
标量类型 BoolByteInt16/ShortUInt16/UshortInt32/IntUInt32/UintInt64/LongUInt64/UlongFloat/SingleDoubleCharString
日期/时间 DateTimeDateTime(不支持读写)
数组类型 ByteArrayBoolArrayShortArray/Int16ArrayUshortArray/UInt16ArrayIntArray/Int32ArrayUintArray/UInt32ArrayLongArray/Int64ArrayUlongArray/UInt64ArrayFloatArray/SingleArrayDoubleArray
哨兵值 None

别名(如 Short = Int16Float = Single)用于方便使用。


最佳实践

  1. 始终将 AutoAllocatingAttributeAutoAllocatingTagAttribute 配对使用。 前者在枚举值上选择属性名称;后者在类属性上标识哪个枚举类型驱动分配。
  2. VerifyAttribute 用作黑名单而非白名单。 匹配的模式会被拒绝。保持模式精确,并使用多样化的输入进行测试。
  3. ByteStructureAttribute.Copy 中设置正确的 offsetend 参数。 end 标志控制填充是出现在数据之前还是之后。
  4. 在每个面向用户的字符串上使用 MultilingualAttribute 枚举值、属性标签和 UI 可见字段都能从集中化的本地化中受益。
  5. 叠加特性以获得完整的元数据。 一个属性可以同时携带 [Display][Unit][Multilingual][ByteStructure][Verify] -- 每个特性控制不同的关注点。
  6. 依赖反射读取特性。 ParamModelIArgs 在运行时自动读取并应用这些特性。遵循约定即可获得正确的 UI 和行为。