class User {
String login
String password
String email
Integer age
static constraints = {
...
}
}
11 驗證
版本 6.2.0
11 驗證
Grails 的驗證功能建構於 Spring 的驗證器 API 和資料繫結功能上。然而,Grails 更進一步,並透過其約束機制提供統一的方式來定義驗證「約束」。
11.1 宣告約束
在網域類別中,約束 是透過指派程式碼區塊的約束屬性來定義的
然後,您使用與約束套用的屬性名稱相符的方法呼叫,搭配命名參數來指定約束
class User {
...
static constraints = {
login size: 5..15, blank: false, unique: true
password size: 5..15, blank: false
email email: true, blank: false
age min: 18
}
}
在此範例中,我們宣告 login
屬性長度必須介於 5 到 15 個字元之間,不能為空白,且必須是唯一的。我們也對 password
、email
和 age
屬性套用其他約束。
預設情況下,所有網域類別屬性都不可為空 (即具有隱含的 nullable: false 約束)。
|
您可以在快速參考部分的約束標題下找到可用約束的完整參考。
請注意,約束只會評估一次,這可能與依賴於值(例如 java.util.Date
的實例)的約束相關。
class User {
...
static constraints = {
// this Date object is created when the constraints are evaluated, not
// each time an instance of the User class is validated.
birthDate max: new Date()
}
}
警告 - 從約束中參照網域類別屬性
很容易嘗試從靜態約束區塊參照實例變數,但這在 Groovy(或 Java)中是不合法的。如果您這樣做,您將會為您的麻煩取得一個 MissingPropertyException
。例如,您可能會嘗試
class Response {
Survey survey
Answer answer
static constraints = {
survey blank: false
answer blank: false, inList: survey.answers
}
}
看看 inList
約束如何參照實例屬性 survey
?這不會奏效。相反,使用自訂 驗證器
class Response {
...
static constraints = {
survey blank: false
answer blank: false, validator: { val, obj -> val in obj.survey.answers }
}
}
在此範例中,自訂驗證器的 obj
參數是正在驗證的網域實例,因此我們可以存取它的 survey
屬性,並傳回一個布林值來表示 answer
屬性 val
的新值是否有效。
11.2 驗證約束
驗證基礎
呼叫 驗證 方法來驗證網域類別實例
def user = new User(params)
if (user.validate()) {
// do something with user
}
else {
user.errors.allErrors.each {
println it
}
}
網域類別上的 errors
屬性是 Spring Errors 介面的實例。Errors
介面提供方法來導覽驗證錯誤,並擷取原始值。
驗證階段
在 Grails 中有兩個驗證階段,第一個是 資料繫結,它發生在您將要求參數繫結到實例時,例如
def user = new User(params)
在這個時候,您可能已經在 errors
屬性中有錯誤,這是因為類型轉換(例如將字串轉換為日期)。您可以使用 Errors
API 檢查這些錯誤並取得原始輸入值
if (user.hasErrors()) {
if (user.errors.hasFieldErrors("login")) {
println user.errors.getFieldError("login").rejectedValue
}
}
if (user.save()) {
return user
}
else {
user.errors.allErrors.each {
println it
}
}
11.3 在類別之間共用約束
Grails 中的常見模式是使用 命令物件 來驗證使用者提交的資料,然後將命令物件的屬性複製到相關的網域類別。這通常表示您的命令物件和網域類別共用屬性及其約束。您可以手動複製和貼上這兩個約束,但那是一個非常容易出錯的方法。相反地,請使用 Grails 的全域約束和匯入機制。
全域約束
除了在網域類別、指令物件和 其他可驗證類別 中定義約束外,您還可以在 grails-app/conf/runtime.groovy
中定義約束
grails.gorm.default.constraints = {
'*'(nullable: true, size: 1..20)
myShared(nullable: false, blank: false)
}
這些約束不會附加到任何特定類別,但可以從任何可驗證類別輕鬆地參考它們
class User {
...
static constraints = {
login shared: "myShared"
}
}
請注意 shared
參數的使用,其值是 grails.gorm.default.constraints
中定義的其中一個約束的名稱。儘管有設定名稱,您仍可以從任何可驗證類別(例如指令物件)參考這些共用約束。
「*」約束是一個特殊情況:它表示相關約束(在上述範例中為「nullable」和「size」)將套用至所有可驗證類別中的所有屬性。這些預設值可以由可驗證類別中宣告的約束覆寫。
匯入約束
Grails 2 導入了一種共用約束的替代方法,讓您可以從一個類別匯入一組約束到另一個類別。
假設您有一個網域類別如下所示
class User {
String firstName
String lastName
String passwordHash
static constraints = {
firstName blank: false, nullable: false
lastName blank: false, nullable: false
passwordHash blank: false, nullable: false
}
}
然後,您想要建立一個指令物件 UserCommand
,它共用網域類別的部分屬性和對應的約束。您可以使用 importFrom()
方法執行此動作
class UserCommand {
String firstName
String lastName
String password
String confirmPassword
static constraints = {
importFrom User
password blank: false, nullable: false
confirmPassword blank: false, nullable: false
}
}
這將從 User
網域類別匯入所有約束,並將它們套用至 UserCommand
。匯入會忽略來源類別 (User
) 中任何沒有對應屬性的約束(匯入類別 (UserCommand
))。在上述範例中,只有「firstName」和「lastName」約束會匯入至 UserCommand
,因為這兩個類別共用的屬性只有這兩個。
如果您想要進一步控制要匯入哪些約束,請使用 include
和 exclude
參數。這兩個參數都接受一個簡單或正規表示式字串清單,這些字串會與來源約束中的屬性名稱進行比對。因此,例如,如果您只想匯入「lastName」約束,您會使用
...
static constraints = {
importFrom User, include: ["lastName"]
...
}
或者,如果您想要匯入所有以「Name」結尾的約束
...
static constraints = {
importFrom User, include: [/.*Name/]
...
}
當然,exclude
會執行相反的動作,指定不應匯入哪些約束。
11.4 驗證客戶端
顯示錯誤
通常,如果您遇到驗證錯誤,您會重新導向回檢視進行呈現。一旦進入檢視,您需要一些方式來顯示錯誤。Grails 支援一組豐富的標籤來處理錯誤。若要將錯誤呈現為清單,您可以使用 renderErrors
<g:renderErrors bean="${user}" />
<g:hasErrors bean="${user}">
<ul>
<g:eachError var="err" bean="${user}">
<li>${err}</li>
</g:eachError>
</ul>
</g:hasErrors>
重點顯示錯誤
當欄位輸入不正確時,使用紅色方塊或一些指標進行重點顯示通常很有用。這也可以透過 hasErrors 標籤來執行,方法是將它呼叫為方法。例如
<div class='value ${hasErrors(bean:user,field:'login','errors')}'>
<input type="text" name="login" value="${fieldValue(bean:user,field:'login')}"/>
</div>
這段程式碼檢查 user
bean 的 login
欄位是否有任何錯誤,如果有,它會將 errors
CSS 類別新增至 div
,讓您可以使用 CSS 規則來重點顯示 div
。
擷取輸入值
每個錯誤實際上都是 Spring 中 FieldError 類別的實例,它會保留原始輸入值。這很有用,因為您可以使用錯誤物件,使用 fieldValue 標籤還原使用者輸入的值
<input type="text" name="login" value="${fieldValue(bean:user,field:'login')}"/>
這段程式碼會檢查 `User` bean 中現有的 `FieldError`,如果有的話,取得 `login` 欄位的原始輸入值。
11.5 驗證與國際化
關於 Grails 中錯誤的另一個重要事項是,錯誤訊息並未在任何地方硬式編碼。Spring 中的 FieldError 類別會使用 Grails 的 i18n 支援,從訊息叢集中解析訊息。
約束與訊息代碼
代碼本身是由慣例決定的。例如,考慮我們先前看過的約束
package com.mycompany.myapp
class User {
...
static constraints = {
login size: 5..15, blank: false, unique: true
password size: 5..15, blank: false
email email: true, blank: false
age min: 18
}
}
如果違反約束,Grails 會根據慣例尋找訊息代碼
約束 | 錯誤代碼 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
對於 空白
約束,這會是 user.login.blank
,因此您需要在 grails-app/i18n/messages.properties
檔案中輸入以下訊息
user.login.blank=Your login name must be specified!
類別名稱會同時尋找有和沒有套件的情況,套件版本優先。因此,例如,com.mycompany.myapp.User.login.blank
會優先於 user.login.blank
使用。這允許您的網域類別訊息代碼與外掛程式發生衝突的情況。
有關哪些代碼適用於哪些約束的參考,請參閱每個約束的參考指南(例如 空白)。
顯示訊息
renderErrors 標籤會使用 message 標籤自動為您查詢訊息。如果您需要更多控制權來進行呈現,您可以自行處理
<g:hasErrors bean="${user}">
<ul>
<g:eachError var="err" bean="${user}">
<li><g:message error="${err}" /></li>
</g:eachError>
</ul>
</g:hasErrors>
11.6 將驗證套用至其他類別
網域類別 和 命令物件 預設支援驗證。其他類別可以透過在類別中定義靜態 constraints
屬性(如上所述)並告知框架,來使其可驗證。應用程式必須向框架註冊可驗證的類別非常重要。僅定義 constraints
屬性是不夠的。
可驗證特質
定義靜態 constraints
屬性並實作 Validateable 特質的類別將會是可驗證的。請考慮以下範例
package com.mycompany.myapp
import grails.validation.Validateable
class User implements Validateable {
...
static constraints = {
login size: 5..15, blank: false, unique: true
password size: 5..15, blank: false
email email: true, blank: false
age min: 18
}
}
程式存取
存取可驗證物件上的約束略有不同。您可以在另一個內容中透過存取類別的 constraintsMap
靜態屬性,以程式方式存取命令物件的約束。該屬性是 Map<String, ConstrainedProperty>
的實例
在上述範例中,存取 User.constraintsMap.login.blank
會產生 false
,而 User.constraintsMap.login.unique
會產生 true
。