[TinyERP: SPA for Enterprise Application] 处理错误/验证





5.00/5 (1投票)
[TinyERP: SPA for Enterprise Application] 处理错误/验证
注意:本文是关于构建企业级应用程序系列文章的一部分。请先阅读上一篇文章,这将有助于您理解我们讨论的上下文。
概述
在本文中,让我们看看如何在 TinyERP 中实现验证流程。
更新 addNewStaffModel.ts
打开 addNewStaffModel.ts 文件,并像下面一样添加 "required
" 装饰器
export class AddNewStaffModel extends BaseModel{
@required("hrm.addNewStaff.firstNameWasRequired")
public firstName:string;
public lastName:string;
}
在此更改中,我们在 firstName
上添加了 "required
"。这意味着 firstName
是一个 required
字段。如果我们创建一个新的员工而 firstName
字段没有值,系统将触发 validationFail
事件,并将 "hrm.addNewStaff.firstNameWasRequired
" 作为错误键。
由于支持多语言,我们使用 "hrm.addNewStaff.firstNameWasRequired
" 路径而不是 "指定文本"。有关多语言的更多信息,请参阅 "多语言"。
何时会触发验证?
在 addNewStaff.ts 的 onSaveClicked
中,我们有这段代码
public onSaveClicked():void{
if(!this.model.validated()){
return;
}
// other logic
}
在第 2 行,系统将调用 validated 方法。如果模型通过所有验证规则,我们可以继续处理其他业务逻辑(在本例中是创建新员工)。否则,系统将触发 "OnValidationFailed
" 事件。
validated 方法如何工作?
再次查看 addNewStaffModel.ts,我们会发现这个类继承自 BaseModel
类
export class AddNewStaffModel extends BaseModel{
// body of AddNewStaffModel class
}
并且 BaseModel
也实现了这个方法所需的逻辑。我的意思是,在 addNewStaffModel
中,我们不需要实现它。只需在你类的属性/字段上添加合适的装饰器。
此错误消息将在哪里显示?
这取决于你。在验证中,我们基于 "hrm.addNewStaff.firstNameWasRequired
" errorKey
。有几种方法可以显示此错误。
如果你想突出显示相应的表单输入,让我们使用下面的验证指令
<horizontal-form>
<form-text-input
[validation]="['hrm.addNewStaff.firstNameWasRequired']"
[labelText]="'First Name'"
[(model)]=model.firstName></form-text-input>
<form-text-input
[labelText]="'Last Name'"
[(model)]=model.lastName></form-text-input>
<form-primary-button [label]="'Save'"
(onClick)="onSaveClicked($event)"></form-primary-button>
<form-default-button [label]="'Cancel'"
(onClick)="onCancelClicked($event)"></form-default-button>
</horizontal-form>
在第 3 行,我们告诉系统在此处显示 "hrm.addNewStaff.firstNameWasRequired
" 验证错误。
让我们创建一个新员工,然后在 firstName
没有值的情况下单击 "保存",验证错误将显示在表单上,如下所示
文本输入已被突出显示,将鼠标悬停在上面,"First name was required field" 显示为工具提示。这提供了有关错误的更多信息。
如果你想更改此 CSS,请使用浏览器上的检查工具自行检查。有一些类需要更改,例如:validation__invalid
等。我们使用 BEM 规则来定义 CSS 类。
对于工具提示消息,请在相应的 JSON 文件(多语言文件)中更新它。在这种情况下,它是位于 "src/resources/locales" 下的 "hrm.en.json" 文件。请参见上图。
如果你想在表单上方显示所有错误消息,请修改 HTML 如下
<page>
<page-header>Add new Staff</page-header>
<page-content>
<error-message
[messages]="['hrm.addNewStaff.firstNameWasRequired' ]"
></error-message>
<horizontal-form>
<form-text-input
[labelText]="'First Name'" [(model)]=model.firstName></form-text-input>
<form-text-input
[labelText]="'Last Name'" [(model)]=model.lastName></form-text-input>
<form-primary-button [label]="'Save'" (onClick)="onSaveClicked($event)">
</form-primary-button>
<form-default-button [label]="'Cancel'" (onClick)="onCancelClicked($event)">
</form-default-button>
</horizontal-form>
</page-content>
</page>
只需移除 firstName
的表单文本输入中的上述验证指令,并在表单组件上方添加 error-message
。
"messages
" 输入属性包含我们想要显示的错误键列表。例如,在这种情况下是:'hrm.addNewStaff.firstNameWasRequired
'。
让我们刷新页面并再次单击 "保存" 按钮,结果如下
我们可以看到 "First name was required" 显示在表单上方。
我们可以为 lastName
添加更多验证规则,以及为 firstName
添加更多规则
export class AddNewStaffModel extends BaseModel{ @valueInRange(1,5,"hrm.addNewStaff.firstNameMax5Letter") @required("hrm.addNewStaff.firstNameWasRequired") public firstName:string; @required("hrm.addNewStaff.lastNameWasRequired") public lastName:string; }
并像这样更新 HTML
<page>
<page-header>Add new Staff</page-header>
<page-content>
<error-message
[messages]="[
'hrm.addNewStaff.firstNameWasRequired',
'hrm.addNewStaff.lastNameWasRequired',
'hrm.addNewStaff.firstNameMax5Letter'
]"
></error-message>
<horizontal-form>
<form-text-input
[labelText]="'First Name'" [(model)]=model.firstName></form-text-input>
<form-text-input
[labelText]="'Last Name'" [(model)]=model.lastName></form-text-input>
<form-primary-button [label]="'Save'" (onClick)="onSaveClicked($event)">
</form-primary-button>
<form-default-button [label]="'Cancel'" (onClick)="onCancelClicked($event)">
</form-default-button>
</horizontal-form>
</page-content>
</page>
只需将更多验证错误添加到 error-message
组件中。
让我们编译并刷新页面。只需将表单留空,然后单击 "保存" 按钮
表单上显示了 3 个错误。让我们尝试在 firstName
文本框中输入一些文本(超过 5 个字母),然后再次单击 "保存"
我们收到上面的 2 条错误消息。
好的,这很好,但错误消息列表可能很长(在本例中是 3 个),有些复杂的表单可能有 20 个错误要显示。因此,我们可以用 "hrm.addNewStaff.
" 替换 3 个错误键,因为所有验证错误都以 "hrm.addNewStaff.
" 开头。所以我们可以使用 "hrm.addNewStaff.
" 作为简写,这意味着 error-message 组件将显示所有引发的验证失败,其错误键以 "hrm.addNewStaff.
" 开头。所以请谨慎使用。error-message 组件已更改为
<error-message
[messages]="['hrm.addNewStaff.']"
></error-message>
让我们刷新页面并再次单击 "保存" 按钮,我们得到相同的结果
好的,简单回顾一下。
- 在
addNewStaffModel
中,我们添加了合适的验证装饰器,例如:在本例中的required
、valueInRange
。 - 在验证中,我们使用错误键来引用 locale 文件中的指定文本。请参阅 多语言。
- 我们可以显示错误消息在指定的
form-element
(使用验证指令)或一起显示所有错误消息(在表单组件上方使用 error-message 指令)。 - 对于多个错误键,我们可以使用简写形式,例如:"
hrm.addNewStaff.
" 表示所有以 "hrm.addNewStaff.
" 开头的错误键。
更新 addNewStaffModel 类
让我们继续,在某些情况下,我们没有可用的装饰器来进行验证,我们可以在 addNewStaffModel
类中添加自定义验证规则,如下所示
protected getValidationErrors(): ValidationException {
let validator:ValidationException = new ValidationException();
let fullName=String.format("{0} {1}", this.firstName, this.lastName);
if(fullName.length>20){
validator.add("hrm.addNewStaff.fullNameTooLong");
}
return validator;
}
在此方法中,只需添加一些现有的验证装饰器不支持的额外验证,或者很难通过验证装饰器实现的验证。例如,在本例中,全名的最大长度。
我们可以根据需要添加更多错误,返回 ValidationException
对象和结果
以及 hrm.en.json 文件的内容如下
{
"addNewStaff":{
"firstNameWasRequired":"First name was required field",
"lastNameWasRequired":"Last name was required",
"firstNameMax5Letter":"First name should be in 1 (min) and 5(max) leters",
"fullNameTooLong":"Full name should not exceed 20 letters."
}
}
我们看到,全名的 max-length
可以更改,我们可以考虑将其扩展到 30 个字母。这要求我们更新 locale 文件。这可能会给开发团队带来潜在的错误。因此,我们应该考虑将数字 "20
" 移到 config 文件中,并将 locale 文件中的字符串替换为 "Full name should not exceed {{MAX_FULLNAME_LENGTH}} letters."。理想情况下,在运行时,我们将为 "MAX_FULLNAME_LENGTH
" 传递值。
更新自定义验证方法
protected getValidationErrors(): ValidationException {
let validator:ValidationException = new ValidationException();
let fullName=String.format("{0} {1}", this.firstName, this.lastName);
if(fullName.length > HrmConst.MAX_FULLNAME_LEGNTH){
validator.add(
"hrm.addNewStaff.fullNameTooLong",[
{key:"MAX_FULLNAME_LENGTH", value: HrmConst.MAX_FULLNAME_LEGNTH}
]);
}
return validator;
}
请注意,我们向 validator.add
方法添加了更多参数。可以有多个参数。有一个新的 const
变量 HrmConst.MAX_FULLNAME_LENGTH
,并将其值设置为 30
。只是想确保错误已重新加载。
同时更新 hrm.en.json 文件中的消息
{
"addNewStaff":{
"firstNameWasRequired":"First name was required field",
"lastNameWasRequired":"Last name was required",
"firstNameMax5Letter":"First name should be in 1 (min) and 5(max) leters",
"fullNameTooLong":"Full name should not exceed {{MAX_FULLNAME_LENGTH}} letters."
}
}
我们在 locale 文本中也使用了该参数。让我们编译并再次运行,我们得到
从现在开始,我们只需要更新 HrmConst.MAX_FULLNAME_LEGNTH const
的新值。
好的,这是客户端的。服务器端呢?服务器端如何将验证失败响应回客户端并在 UI 上显示?
在服务器上,我们必须重新验证 UI 上的所有验证规则。因为 API 可以由多个方调用,或者用户可以使用某些工具并绕过客户端验证。
更新 CreateStaffRequest.cs 类的验证属性
客户端将创建员工请求发送到服务器,相应的信息存储在 CreateStaffClass
中,并将此请求发送到 StaffCommandHandler.cs 类。所以,让我们为 CreateStaffRequest
添加一些验证规则
public class CreateStaffRequest: IBaseCommand
{
[Required("hrm.addNewStaff.firstNameWasRequired")]
public string FirstName { get; set; }
[Required("hrm.addNewStaff.lastNameWasRequired")]
public string LastName { get; set; }
/*other */
}
TinyERP 中定义了一些可用的验证属性。它们的行为与客户端的验证装饰器相同。只需接收错误键作为参数。
我们需要使用与客户端相同的错误键,因为每个键都映射到 locale 文本。因此,对于相同的验证失败使用 2 个不同的文本是不合理的,例如:在本例中是 hrm.addNewStaff.firstNameWasRequired
。
好的,API 可以通过 REST 客户端调用。所以,让我们编译并使用 REST 客户端工具进行检查
在这种情况下,我发送请求创建新员工,但 firstName
为空,并收到带有错误的响应,如图所示。让我们发送同时 firstName
和 lastName
为空的请求
并收到 firstName
和 lastName
的验证失败。
在某些情况下,我们希望执行自定义验证,例如:full-name
不应超过 20 个字母。为此,我们需要在 StaffCommandHandler.cs 中添加自定义验证规则,打开 Validate(CreateStaffRequest command)
方法并添加新的自定义规则
private void Validate(CreateStaffRequest command)
{
IValidationException validator = ValidationHelper.Validate(command);
if (string.Format("{0} {1}", command.FirstName, command.LastName).Length >
HRMConst.MAX_FULLNAME_LEGNTH) {
validator.Add(new ValidationError("hrm.addNewStaff.fullNameTooLong"));
}
validator.ThrowIfError();
}
我们可以看到,在服务器端,我们主要以与客户端相同的方式进行操作。我们也可以为 locale 文本传递参数,如下所示
private void Validate(CreateStaffRequest command)
{
IValidationException validator = ValidationHelper.Validate(command);
if (string.Format("{0} {1}", command.FirstName, command.LastName).Length >
HRMConst.MAX_FULLNAME_LEGNTH) {
validator.Add(new ValidationError(
"hrm.addNewStaff.fullNameTooLong",
"MAX_FULLNAME_LENGTH",
HRMConst.MAX_FULLNAME_LEGNTH.ToString()
));
}
validator.ThrowIfError();
}
让我们编译 API 并重试
这还不够,我们可能需要进行更多验证规则,例如,不允许员工拥有相同的 firstName
和 lastName
(仅假设)。让我们在 Validate
方法中添加更多验证规则
private void Validate(CreateStaffRequest command)
{
IValidationException validator = ValidationHelper.Validate(command);
if (string.Format("{0} {1}", command.FirstName, command.LastName).Length >
HRMConst.MAX_FULLNAME_LEGNTH) {
validator.Add(new ValidationError(
"hrm.addNewStaff.fullNameTooLong",
"MAX_FULLNAME_LENGTH",
HRMConst.MAX_FULLNAME_LEGNTH.ToString()
));
}
IStaffRepository repo = IoC.Container.Resolve<IStaffRepository>();
if (repo.Exists(command.FirstName, command.LastName)) {
validator.Add(new ValidationError(
"hrm.addNewStaff.fullNameWasExisted",
"FIRST_NAME", command.FirstName,
"LAST_NAME", command.LastName
));
}
validator.ThrowIfError();
}
只需检查数据库,看是否有员工拥有相同的名字和姓氏。我建议你使用 const
来表示 "FIRST_NAME
"、"LAST_NAME
" 等。
我非常确定验证仍然会以与 "hrm.addNewStaff.fullNameTooLong
" 验证规则相同的方式返回给客户端。
请也在你的 locale 文件中添加 "hrm.addNewStaff.fullNameWasExisted
"。
让我们集成到客户端。请移除所有客户端验证,这样我们就可以更容易地检查服务器验证。
发送 firstName
和 lastName
都为空的请求
创建 fullName
超过 20 个字母的员工
创建具有现有 first-name
和 last-name
的员工
好的,我们可以看到客户端和服务器的验证可以很好地集成在一起。
摘要
让我们回顾一下本篇文章中学到的内容
- 对于验证,我们可以在客户端和服务器端都进行。
- 在验证中,我们使用错误键而不是指定文本。UI 将根据当前语言解析为指定文本。有关更多信息,请参阅 多语言。
- 在客户端,我们可以直接在模型的属性上添加现有的验证装饰器。例如,本文中的
addNewStaffModel
。 - 或者,我们可以通过覆盖
BaseModel
类中声明的getValidationErrors
函数来添加自定义验证规则。 - 在服务器端,我们可以通过使用 "
TinyERP.Common.Validation.Attribute
"命名空间
中可用的验证属性或添加自定义验证规则来执行相同的操作。
有关此部分的源代码的更多信息,请参阅 https://github.com/tranthanhtu0vn/TinyERP(在 feature/handle_error 分支中)。
系列中的其他文章
感谢阅读!