日期可见,模型为空。
表单显示已选日期。保存却提示日期必填。
这听起来像验证错误,但验证器报告的是事实:可空 .NET 属性仍为 null。浏览器与应用程序已悄然分歧。
最近提交的一项修复让核心教训格外清晰。原生 HTML 日期输入与组件转换器各自行为合理,却未共享同一格式契约。控件展示的状态从未被模型接受。
教训不止于单个组件:可见的 UI 状态并非绑定后应用状态的证明。
一个字段可能有三种表现形式
日期字段通常跨越三种不同的表现形式:
- 浏览器渲染的人类可见值。
- 原生输入在网络上传递的线格式值。
- .NET 模型中存储的类型化值。
对于 <input type="date">,浏览器可能以本地熟悉格式显示,但其值契约是形如 2026-08-01 的 ISO 日期。显示可变,线格式不变。
外层 Blazor 组件可能有不同默认。其转换器可用当前区域文化的短日期模式格式化与解析 DateTime?。该模式可能因文化而使用斜杠、不同字段顺序或其他分隔符。
两种行为单独看都合理。若未提供显式适配器直接组合,便会形成意外协议。
失败双向皆可
先考虑浏览器到模型方向。
用户选择日期,原生控件发出 ISO 值。区域文化感知转换器却期望当前短日期格式。转换失败,可空属性未更新。浏览器仍可显示自己拥有的选择,导致后续验证消息看似荒谬。
反向流程同样如此。
编辑表单从模型加载已有日期。转换器生成区域文化格式字符串。原生日期输入仅接受 ISO 值格式,拒绝该文本并渲染为空控件。若应用将空字段视为有意清空,从此状态保存可能将显示问题转化为数据丢失。
因此仅测试验证器、映射器或服务无法捕获缺陷。这些层从未触及浏览器契约与组件转换器之间的不一致。
让边界拥有唯一所有者
评审后的修复选用一个同时拥有选择器 UI 与类型化绑定契约的日期选择器组件。这也是周围应用既定惯例,减少新奇性并缩小改动范围。
这是可行方案之一,并非唯一。若原生日期输入至关重要,则需显式搭建桥梁。自定义 InputBase<DateOnly?>、显式 get/set 绑定适配器,或刻意使用原生 ISO 格式的转换器均可。
关键设计问题在于所有权:
- 谁负责将模型状态格式化给控件?
- 谁负责将控件值解析回模型?
- 两个方向是否使用同一契约?
- 转换失败在何处可见?
对于仅日期业务概念,DateOnly 可比午夜 DateTime 更准确表达意图。它不能消除线格式问题,但可避免时区与时间语义在仅日历工作流中不必要地介入。
测试绑定而非仅测试字符串
有用的回归套件会在代表性区域文化下完整测试字段边界。
从以下用例开始:
- 用户选择更新类型化模型属性。
- 已有模型值渲染回控件。
- 清除可选日期产生
null。 - 必填日期在模型仍为空时不可表现为已接受。
- 重新渲染保留有效值。
在多种区域文化下双向运行测试,包括短日期格式与原生 ISO 格式不同的文化。转换器单元测试有帮助,但渲染后的组件测试更强,因其包含绑定事件与组件状态。针对原生控件行为的小型浏览器测试更强。
不变式简单:
displayed date = wire date = model date
Enter fullscreen mode Exit fullscreen mode
字符串无需对人类看起来相同,但需经过刻意、可逆的映射。
权衡值得命名
原生控件轻量、熟悉、可访问且获浏览器支持。组件自有选择器会增加 JavaScript、样式、依赖重量及另一层 UI 抽象。
答案并非避免使用原生输入,而是诚实评估转换边界的代价。若外层组件无法可靠遵循原生控件的值协议,表面的简洁只是从未来的调试时间借来的。
同样,区域文化矩阵与渲染组件测试成本高于单个单元测试。应聚焦于边界而非复制所有表单场景。少量双向契约测试通常比大量从未触及 UI 协议的服务测试更具信心。
实用评审清单
评审类型化表单控件时,请问:
- 浏览器显示什么?
- 控件交换的确切值是什么?
- 模型要求的类型与空值语义是什么?
- 哪个组件拥有每个方向的转换?
- 测试了哪些区域文化?
- 转换失败是否可能留下看似正常的陈旧 UI?
日期让边界易见,但同样模式也出现在小数分隔符、百分比、货币、枚举与时区中。
屏幕证明浏览器渲染了什么。模型证明应用接受了什么。良好的绑定代码与测试需证明两者保持连接。
在您的 UI 中,哪里可能出现值看似有效却从未到达模型的情况?
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.