自定义 ValidationAttribute 和 Blazor 验证

· 2 分钟阅读

# 定制一切

正如您可能在我的所有帖子中看到的那样,我真的尽力使所有内容尽可能干净,因为我已经写了有关自定义属性、自定义异常处理、服务集合注入等的帖子。

随着时间的推移,我意识到这种编码方式为我和我的团队提供了一种改进加班、更轻松地发现问题并尽可能分离代码的方法。

是的,在这个很酷的故事之后,我最近像往常一样一直在 Blazor 上工作,我发现,经过多年的开发,您可以创建自定义验证属性。

是的,这很有趣,这么多年过去了……

自定义验证属性

这个想法实际上来自工作,我们总是在所有地方进行验证,但我有一些字段需要相同的验证过程,所以我认为那里可能有一些东西……比如自定义验证属性!

因此,我为此启动了 Microsoft 文档,发现是的,您可以创建自定义验证属性并将它们分配给属性,就像这样:

public class StringLengthRangeAttribute : ValidationAttribute
{
    public int Minimum { get; set; }
    public int Maximum { get; set; }

    public StringLengthRangeAttribute()
    {
        this.Minimum = 0;
        this.Maximum = int.MaxValue;
    }

    public override bool IsValid(object value)
    {
        string strValue = value as string;
        if (!string.IsNullOrEmpty(strValue))
        {
            int len = strValue.Length;
            return len >= this.Minimum && len <= this.Maximum;
        }
        return true;
    }
}

并在一个简单的类中使用它,如下所示:

[Required]
[StringLengthRange(Minimum = 10, ErrorMessage = "Must be >10 characters.")]

[StringLengthRange(Maximum = 20)]

[Required]
[StringLengthRange(Minimum = 10, Maximum = 20)]

自定义验证器

因此,我有一个针对某些特定业务案例需要的验证器,该验证器将计算 20 个第一个字符,这些字符将由 9 个数字和一个连字符组成,并以 2 个通常是国家/地区代码的字符结尾,因此如下所示: 123456789-123456789-ES

我最终得到了这样的东西,它非常简单但有效:

using System;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;

public class SpecialStringValidatorAttribute : ValidationAttribute
{
    private const int TotalLength = 22;
    private const string Pattern = @"^(\d{10})-(\d{10})-([A-Za-z]{2})$";

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        string strValue = value as string;

        if (!string.IsNullOrEmpty(strValue))
        {
            if (strValue.Length != TotalLength)
            {
                return new ValidationResult($"The string must be {TotalLength} characters long.");
            }

            if (!Regex.IsMatch(strValue, Pattern))
            {
                return new ValidationResult("The string must follow the pattern: 1234567890-1234567890-AB");
            }

            return ValidationResult.Success;
        }

        return new ValidationResult("The string cannot be null or empty.");
    }
}

测试

我也为他们写了一些测试,以防万一:

public class SpecialStringValidatorTests
{
    [Theory]
    [InlineData("1234567890-1234567890-AB", true)]
    [InlineData("1234567890-1234567890-XY", true)]
    [InlineData("1234567890-1234567890-A1", false)]
    [InlineData("1234567890-1234567890-A", false)]
    [InlineData("1234567890-123456789-AB", false)]
    [InlineData("1234567890-1234567890", false)]
    [InlineData("1234567890-1234567890-ABCDE", false)]
    public void SpecialStringValidatorTest(string input, bool expectedResult)
    {
        // Arrange
        var validator = new SpecialStringValidatorAttribute();

        // Act
        var result = validator.IsValid(input);

        // Assert
        Assert.Equal(expectedResult, result);
    }
}

当运行时我得到了这个结果:

Microsoft (R) Test Execution Command Line Tool Version 16.9.1
Copyright (c) Microsoft Corporation.  All rights reserved.

Starting test execution, please wait...

A total of 1 test files matched the specified pattern.

Test run in progress...

Passed!  - SpecialStringValidatorTests.SpecialStringValidatorTest(input: "1234567890-1234567890-AB", expectedResult: True)
Passed!  - SpecialStringValidatorTests.SpecialStringValidatorTest(input: "1234567890-1234567890-XY", expectedResult: True)
Passed!  - SpecialStringValidatorTests.SpecialStringValidatorTest(input: "1234567890-1234567890-A1", expectedResult: False)
Passed!  - SpecialStringValidatorTests.SpecialStringValidatorTest(input: "1234567890-1234567890-A", expectedResult: False)
Passed!  - SpecialStringValidatorTests.SpecialStringValidatorTest(input: "1234567890-123456789-AB", expectedResult: False)
Passed!  - SpecialStringValidatorTests.SpecialStringValidatorTest(input: "1234567890-1234567890", expectedResult: False)
Passed!  - SpecialStringValidatorTests.SpecialStringValidatorTest(input: "1234567890-1234567890-ABCDE", expectedResult: False)

Test Run Successful.
Total tests: 7
     Passed: 7
 Total time: 1.7296 Seconds

自定义验证和 Blazor

现在我知道可以使用他的方法,将其实施到 Blazor 中显然是个好主意,对吧?

假设我有这个表单,它将使用我之前展示的模型 Person

@using Models
@page "/"

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

<EditForm Model=@Person FormName="PersonForm">
  <DataAnnotationsValidator/>
  <ValidationSummary/>
  <div class="form-group">
    <label for="Name">Name</label>
    <InputText @bind-Value=Person.Name class="form-control" id="Name" />
    <ValidationMessage For="() => Person.Name"/>
  </div>
  <div class="form-group">
    <label for="MySpecialString">My special string</label>
    <InputText @bind-Value=Person.MySpecialString class="form-control" id="Name" />
    <ValidationMessage For="() => Person.MySpecialString"/>
  </div>
  <div class="form-group">
    <label for="Age">Age</label>
    <InputNumber @bind-Value=Person.Age class="form-control" id="Age" />
    <ValidationMessage For=@(() => Person.Age) />
  </div>
  <input type="submit" class="btn btn-primary" value="Save"/>
</EditForm>

@code {
  Person Person = new Person();
}

一旦我们运行它,我们就会收到以下错误:

如果我们只输入我们想要的内容,我们就会得到以下结果,一切都很清楚:

老实说,对我来说,很明显我们应该至少将验证这些表单的逻辑转移到自定义验证属性中,它使我们可以自由地将登录代码存储在单个位置,并且我们可以稍后将其用于 API 或其他应用程序。

希望您喜欢它,如果您有任何疑问或想联系我,请不要犹豫,与我联系!