Fala galera, tranquilo?

Hoje gostaria de compartilhar uma de ‘n’ formas que existe para validar uma entidade de domínio.  Em projetos que passei, ou até mesmo pesquisando na internet, é muito comum o uso de Exceptions para tal. Se você não quer entrar em um pattern mais elaborado e talvez gerar excesso de engenharia para uma tarefa relativamente simples, usar exceções na validação é sem dúvidas a forma mais “clean” do ponto de vista de código, afinal se uma regra não for atendida, simplesmente força uma exceção que interrompe o fluxo e ainda pode ser capturado em um nível acima com a instrução throw.

Mas é aí que mora o perigo…

O problema é que essa abordagem pode impactar negativamente dependendo da complexidade do projeto e do número de acessos a sua aplicação. Manipular uma exceção gera um certo custo para o servidor em tempo de execução como memória, log e etc. Prefiro utilizar exceções primitivas somente para quando um determinado código pode gerar um erro inesperado, como por exemplo, na tentativa de conexão com um banco de dados via ADO.NET, onde existe a possibilidade do servidor estar fora e assim precisar de manipulação de erro, evitando trazer informações sigilosas para o usuário. Evite o uso de exceções para eventos previsíveis e controle de fluxo do seu código.

Então uma Exception NUNCA deve ser usada para validar dados?

Calma, não disse isso, sei que exceptions na validação em um contexto pequeno, como um sistema de cadastro e um contingente controlado de acesso, pode significar maior produtividade e menor tempo de desenvolvimento, o difícil é saber até onde esse cenário vai durar e essa “pequena” aplicação se tornar um ERP gigantesco e malvado.

Ambiente
Visual Studio Community 2013

Cenário
Vamos simular uma aplicação ASP.NET MVC do qual tem a “complexa” finalidade de cadastrar um contato, daí precisamos validar esse cara antes de despachar para o banco, beleza? Simples, não?

Bora então…

Abra o Visual Studio > New Project > Other Project Type > Visual Studio Solution, o nome da solução será DomainValidationAspNetMvc, depois dê Ok.

Imagem 01 - Validando Domínio Sem Exception

 Figura 1 – Criando a Solução.

 

Depois de criar a solution, adicione mais 2 projetos C# do tipo Class Library, com os seguintes nomes DomainValidationAspNetMvc.Domain e DomainValidationAspNetMvc.Infra.Common.

Imagem 02 - Validando Domínio Sem Exception

 Figura 2 – Criando o projeto DomainValidationAspNetMvc.Domain.

 

Imagem 03 - Validando Domínio Sem Exception

         Figura 3 – Criando o projeto DomainValidationAspNetMvc.Infra.Common.

 

No projeto DomainValidationAspNetMvc.Infra.Common, clique com o botão direito em References > Add Reference > Assemblies e pesquise pela referência do System.Web, quando encontrar, dê um oi, mentira, clique em Ok.

Imagem 04 - Validando Domínio Sem Exception                Figura 4 – Referenciando o System.Web.

 

Opa, um momento… System.Web em uma camada que não é web? Sim, e daí?!

Esse projeto Infra.Common é nosso canivete suíço, nele temos funções usadas em todos as camadas da aplicação e por ser um cara de Infraestrutura, me sinto muito a vontade de adicionar uma referência da infraestrutura do ASP.NET.

Agora adicione mais um projeto do tipo Web > ASP.NET Web Application com Visual C# e nomeie  como DomainValidationAspNetMvc.Web, escolha o template MVC e deixe o Authentication como “No Authentication” , em seguida  Ok.

Imagem 05 - Validando Domínio Sem Exception                 Figura 5 – Criando o projeto ASP.NET MVC.

 

 

Clique com o botão direito sobre o projeto que acabamos de adicionar e escolha Set as Startup Project.

Tanto no projeto DomainValidationAspNetMvc.Domain como no DomainValidationAspNetMvc.Web adicione a referência do projeto DomainValidationAspNetMvc.Infra.Common.

Por enquanto, nossa Solution deve ficar assim…

Imagem 06 - Validando Domínio Sem Exception

                                           Figura 6 – Solution Completa.

 

Agora vamos de fato começar a programar, em DomainValidationAspNetMvc.Infra.Common, crie uma pasta chamada Validation e dentro da mesma, adicione uma classe com o nome DomainValidation.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DomainValidationAspNetMvc.Infra.Common.Validation
{
     public class DomainValidation
     {
          public DomainValidation(string message)
          {
              Message = message;
          }

          public string Message { get; private set; }
     }
}

Listagem 1 – DomainValidation.cs.

 

Essa classe vai funcionar como um mensageiro, nela teremos nossas inconsistências geradas na entidade de domínio.

Ainda no projeto DomainValidationAspNetMvc.Infra.Common, dentro da pasta Validation, crie uma
nova classe com o nome DomainValidationManagement.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace DomainValidationAspNetMvc.Infra.Common.Validation
{
       public class DomainValidationManagement
       {
             public static bool HasErrors
             {
                 get { return HttpContext.Current.Items["DomainValidation"] != null; }
             }

             public static void Add(DomainValidation newDomainValidation)
             {
                 if (HttpContext.Current.Items["DomainValidation"] == null)
                  HttpContext.Current.Items["DomainValidation"] = new List<DomainValidation>();

                 (HttpContext.Current.Items["DomainValidation"] as List<DomainValidation>).Add(newDomainValidation);
              }

              public static List<DomainValidation> GetAll()
              {
                return (List<DomainValidation>)HttpContext.Current.Items["DomainValidation"];
              }
        }
}

Listagem 2 – DomainValidationManagement.cs.

Veja um ponto interessante, estamos usando o HttpContext.Current.Items, com esse cara, podemos guardar informações durante o HTTP Request/Response, quando terminar a requisição, todas as chaves nele armazenadas, serão destruídas, diferente do HttpContext.Current.Session, que mantém objetos no servidor até algum evento matar a sessão.

Legal, no projeto DomainValidationAspNetMvc.Domain adicione uma pasta chamada Entities e dentro dela, crie uma nova classe com o nome de Contact. 

using DomainValidationAspNetMvc.Infra.Common.Validation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DomainValidationAspNetMvc.Domain.Entities
{
       public class Contact
       {
             public Contact(string name, int age)
             {
                  Name = name;
                  Age = age;

                  Validate();
             }

             public int Id { get; private set; }
             public string Name { get; private set; }
             public int Age { get; private set; }

             private void Validate()
             {
                if (String.IsNullOrEmpty(Name))
                      DomainValidationManagement.Add(
                               new DomainValidation("O nome não pode ser vazio"));

                if (Age < 18)
                      DomainValidationManagement.Add(
                               new DomainValidation("O contato deve ter 18 anos"));
             }
       }
}

Listagem 3 – Contact.cs.

Veja que o método Validate é chamado dentro do construtor, se alguma regra for quebrada, adicionamos um DomainValidation no HttpContext, sendo possível recuperar esses dados até o final da Request.

Agora vamos para a camada de apresentação, no projeto DomainValidationAspNetMvc.MVC, dentro da pasta Models adicione uma nova classe e dê o nome de ContactVM, a mesma deverá ficar como na listagem abaixo.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace DomainValidationAspNetMvc.MVC.Models
{
       public class ContactVM
       {
            public string Nome { get; set; }
            public int Idade { get; set; }
       }
}

Listagem 4 – ContactVM.cs.

Na pasta Controllers, clique com o botão direito > Add > Controller e dê o nome de ContactController. O mesmo deverá ficar como na imagem abaixo.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using DomainValidationAspNetMvc.MVC.Models;
using DomainValidationAspNetMvc.Domain.Entities;
using DomainValidationAspNetMvc.Infra.Common.Validation;

namespace DomainValidationAspNetMvc.MVC.Controllers
{
    public class ContactController : Controller
    {
         public ActionResult Create()
         {
             return View();
         }
    }
}

Listagem 5 – ContactController.cs.

 

Clique com o botão direito sobre a Action Create > Add View e mantenha o nome Create para a View. O código da deve ficar da seguinte forma:

@model DomainValidationAspNetMvc.MVC.Models.ContactVM

@{
 ViewBag.Title = "Novo Contato";
}

<h2>Novo Contato</h2>

@using (Html.BeginForm())
{
    <div class="form-horizontal">

    <hr />
    @Html.ValidationSummary(false, "", new { @class = "text-danger" })
    <div class="form-group">
    @Html.LabelFor(model => model.Nome, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-10">
    @Html.EditorFor(model => model.Nome, new { htmlAttributes = new { @class = "form-control" } })
   </div>
   </div>

   <div class="form-group">
   @Html.LabelFor(model => model.Idade, htmlAttributes: new { @class = "control-label col-md-2" })
   <div class="col-md-10">
    @Html.EditorFor(model => model.Idade, new { htmlAttributes = new { @class = "form-control" } })
   </div>
   </div>

   <div class="form-group">
   <div class="col-md-offset-2 col-md-10">
   <input type="submit" value="Create" class="btn btn-default" />
   </div>
   </div>
   </div>
}

@section Scripts {
 @Scripts.Render("~/bundles/jqueryval")
}

Listagem 6 – Create.cshtml.

Para finalizar o Controller Contact, inclua a Action Create decorada com o atributo HttpPost…

[HttpPost]

public ActionResult Create(ContactVM contact)
{
     //Fazemos a tentativa de criar um novo Contact
     var contactDomain = new Contact(contact.Nome, contact.Idade);

     //Se houve uma quebra de regra, a propriedade HasErrors deverá estar como true
     if (DomainValidationManagement.HasErrors)
     {
           //Percorremos as regras quebradas e adicionamos no ModelState
           foreach (var validation in DomainValidationManagement.GetAll())
              ModelState.AddModelError("", validation.Message);

           return View(contact);
     }

     //Método para cadastrar o Contact

     return Redirect("/Home/Index");

}

Listagem 7 – Action HttpPost Create.

Na View Index, altere o código e deixe como na listagem 8.

@{
    ViewBag.Title = "Home Page";
}

<div class="jumbotron">
 <h1>ASP.NET MVC</h1>
 <p class="lead">Validando uma entidade de domínio sem usar Exception, que beleza!!!.</p>
 <p><a href="/Contact/Create" class="btn btn-primary btn-lg">Novo Contato &raquo;</a></p>
</div>

<div class="row">

</div>

Listagem 8 – Index.cshtml.

 

Agora vamos testar nossa aplicação, como estamos usando mais de um projeto, antes de executar, dê um Build (Ctrl+Shift+B), em seguida, execute o projeto… se tudo estiver certo, deverá exibir a Index do sistema como na Figura 7.

Imagem 07 - Validando Domínio Sem Exception      Figura 7 – Index.

 

Clique em Novo Contato…

Na tela de cadastro de Contato, deixe o campo Nome vazio e no campo Idade, preencha 10 como na Figura 8.

Imagem 08 - Validando Domínio Sem Exception

 

                                      Figura 8 – Tela de Cadastro.

Segundo nossas regras de domínio, um contato não pode ser criado com o nome nulo e ter pelo menos 18 anos, correto?

Clique em Create e veja a validação de domínio ser apresentada na tela…

Imagem 09 - Validando Domínio Sem Exception

 

                                          Figura 9 – Validação de Domínio.

 

Concluindo… 

Bom, fica mais uma alternativa para validar uma entidade de domínio, de forma limpa, com baixa complexidade e livre do uso inadequado de Exceptions, porém, quero ressaltar que a intenção desse artigo não é apontar como a melhor maneira e sim apresentar uma ideia que vem dando certo nos projetos que participo.

 

Dúvidas, críticas, feedbacks, convites para churrascos, partidas de FIFA15 no PS4 e afins, [email protected]

Projeto no GitHub

Valeu, até a próxima…

 

 

Links:

https://msdn.microsoft.com/en-us/library/vstudio/system.exception%28v=vs.100%29.aspx

http://odetocode.com/articles/111.aspx

Diego Neves

Desenvolvedor de software, graduado em Análise e Desenvolvimento de Sistemas pela Universidade Nove de Julho, atualmente trabalha na FCamara e tem foco no mundo .NET, possui algumas certificações como MCSD Web Applications, MCSD App Builder, MCSA Web Applications, Microsoft Specialist, MCP e ITIL V3 Foundation.

Facebook LinkedIn 

Comentários

comentarios