ValidationAttribute personalizado e validação Blazor
# Personalize tudo
Como você provavelmente já viu em todos os meus posts, eu realmente tento manter tudo o mais limpo possível, pois já escrevi posts sobre atributos customizados, tratamento de exceções customizadas, injeção de coleção de serviços, etc.
Com o tempo, percebi que esse tipo de codificação dá a mim e à minha equipe uma maneira de melhorar as horas extras, encontrar problemas com mais facilidade e separar o código o máximo possível.
Sim, depois dessa história legal que acabei de contar, tenho trabalhado no Blazor ultimamente como de costume e descobri que, depois de anos e anos de desenvolvimento, você pode criar atributos de validação personalizados.
Sim, é engraçado, depois de todos esses anos…
Atributos de validação personalizados
A ideia veio do trabalho, na verdade, sempre fazemos validação em todos os lugares, mas eu tinha alguns campos que exigiam o mesmo processo de validação, então pensei que poderia haver algo lá… como atributos de validação customizados!
Então, abri a documentação da Microsoft para isso e descobri que sim, você pode criar atributos de validação personalizados e atribuí-los às propriedades, assim:
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;
}
}
e use-o em uma classe simples como esta:
[Required]
[StringLengthRange(Minimum = 10, ErrorMessage = "Must be >10 characters.")]
[StringLengthRange(Maximum = 20)]
[Required]
[StringLengthRange(Minimum = 10, Maximum = 20)]
Validador personalizado
Então eu tenho esse validador que preciso para algum caso de negócio específico que contará 20 primeiros caracteres que serão 9 números e um hífen, e terminarão com 2 caracteres que normalmente serão o código do país, então algo assim: 123456789-123456789-ES
Acabei chegando com algo assim, é muito simples, mas funciona:
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.");
}
}
Testes
Eu escrevi alguns testes para eles também, só para garantir:
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);
}
}
E quando executei tive estes resultados:
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
Validação personalizada e Blazor
Então agora que sei que o dele pode ser usado, é claramente uma boa ideia implementá-lo no Blazor, certo?
Vamos supor que eu tenha este formulário, que usará o modelo Person que mostrei antes:
@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();
}
Assim que executamos isso, obtemos os seguintes erros:

E se colocarmos apenas o que queremos, obtemos o seguinte, tudo claro:

Para ser honesto, está bastante claro que devemos mover a lógica pelo menos para validar esses formulários em atributos de validação personalizados, isso nos dá liberdade para armazenar o código desse login em um único local e podemos usá-lo posteriormente para uma API ou outro aplicativo.
Espero que tenham gostado, se tiver alguma dúvida ou quiser entrar em contato, não hesite e entre em contato comigo!