65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2018年12月21日

Apache

8分钟阅读

viewsIcon

5603

[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.tsonSaveClicked 中,我们有这段代码

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 中,我们添加了合适的验证装饰器,例如:在本例中的 requiredvalueInRange
  • 在验证中,我们使用错误键来引用 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 为空,并收到带有错误的响应,如图所示。让我们发送同时 firstNamelastName 为空的请求

并收到 firstNamelastName 的验证失败。

在某些情况下,我们希望执行自定义验证,例如: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 并重试

这还不够,我们可能需要进行更多验证规则,例如,不允许员工拥有相同的 firstNamelastName(仅假设)。让我们在 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"。

让我们集成到客户端。请移除所有客户端验证,这样我们就可以更容易地检查服务器验证。

发送 firstNamelastName 都为空的请求

创建 fullName 超过 20 个字母的员工

创建具有现有 first-namelast-name 的员工

好的,我们可以看到客户端和服务器的验证可以很好地集成在一起。

摘要

让我们回顾一下本篇文章中学到的内容

  • 对于验证,我们可以在客户端和服务器端都进行。
  • 在验证中,我们使用错误键而不是指定文本。UI 将根据当前语言解析为指定文本。有关更多信息,请参阅 多语言
  • 在客户端,我们可以直接在模型的属性上添加现有的验证装饰器。例如,本文中的 addNewStaffModel
  • 或者,我们可以通过覆盖 BaseModel 类中声明的 getValidationErrors 函数来添加自定义验证规则。
  • 在服务器端,我们可以通过使用 "TinyERP.Common.Validation.Attribute" 命名空间中可用的验证属性或添加自定义验证规则来执行相同的操作。

有关此部分的源代码的更多信息,请参阅 https://github.com/tranthanhtu0vn/TinyERP(在 feature/handle_error 分支中)。

系列中的其他文章

感谢阅读!

© . All rights reserved.