特性系统
命名空间: 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 -- 物理单位标签
标记属性的物理单位,用于界面展示(如 °C、V、A、rpm、kPa)。
[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 -- 中英文描述
为属性、枚举值、类等任何目标提供中英文文本。配合 LanguageType 和 ILanguage 接口实现运行时本地化。
[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时返回ZhLanguageType.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 使用:
| 类别 | 枚举值 |
|---|---|
| 标量类型 | Bool、Byte、Int16/Short、UInt16/Ushort、Int32/Int、UInt32/Uint、Int64/Long、UInt64/Ulong、Float/Single、Double、Char、String |
| 日期/时间 | DateTime、Date、Time(不支持读写) |
| 数组类型 | ByteArray、BoolArray、ShortArray/Int16Array、UshortArray/UInt16Array、IntArray/Int32Array、UintArray/UInt32Array、LongArray/Int64Array、UlongArray/UInt64Array、FloatArray/SingleArray、DoubleArray |
| 哨兵值 | None |
别名(如 Short = Int16、Float = Single)用于方便使用。
最佳实践
- 始终将
AutoAllocatingAttribute与AutoAllocatingTagAttribute配对使用。 前者在枚举值上选择属性名称;后者在类属性上标识哪个枚举类型驱动分配。 - 将
VerifyAttribute用作黑名单而非白名单。 匹配的模式会被拒绝。保持模式精确,并使用多样化的输入进行测试。 - 在
ByteStructureAttribute.Copy中设置正确的offset和end参数。end标志控制填充是出现在数据之前还是之后。 - 在每个面向用户的字符串上使用
MultilingualAttribute。 枚举值、属性标签和 UI 可见字段都能从集中化的本地化中受益。 - 叠加特性以获得完整的元数据。 一个属性可以同时携带
[Display]、[Unit]、[Multilingual]、[ByteStructure]和[Verify]-- 每个特性控制不同的关注点。 - 依赖反射读取特性。
ParamModel和IArgs在运行时自动读取并应用这些特性。遵循约定即可获得正确的 UI 和行为。
