InventorySample数据验证深度解析:ValidationConstraint与Model绑定校验的完整实现
【免费下载链接】InventorySampleSample UWP application for LOB scenarios项目地址: https://gitcode.com/gh_mirrors/in/InventorySample
InventorySample 是一个面向企业级(LOB)场景的 UWP 示例应用,本文带你完整拆解它的数据验证机制:如何通过ValidationConstraint约束类与 MVVM 模式的 Model 绑定校验,在保存前拦截非法数据,保障数据完整性。无论你是 UWP 新手还是 MVVM 实践者,都能在 10 分钟内看懂这套开箱即用的校验体系。
上图是 InventorySample 的客户编辑界面:右侧表单中的每个输入框,在点击 Save 之前都会经过一套约束校验,任何必填项缺失都会被立即拦截并提示。
为什么 UWP 应用必须做数据验证?
任何接受用户输入的应用,都必须先验证数据才能入库,原因有三:
- 🛡️数据完整性:防止空姓名、非法数量等脏数据写入数据库;
- ⚡提前失败:在保存前就报错,而不是等到数据库层才抛异常;
- 🔒业务规则落地:如"折扣不能大于小计金额"这类规则,代码化后不可绕过。
在 MVVM 架构中,验证逻辑应该放在ViewModel层执行、作用于Model数据,并向 View 反馈错误——这正是 InventorySample 的设计思路。
核心接口 IValidationConstraint:一条规则 = 一个判定函数 + 一句错误提示
整个验证体系的地基是 ValidationConstraint.cs 中的接口,它非常简洁:
public interface IValidationConstraint<T> { Func<T, bool> Validate { get; } // 校验函数:输入Model,返回是否通过 string Message { get; } // 失败时的提示文案 }项目已内置7 个现成的约束类,覆盖绝大多数业务场景:
| 约束类 | 用途 | 提示文案 |
|---|---|---|
RequiredConstraint<T> | 字段必填 | 属性 '{名称}' 不能为空 |
RequiredGreaterThanZeroConstraint<T> | 必填且大于零 | 属性 '{名称}' 不能为空 |
PositiveConstraint<T> | 数值必须 ≥ 0 | 属性 '{名称}' 必须为正数 |
NonZeroConstraint<T> | 数值不能为零 | 属性 '{名称}' 不能为零 |
GreaterThanConstraint<T> | 必须大于指定值 | 属性 '{名称}' 必须大于 {值} |
NonGreaterThanConstraint<T> | 不能超过指定值 | 属性 '{名称}' 不能大于 {值} |
LessThanConstraint<T> | 必须小于指定值 | 属性 '{名称}' 必须小于 {值} |
所有约束都是泛型T,同一个约束类可以作用于任意 Model;数值型约束内部用Double.TryParse安全解析,解析失败时不会抛异常,而是按宽松策略放行——对新手很友好。
GenericDetailsViewModel 基类:所有详情页"免费获得"的保存前校验
这套校验不是每个页面各自实现的,而是统一收敛在详情页基类中。如下面的 ViewModel 继承体系图所示,所有*DetailsViewModel都继承自GenericDetailsViewModel<TModel>:
在 GenericDetailsViewModel.cs 中,保存命令的流程是:
- 用户点击Save→ 触发
SaveCommand; - 先调用
Validate(EditableItem)逐条执行约束,任一失败立即返回Result.Error; - 校验通过才执行
SaveAsync()真正写库; - 校验失败则弹出对话框:"Validation Error — {错误提示}。请修正后重试"。
子 ViewModel 要接入这套机制,只需重写一个虚方法GetValidationConstraints(model),返回约束列表即可——这就是模板方法模式的经典应用。
实战案例:三种实体的真实校验规则
客户:8 个必填字段(RequiredConstraint)
CustomerDetailsViewModel.cs 为CustomerModel声明了姓名、邮箱、地址、城市、地区、邮编、国家共 8 条必填约束。客户详情页就是下图中的编辑面板,任何必填项留空都无法保存:
订单:条件校验(按订单状态动态追加规则)
OrderDetailsViewModel.cs 展示了更高级的用法——条件约束:
- 任何订单都必须指定客户(
RequiredGreaterThanZeroConstraint校验CustomerID); - 当订单状态 > 0(已提交)时,才追加"付款方式必填";
- 状态再进一步(> 1,已发货)时,才追加"承运商必填"。
约束列表是动态yield return生成的,所以同一套机制能表达分阶段业务规则,无需 if-else 散落各处。
订单明细:数量与折扣的组合拳
OrderItemDetailsViewModel.cs 是约束组合最丰富的示例,对同一属性叠加多条规则:
- Quantity(数量):不为零 + 必须为正 + 必须小于 100;
- Discount(折扣):必须为正 +不能大于 Subtotal(小计)。
注意最后一条:NonGreaterThanConstraint的上限值直接取自当前 Model 的model.Subtotal属性,说明约束的边界值不是硬编码的,而是与业务数据实时联动。
Result 结果对象:验证结果与对话框的衔接
校验的返回值统一使用 Result.cs 中的Result类:IsOk标记成败,Message/Description分别承载标题与详情。这种"无异常"的错误传递方式让 ViewModel 逻辑干净、可测试,也与项目中服务层的数据访问风格保持一致。
小结:这套方案好在哪?
| 优点 | 说明 |
|---|---|
| 单一接口 | 一条约束 = 一个函数 + 一句文案,5 分钟即可上手 |
| 零重复 | 基类统一执行,每个详情页只写规则、不写流程 |
| 泛型复用 | 7 个约束类适用于所有 Model |
| 规则内聚 | 校验逻辑随 ViewModel 定义,随业务演进 |
📌 关键文件速查:
- 约束接口与 7 个实现:ValidationConstraint.cs
- 保存前校验基类:GenericDetailsViewModel.cs
- 官方验证机制文档:Validation.md
- 数据模型定义:Models/
理解了IValidationConstraint+GenericDetailsViewModel的组合,你就掌握了 InventorySample 数据验证的完整闭环:定义约束 → 重写方法 → 保存自动校验 → 失败弹窗提示。把这套模式搬进你自己的 MVVM 项目,数据质量就有了第一道坚实防线。
【免费下载链接】InventorySampleSample UWP application for LOB scenarios项目地址: https://gitcode.com/gh_mirrors/in/InventorySample
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考