Validando dados com Fluent Validation

NetCoders
netcoders
Published in
5 min readJan 28, 2017

Fala galera, beleza?
Vou apresentar uma maneira bem sucinta de validar os dados de modo server-side no ASP.NET Core usando o Fluent Validation.

Para quem não conhece o Fluent Validation, trata se de uma excelente biblioteca disponível no .NET Framework para realizar validação de classes usadas como View Models, Commands (conceito do CQRS), DTOs ou até mesmo entidades de domínio. A validação ocorre usando o que o próprio framework define como Fluent Interface que nada mais é que construir as regras de validação com métodos encadeados e expressões lambdas, modo que todo desenvolvedor .NET já é acostumado a trabalhar.

No caso do ASP.NET, o uso do Fluent Validation acontece em alternativa (ou até mesmo em conjunto) as Data Annotations, ao invés de ter classes decorando as propriedades das View Models, temos uma classe separada com as regras de validação de uma determinada View Model.

No exemplo a seguir, vou mostrar como realizar a validação dos dados de um formulário HTML que são recebidos em uma requisição HTTP serializados numa View Model, pattern bem comum no ASP.NET MVC. As View Models são classes que normalmente tem apenas propriedades e auxiliam na lógica das views.

Para este exemplo, vou usar o Visual Studio Community 2015 com Update 3, lembre se de verificar se possui o ASP.NET Core instalado em sua máquina. Vamos começar, no Visual Studio, escolha File>New>Project (ou clique Ctrl+Shift+N)>Web e escolha o template ASP.NET Core Web Application (.NET Core).

Primeiro, no arquivo project.json adicione a referência do Fluent Validation.

[code language=”javascript”]
“FluentValidation.AspNetCore”: “6.4.0-beta8”
[/code]

Importante!!!
Para que não haja problema de compatibilidade, veja as versões que estou trabalhando nas dependências do projeto.

[code language=”javascript”]
{
“dependencies”: {
“Microsoft.NETCore.App”: {
“version”: “1.0.1”,
“type”: “platform”
},
“Microsoft.AspNetCore.Diagnostics”: “1.0.0”,
“Microsoft.AspNetCore.Mvc”: “1.0.1”,
“Microsoft.AspNetCore.Razor.Tools”: {
“version”: “1.0.0-preview2-final”,
“type”: “build”
},
“Microsoft.AspNetCore.Routing”: “1.0.1”,
“Microsoft.AspNetCore.Server.IISIntegration”: “1.0.0”,

“FluentValidation.AspNetCore”: “6.4.0-beta8”
}

[/code]

Continuando…

Na classe Startup, vamos adicionar o Fluent Validation ao pipeline no método ConfigureServices:

[code language=”csharp”]
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc()
.AddFluentValidation(x => x.RegisterValidatorsFromAssemblyContaining<Startup>());
}
[/code]

Na pasta Controllers crie uma nova classe chamada ClienteController, a mesma deve ficar como na imagem abaixo:

[code language=”csharp”]
public class ClienteController : Controller
{

}
[/code]

Crie duas pastas na raiz do projeto chamada ViewModels e outra chamada ViewModelValidators. Dentro da pasta ViewModels crie uma classe chamada ClienteVM, a mesma deverá ficar como abaixo:

[code language=”csharp”]
public class ClienteVM
{
public string Nome { get; set; }
public string Email { get; set; }
public DateTime DataNascimento { get; set; }
}
[/code]

Dentro da pasta ViewModelValidators crie uma classe chamada ClienteVMValidator herdando a classe AbstractValidator

[code language=”csharp”]
public class ClienteVMValidator : AbstractValidator<ClienteVM>
{
public ClienteVMValidator()
{
//Aqui adicionamos as validações de entrada
RuleFor(x => x.Nome)
.NotEmpty().WithMessage(“O campo nome deve ser informado”)
.Length(3, 150).WithMessage(“O campo nome deve ter entre 3 e 150 caracteres”);

RuleFor(c => c.Email)
.NotEmpty().WithMessage(“O campo e-mail deve ser informado”)
.EmailAddress().WithMessage(“E-mail inválido”);

RuleFor(c => c.DataNascimento)
.NotEmpty().WithMessage(“O campo data de nascimento deve ser informado”)
.Must(ClienteMaiorDeIdade).WithMessage(“O cliente deve ter no mínimo 18 anos”);

}
//Aqui criamos uma validação customizada
private static bool ClienteMaiorDeIdade(DateTime dataNascimento)
{
return dataNascimento <= DateTime.Now.AddYears(-18);
}
}
[/code]

Veja que quando precisamos criar uma validação customizada o framework nos dá total liberdade para tal,
veja no caso da validação da idade do cliente.

Voltando a Controller, vamos criar duas Actions para criar um novo cliente, uma GET e outra POST.

[code language=”csharp”]

public IActionResult Create()
{
return View();
}

[HttpPost]
public IActionResult Create(ClienteVM model)
{
if (ModelState.IsValid)
{
//Faz alguma coisa…
return RedirectToAction(“Index”, “Home”);
}
return View(model);
}
[/code]

Dentro da pasta Views, edite o arquivo _ViewImports.cshtml, isso facilita na hora de trabalhar com as classes View Models no arquivo cshtml sem a precisão de escrever toda a namespace, editando esse arquivo escreveremos uma única vez sua referência.

[code language=”csharp”]
@using NomeDoMeuProjeto
@using NomeDoMeuProjeto.ViewModels
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
[/code]

Ainda na pasta Views crie uma pasta chamada Cliente seguindo a convenção do ASP.NET e dentro uma View chamada Create.cshtml.
A mesma deverá ficar como abaixo:

[code language=”csharp”]
@model ClienteVM

@{
ViewData[“Title”] = “Registrar Cliente”;
}

<h2>@ViewData[“Title”]</h2>

<form asp-action=”Create” method=”post”>
<div class=”form-horizontal”>
<hr />

<div class=”form-group”>
<label asp-for=”Nome” class=”col-md-2 control-label”></label>
<div class=”col-md-10">
<input asp-for=”Nome” class=”form-control” />
<span asp-validation-for=”Nome” class=”text-danger”></span>
</div>
</div>

<div class=”form-group”>
<label asp-for=”Email” class=”col-md-2 control-label”></label>
<div class=”col-md-10">
<input asp-for=”Email” class=”form-control” />
<span asp-validation-for=”Email” class=”text-danger”></span>
</div>
</div>

<div class=”form-group”>
<label asp-for=”DataNascimento” class=”col-md-2 control-label”></label>
<div class=”col-md-10">
<input asp-for=”DataNascimento” class=”form-control” />
<span asp-validation-for=”DataNascimento” class=”text-danger”></span>
</div>
</div>

<div class=”form-group”>
<div class=”col-md-offset-2 col-md-10">
<input type=”submit” value=”Cadastrar” class=”btn btn-success” />
</div>
</div>
</div>
</form>

[/code]

Coloque um breakpoint na action Create do tipo POST e execute o projeto, em seguida, vá até a rota
Cliente/Create e clique em Cadastrar sem preencher nenhum campo, veja abaixo que o estado do
ModelState fica inválido.

CreateIsValid

Quando a model inválida é devolvida a View lá estão nossas regras sendo executadas e refletidas no ModelState.

CreateView

Vou fazer diferente, vou digitar menos que 3 caracteres para o campo nome junto de um e-mail inválido e cadastrar um cliente menor de idade, veja abaixo que todas as regras de validação construídas no Fluent Validation são refletidas no ModelState e passadas a View.

CreateViewInvalida2

Caso prefira que o ModelState não seja afetado pelas regras do Fluent Validation automaticamente e precise que manualmente adicionar as regras, veja como ficaria:

Primeiros mudamos a forma que adicionar o Fluent Validation ao pipeline…

[code language=”csharp”]

public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc()
//.AddFluentValidation(x => x.RegisterValidatorsFromAssemblyContaining<Startup>());
.AddFluentValidation();
}

[/code]

Depois na Action vamos capturar os erros de validação manualmente e adicionar ao ModelState também manualmente:

[code language=”csharp”]

[HttpPost]
public IActionResult Create(ClienteVM model)
{

//aqui fazemos a validação da classe ClienteVMValidator passando a
//ViewModel ao método Validade
var validationResult = new ClienteVMValidator().Validate(model);

//por fim adicionamos manualmente os erros ao ModelState
foreach (var error in validationResult.Errors)
ModelState.AddModelError(error.PropertyName, error.ErrorMessage);

}

[/code]

Bacana, não?

Bom, por hoje é só…abraço!!!

Referências:

--

--