(Este aqui é o terceiro post sobre as novidades do Preview 5 (P5) do ASP.Net MVC. Mais informações aqui e aqui.)

Calma, meus amigos… como tudo no ASP.Net Mvc, a idéia é a mesma, mas a implementação é mais “unplugged”, mais hardcore! Eu sei que todos estão esperando os validadores, já estamos no Preview 5 (!), então vamos lá.

O conceito aqui é de validação, mas não teremos controles validadores, em que você indica que quer validação de datas e ele faz, ou que um campo é obrigatório e o javascript já é gerado para você. A validação, pelo menos até agora, acontece toda no servidor, e o cliente só exibe os erros de validação e as mensagens. Mas acompanhem comigo a implementação. Vou comentado ao longo do post.

O conceito é o seguinte: você tem um campo “x” de algum objeto de negócio. O servidor valida este campo contra alguma regra física (tipo, nulabilidade, etc) e/ou de negócio (deve ser maior o campo “y”, só pode ser nulo se o campo “w” também for, etc). Se houver um problema o cliente recebe a página de volta, corretamente preenchida, e com indicação dos campos incorretos, com mensagens, inclusive. Essa parte ficou parecida ao Webforms, há o conceito do validador que gera o * ao lado do campo com problema, e do summary de validação, mas eles são diferentes. Vamos aos screenshots:

Entrada de dados:

Input simples

Campo com erro após o submit. Notem que os valores voltam conforme foram digitados, mesmo os incorretos. Neste caso, que é um cadastro de categorias, é obrigatório a categoria tenha descrição e nome:

Primeiro erro de validação sem summary

Agora utilizando a exibição com summary (notem os asteriscos também):

Segundo erro de validação com summary

Tudo isso é meio automático. Meio, não muito. Vejam o código do formulário de edição:

    1 <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
    2 AutoEventWireup="true" CodeBehind="Edit.aspx.cs" 
    3 Inherits="MvcApplication1Preview5.Views.Categories.Edit" %>
    4 <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
    5 <% using (Html.Form())
    6    { %>
    7    <% =Html.ValidationSummary() %>
    8    <table>
    9     <tr>
   10         <td>Category Name:</td>
   11         <td>
   12             <% =Html.TextBox("CategoryName") %>
   13             <%--<% =Html.ValidationMessage("CategoryName") %>--%>
   14             <% =Html.ValidationMessage("CategoryName","*") %>
   15         </td>
   16     </tr>
   17     <tr>
   18         <td>Description:</td>
   19         <td>
   20             <% =Html.TextBox("Description") %>
   21             <%--<% =Html.ValidationMessage("Description")%>--%>
   22             <% =Html.ValidationMessage("Description", "*")%>
   23         </td>
   24     </tr>
   25    </table>
   26 <% =Html.SubmitButton("Enviar","Enviar") %>
   27 <% } %>
   28 </asp:Content>

(Notem o novo método “Html.Form”, com overload sem parâmetros. Ele vai postar de volta para o mesmo controlador e mesma ação, algo desejável depois da possibilidade de postar para ações com o mesmo nome.)

É bastante simples. Esse é o código com o summary incluido. Continuando no mesmo caminho, a utilização dos validadores é feita com métodos de auxílio (helper methods), a partir da classe HtmlHelper. São dois os métodos: ValidationMessage, para as mensagens, e ValidationSummary, para o summary. O método ValidationMessage tem um overload em você passa o nome do campo com problema (está comentado no código acima), e tem outro que você passa o nome do campo e o texto a ser exibido. Se você passar este segundo parâmetro, como estou fazendo quando passo só um asterisco “*”, a mensagem de erro não é exibida, só o que você digitou neste segundo campo. E se você tiver uma chamada ao método ValidationSummary, é lá que os erros que você ocultou são exibidos. Não é obrigatório ter o summary e o message juntos, você pode ter só o ValidationMessage, ou só o ValidationSummary, mas os dois juntos ficam melhores.

Nesse momento você, assim como eu também pensei quando vi, deve estar pensando: e de onde vem esta informação de erros de validação? Não há no MVC nenhum lugar em que se armazena isso. Não havia. Agora há. Chama-se ModelState, e é uma classe nova no MVC. Lá você coloca erros o valor que foi tentado. Há também um dicionário (chamado ModelStateDictionary), onde você indica o campo como chave em string, e o ModelState deste campo como valor. Fica então fácil fazer coisas desse tipo:

modelState.AddModelError("CategoryName", cat.CategoryName, 
"O valor de CategoryName é obrigatório.");

Essa classe é acessível a partir do ViewData. Há uma propriedade chamada ModelState (que na verdade é um ModelStateDictionary), que você pode manipular. E se está na View o Controller enxerga e pode manipular. E é a partir dele mesmo que rola toda a coordenação. Afinal, é trabalho do controlador dizer o que a view deve exibir.

Existe um motivo para a complexidade adicional que é colocar os erros de validação no modelo: É PORQUE É LÁ QUE ELES DEVEM FICAR. Eu sei que quando usamos webforms quem controla a exibição dos erros de negócio são os validadores diretamente, ou seja, a interface final com o usuário (ou UI, user interface). Pois é, isso traz altíssimo acomplamento, dependência da camada de negócio da camada de interface gráfica (o correto é o contrário), e uma separação de responsabilidades pobre. Funciona? Funciona. Mas, se você não validar no modelo de novo, pode ter problemas. E se validar, está indo contra o famoso princípio DRY (Don’t Repeat Yourself, ou, não se repita). E a manutenção passa a ficar um inferno, porque as regras de negócio estão espalhadas por toda a aplicação. É por isso que sempre digo que essa nova maneira de trabalhar enfatiza as boas práticas. ASP.Net MVC é o que há!

Discursos a parte, o que eu fiz para controlar os erros foi simples, foi mais para simplificar o exemplo. O ScottGu tem um exemplo um pouco diferente, mais complexo, com uso de interfaces, sugiro dar uma olhada depois. Continuo utilizando o Entity Framework - EF (funciona bem, é rápido de montar, e está à mão). Ele gerou para mim uma classe Category, e eu criei uma parcial da mesma. Adicionei métodos parciais de validação, que já vêm criados quando a classe é gerada pelo EF, no caso os métodos OnCategoryNameChanging, e OnDescriptionChanging. Os dois acontecem antes da classe de categoria ser atualizada. Minha regra é simples: descrição e nomes são campos obrigatórios. Se vierem em branco ou nulos é um erro, e eu lanço uma exceção. Assim:

    1 [ModelBinder(typeof(Binders.CategoryBinder))]
    2 public partial class Category
    3 {
    4     partial void OnCategoryNameChanging(string value)
    5     {
    6         if (string.IsNullOrEmpty(value))
    7         {
    8             LancaErro("O valor de CategoryName é obrigatório.");
    9         }
   10     }
   11     partial void OnDescriptionChanging(string value)
   12     {
   13         if (string.IsNullOrEmpty(value))
   14         {
   15             LancaErro("O valor de Description é obrigatório.");
   16         }
   17     }
   18 
   19     private void LancaErro(string textoErro)
   20     {
   21         Erros.Add(textoErro);
   22         throw new ApplicationException(textoErro);
   23     }
   24 
   25     public IList<string> Erros = new List<string>();
   26 
   27 }

Eu não morri de amores com esse negócio de lançar exceção à toa, mas é o modelo recomendado. Isso tudo porque foi criado um novo método no ASP.Net MVC chamado TryUpdateModel, em que você passa a classe, ele já pega os erros, e cadastra no ModelStateDictionary, tudo sozinho, deixando a implemantação mais leve. Se der tudo certo ele retorna true. Se der algum erro ele retorna falso.(Há também o método UpdateModel, que retorna void, mas se não conseguir atualizar o modelo joga uma exceção.)

    1 [ActionName("Edit")]
    2 [AcceptVerbs("POST")]
    3 public ActionResult SaveEdit(int categoryID)
    4 {
    5     bool atualizou;
    6     Models.Category catFromDB;
    7     using (var db = new Models.NorthwindEntities())
    8     {
    9         catFromDB = (from cats in db.Categories
   10                         where cats.CategoryID == categoryID
   11                         select cats).First();
   12         atualizou = TryUpdateModel(catFromDB, 
               new[] { "CategoryName", "Description" });
   13         if (atualizou)
   14             db.SaveChanges();
   15     }
   16 
   17     if (atualizou)
   18         return RedirectToAction("Edit", new { CategoryID = categoryID });
   19     else
   20         return View(catFromDB);
   21 }

Viram a chamada do método na linha 12? Se não der erro eu atualizo o banco e volto para a view de edição, passando a própria categoria que recebi (que não foi alterada). Se der certo, eu redireciono para a edição de novo, para montar a tela de edição à partir de um GET, não de um POST.

Se estamos passando o objeto de categorias não modificado, como pode ser que o campo exibido para o usuário contém o valor digitado anteriormente, e não o valor do banco de dados? Isso é culpa do ModelState, que carrega a tentativa, lembram? Agora, todos os métodos do HtmlHelper estão passando a checar o ModelState. Se tiver um valor lá para um campo determinado ele é utilizado. Vejam o código retirado do método InputHelper, utilizado pelos outros métodos, como o Textbox(), para compor o html:

tagBuilder.MergeAttribute("value", attemptedValue ?? 
           ((useViewData) ? EvalString(name) : value));

Ou seja, se tem valor de tentativa, utilize.

E o fundo e bordas vermelhos? Mesma coisa, ModelState. Vejam outro trecho de código do mesmo método do HtmlHelper:

if (ViewData.ModelState.TryGetValue(name, out modelState)) {
    if (modelState.Errors.Count > 0) {
        tagBuilder.AddCssClass(ValidationInputCssClassName);
    }
}

Ou seja, se tiver um erro que seja, utilize uma classe de CSS. É isso que deixa o fundo e a borda vermelhos. Simples, não? A partir da inclusão do código do controlador, qualquer adição será trabalhada na View e na camada de negócios.

Nesse ponto fica devendo ainda algo que utilize Javascript, para facilitar a vida do usuário, sem postback. Aumenta a segurança, melhora a usabilidade, e diminui o uso da banda. Sinceramente, não sei se vamos ter esse presente, pode ser que não tenha. O foco em deixar tudo na camada de modelo, e ficar bem DRY pode impedí-los de caminhar nessa direção. Vamos ver.

Gostaria de ouvir a opinião de vocês. Gostaram do que viram? Dá muito trabalho? O ScottGu, no post que comentei, relembra que o webforms não vai morrer, e o MVC e o webforms vão continuar evoluindo em paralelo, e lembra: se você não quiser, não precisa usar o MVC. O que você acha? Vale a pena? Dá para abandonar o webforms?

O projeto que usei está disponível aqui.


Postado na(s) categoria(s) ASP.Net MVC pelo Giovanni Bassi em 3 de outubro de 2008 às 13:15 | Tags:

Comentários


Brazil Adriano
outubro 7. 2008 17:54
Adriano
Interessante o validator ter como fonte de validação objetos de regra de negócio, diferente dos validators do webform que baseam-se no que está no aspx, mas ainda acho que os validators no webforms são úteis quando utilizados para regrinhas de validações simples como validação de email, validação de números, cpf e cnpj (custom validator), campos nulos, etc. Mas nunca deixando de fazer a validação na camada de negócios, é claro. Na verdade os validators não vieram para fazer parte da regra de negócios e sim para evitar um request e um post desnecessário por causa de um campo não preenchido por exemplo.

no site


Brazil Bruno Caimar
outubro 8. 2008 00:31
Bruno Caimar
Acredito que ASP.NET MVC vai ser uma ótima opção para quem está buscando desenvolver aplicações com um design mais OO e aplicações mais 'testáveis'.
O Castle Monorail tem um algo interessante de validação baseada em atributos com uma integração para geração de código javascript para validação no client.

no site


outubro 8. 2008 10:57
Giovanni Bassi
Adriano, pois é, isso poderia ser útil, desde que a validação também fosse feita na camada de negócios. Acaba duplicando, mas concordo que ajuda, sim, claro.
Bruno, provavelmente alguma coisa parecida vai ser implantada no ASP.Net MVC. Quem conhece o monorail sabe que as semelhanças são bem grandes.

http://unplugged.giggio.net/http://unplugged.giggio.net/


Brazil Cristian Mathias
novembro 13. 2008 14:11
Cristian Mathias
Giovanni, como posso fazer para permitir apenas números em um text utilizando o Asp.Net MVC? Não posso utilizar javascript?

Como poderia fazer no exemplo abaixo por exemplo?

<div>
    <label for="Nome">Nome:</label>
    <%= Html.TextBox("Nome", ViewData.Model != null ? ViewData.Model.Nome : "")%>
    <%= Html.ValidationMessage("Nome","*") %>
</div>

no site


novembro 14. 2008 11:10
Giovanni Bassi
Cristian, você pode usar javascript a vontade, mas ainda não há suporte para geração de código javascript automaticamente para os validadores como acontece com webforms.

http://unplugged.giggio.net/http://unplugged.giggio.net/

Comentar


(Vai mostrar seu Gravatar)

  Country flag

biuquote
  • Comentário
  • Pré-visualização
Loading



Quem é Giovanni Bassi

Giovanni Bassi Sou uma pessoa apaixonada por tecnologia e especificamente por .Net. Sou consultor independente especialista em .Net, focado em arquitetura e melhores práticas. Tenho dezenas de artigos publicados na .Net Magazine, revista da qual sou editor técnico. Ministro palestras e cursos de vez em quando, e quando dá tempo eu respiro um pouco. Mais detalhes nesta página.

Busca

Selos

MVP

MCPD

MCSD

.Net Magazine

Abaixo ao if!

Calendário

«  março 2010  »
seteququsedo
22232425262728
1234567
891011121314
15161718192021
22232425262728
2930311234
Ver detalhamento de posts no calendário

Blogs interessantes

    OPMLDownload OPML file

    Postagens recentes

    Comentários recentes

    Disclaimer / Aviso
    As opiniões colocadas neste blog são minhas e pessoais e não expressam necessariamente as opiniões de meus empregadores, pareceiros e amigos. Da mesma forma, os comentários feitos por leitores do blog não expressam a minha opinião.

    © Copyright 2010 .Net Unplugged
    Log in