日期已顯示。模型卻是 Null。
表單顯示已選取日期。儲存卻提示日期為必填欄位。
聽起來像是驗證錯誤,但驗證器所回報的其實是事實:可為 Null 的 .NET 屬性仍然是 null。瀏覽器與應用程式已在無聲無息中產生分歧。
最近提交的修正讓這個底層教訓格外清晰。原生 HTML 日期輸入與元件轉換器各自做了合理的事,但它們並未共用相同的格式化契約。控制項所顯示的狀態,模型從未真正接受。
這個教訓不只限於單一元件:可見的 UI 狀態並不能證明應用程式狀態已正確綁定。
一個欄位可能有三種表示方式
日期欄位通常會跨越三種不同的表示:
- 瀏覽器呈現給人類觀看的數值。
- 原生輸入元件交換的線上值。
- 儲存在 .NET 模型中的型別化數值。
對於 <input type="date">,瀏覽器可能顯示當地熟悉的格式,但其值契約必須是 ISO 格式的日期,例如 2026-08-01。顯示方式可變更,線上格式則不會。
包裝它的 Blazor 元件可能有不同的預設值。其轉換器可依目前文化特性,使用短日期模式來格式化與剖析 DateTime?。視文化特性而定,該模式可能使用斜線、不同的欄位順序或其他分隔符號。
兩種行為在各自的情境下都合理。但若在沒有明確轉接器的情況下結合,就會產生意外的通訊協定。
失敗會在兩個方向發生
先來看瀏覽器到模型的方向。
使用者選取日期。原生控制項發出 ISO 值。文化特性感知的轉換器卻期待目前短日期格式。轉換失敗,可為 Null 的屬性因此未被更新。瀏覽器仍可顯示它自己擁有的選取值,讓接下來的驗證訊息看起來荒謬。
現在反轉流程。
編輯表單從模型載入既有日期。轉換器產生依文化特性格式化的字串。原生日期輸入只接受其 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 協定的服務測試提供更多信心。
實用的審查清單
在審查型別化表單控制項時,請問:
- 瀏覽器顯示什麼?
- 控制項實際交換什麼值?
- 模型需要什麼型別與可為 Null 的語意?
- 哪個元件在每個方向擁有轉換?
- 測試涵蓋哪些文化特性?
- 轉換失敗是否會留下看似合理的舊 UI?
日期讓這個邊界容易被看見,但相同的模式也會出現在小數分隔符、百分比、貨幣、列舉與時區。
畫面是瀏覽器渲染結果的證據。模型是應用程式接受內容的證據。良好的綁定程式碼與測試,會證明兩者持續保持連結。
在你的 UI 中,哪裡可能出現「看起來有效,但從未到達模型」的數值?
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.