Customizando Templates no Asp.Net MVC2

Customizar os templates padrão do Asp.Net MVC2 é muito fácil. Já mostrei antes como usar os templates, agora vou mostrar como customizar.

A primeira coisa que é preciso saber para customizar é que quando o MVC2 busca por customizações ele busca nas seguintes pastas:

  • ~/Areas/NomeDaArea/Views/NomeDoController/DisplayTemplates/TemplateName.aspx & .ascx
  • ~/Areas/NomeDaArea/Views/Shared/DisplayTemplates/TemplateName.aspx & .ascx
  • ~/Views/NomeDoController/DisplayTemplates/TemplateName.aspx & .ascx
  • ~/Views/Shared/DisplayTemplates/TemplateName.aspx & .ascx
  • ~/Areas/NomeDaArea/Views/NomeDoController/EditTemplates/TemplateName.aspx & .ascx
  • ~/Areas/NomeDaArea/Views/Shared/EditTemplates/TemplateName.aspx & .ascx
  • ~/Views/NomeDoController/EditTemplates/TemplateName.aspx & .ascx
  • ~/Views/Shared/EditTemplates/TemplateName.aspx & .ascx

Veja que são principalmente duas pastas DisplayTemplates para os templates de exibição e EditTemplates para os de edição dispostas em alguns locais específicos.

Só para facilitar vou continuar o exemplo feito no post anterior que tinha as views Index.aspx e Edit.aspx e classe de model Cliente.cs como mostrados abaixo:

Index

Edit

public class Cliente
{
    public string Nome { get; set; }
    public string Email { get; set; }
    public bool Ativo { get; set; }
    [DisplayName("Data de Nascimento")]
    public DateTime DataNascimento { get; set; }
    public decimal Credito { get; set; }

    public static Cliente Criar()
    {
        return new Cliente {
             Nome = "Thiago Temple",
             Ativo = true,
             Credito = 1000,
             DataNascimento = new DateTime(1981, 10, 15)
        };
    }
}

Existem alguns templates pré-definidos no MVC2, são eles: String, Html, Boolean, Decimal, EmailAddress, HiddenInput, Html, Object, String, Text, Url e DateTime. Então para se customizar um desses campos tudo o que temos que fazer é criar um arquivo com o nome do template na pasta específica. Vamos imaginar que a idéia fosse que toda que vez que tivéssemos uma propriedade do tipo DateTime e ela fosse editável nós gostaríamos que o textbox fosse exibido com uma classe css específica e ao lado do campo aparecesse uma imagem para que o usuário pudesse clicar e selecionar a data de um calendário em javascript, por exemplo.

Temos então que criar um arquivo DateTime.ascx na pasta ~/Views/Shared/EditTemplates/ assim esse template estará disponível para todas as propriedades DateTime do nosso projeto. Veja como ficaria esse arquivo:

DataTime Edit

Isso vale para todos os tipos de templates pré-definidos. Seguindo o exemplo do post passado, se quisermos modificar a exibição da nossa classe cliente para que ela fique dentro de uma tabela, veja como ficará o template do arquivo ~/Views/Shared/DisplayTemplates/Object.ascx

Display Object

Dentro do objeto ViewData agora temos um objeto ModelMetadata que contém informações sobre a classe do Model que está sendo usada na view. Entre outras informações o que temos ali é uma coleção com todas as propriedades do model.

No exemplo acima primeiro exibimos o nome do model e depois fazemos um loop entre as propriedades do model exibindo as que estão marcadas com a opção ShowForDisplay = true, que é o padrão. Isso deve gerar uma página assim:

Cliente Display

Para o template de edit não é muito diferente, veja o arquivo ~/Views/Shared/EditTemplates/Object.ascx:

Object Edit

Da mesma forma fazemos um loop nas propriedades do objeto ModelMetada que estão marcados com a propriedade ShowForEdit = true, novamente, esse é o padrão. Além disso, verifiquei se a propriedade é obrigatória e coloquei um * na frente do label. Veja o resultado:

Client Edit

Veja também que a modificação feita para o campo DateTime continuou valendo para a propriedade Data de Nascimento que foi exibida com a imagem do calendário ao lado do campo.

Conclusão

Os templates do MVC2 são uma forma simples e prática de exibir dados. Mas, o mais importante, são também muito poderosos para que sejam customizados da melhor forma possível para as necessidades específicas de cada projeto.

Templates no Asp.Net MVC2

Uma das grandes novidades do Asp.Net MVC2 são os templates. Quem já fez algum projeto usando o Dynamic Data do WebForms vai ver que essa funcionalidade é bem parecida com a do Dynamic Data.

O framework agora é capaz de exibir um dado em um formato específico de acordo com o tipo de dados (integer, decimal, boolean, ou mesmo uma classe).

Como isso funciona? A forma mais simples é utililando o método Html.DisplayForModel. Vejamos, dado a classe de modelo:

public class Cliente
{
    public string Nome { get; set; }
    public string Email { get; set; }
    public bool Ativo { get; set; }
    public DateTime DataNascimento { get; set; }
    public decimal Credito { get; set; }

    public static Cliente Criar()
    {
        return new Cliente {
            Nome = "Thiago Temple",
            Email = "eu@vintem.com.br",
            Ativo = true,
            Credito = 1000,
            DataNascimento = new DateTime(1981, 10, 15)
        };
    }
}

Uma view dessa forma:

E um controller:

public class ClienteController : Controller
{
    public ViewResult Index()
    {
        return View(Cliente.Criar());
    }

    public ViewResult Edit()
    {
        return View(Cliente.Criar());
    }

    [HttpPost]
    public ViewResult Edit(Cliente cliente)
    {
        if (ModelState.IsValid)
            return View("Index", cliente);
        return View(cliente);
    }
}

Então ao chamarmos a Action Index do nosso controller veremos:

Veja que os nomes das propriedades aparecem nos labels e os campos já estão formatados como o booleano, data e decimal.

Para a action Edit, temos um método similar Html.EditForModel

Que vai exibir um visualização assim:

Simples não? Mas e se eu quiser exibir “Data de Nascimento” ao invés de DataNascimento no label? Basta decorar a propriedade com o atributo DisplayName do namespace System.ComponentModel assim:

public class Cliente
{
    public string Nome { get; set; }
    public string Email { get; set; }
    public bool Ativo { get; set; }
    [DisplayName("Data de Nascimento")]
    public DateTime DataNascimento { get; set; }
    public decimal Credito { get; set; }
}

Existem alguns atributos disponíveis para customizar o modelo, são eles:

  • [HiddenInput] (System.Web.Mvc) – usando esse atributo será gerado um campo hidden no modo Edit
  • [DataType] (System.ComponentModel.DataAnnotations) – Define o tipo de dados da propriedade
  • [ReadOnly] (System.ComponentModel) – Deixará a propriedade como ReadOnly
  • [DisplayFormat] (System.ComponentModel.DataAnnotations) – Definir a propriedade NullDisplayText exibe um texto para quando o valor for nulo.  Informar a propriedade DataFormatString define em qual formato o texto deverá ser exibido. Informar a propriedade ApplyFormatInEditMode como true irá usar o formato também no modo Edit. Informar a propriedade ConvertEmptyStringToNull irá converter uma string vazia para  nulo.
  • [DisplayName] (System.ComponentModel) – Define o nome da propriedade

Exibindo campos específicos

Se for o caso de exibir ou editar apenas alguns campos do modelo, pode-se utililzar os métodos Html.DisplayFor(model => model.Propriedade) e Html.EditFor(model => model.Propriedade).

Conclusão

É muito simples gerar uma visualização a partir dos assistentes do visual studio, mas isso irá gerar um código estático. Se você quiser uma forma mais prática com base nas propriedades do modelo basta usar os novos métodos. Existe também uma forma de customizar a exibição das propriedades. Vou mostrar isso num próximo post.

Validação com Asp.Net MVC2 parte 3 – Input vs Model validation

Obs.: Esse post está baseado na versão RC2 do Asp.Net MVC2.

O Asp.Net MVC 1 já possuía uma forma de realizar validações, como as validações Required ou Regular Expression que eu já mostrei aqui em outros posts, além dessa validação o framework também avisaria/perceberia erros ao submeter um formulário com dados inválidos, exemplo: enviar uma string com “xxxxx” para uma propriedade de data ou não enviar um valor para um campo que não fosse nullable, como, por exemplo, um campo int.

Vamos olhar uma classe de modelo como a classe Cliente abaixo:

public class Cliente
{
    public int IdCliente { get; set; }
    [Required(ErrorMessage = "Preencha o nome")]
    public string Nome { get; set; }
    [Required(ErrorMessage = "Preencha a idade")]
    [Range(0, 150, ErrorMessage="Idade inválida")]
    public int? Idade { get; set; }
    [Required(ErrorMessage = "Preencha o email")]
    [RegularExpression(@"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,4}$", ErrorMessage = "Email inválido")]
    public string Email { get; set; }
    [Required(ErrorMessage="Preencha o Cpf")]
    public string Cpf { get; set; }
}

E um formuário como o abaixo:

<%: Html.ValidationSummary() %>
Fields
<%: Html.LabelFor(model => model.Nome) %>
<%: Html.TextBoxFor(model => model.Nome) %> <%: Html.ValidationMessageFor(model => model.Nome) %>
<%: Html.LabelFor(model => model.Idade) %>
<%: Html.TextBoxFor(model => model.Idade) %> <%: Html.ValidationMessageFor(model => model.Idade) %>
<%: Html.LabelFor(model => model.Email) %>
<%: Html.TextBoxFor(model => model.Email) %> <%: Html.ValidationMessageFor(model => model.Email) %>

Repare que o modelo tem uma propriedade Cpf decorada com o atributo Required e no formulário essa propriedade não existe. No MVC1 ou até o RC1 do MVC2, se submetêssemos esse formulário, o campo Cpf não seria exigido. Isso é o que é conhecido como Input validation, ou seja, apenas os dados que são submetidos pelo formulário são validados. A partir do MVC RC2, esse campo também é validado, e isso é conhecido como Model validation.

Vou alterar a classe Cliente para ter um Endereco, como abaixo:

public class Cliente
{
    public int IdCliente { get; set; }
    [Required(ErrorMessage = "Preencha o nome")]
    public string Nome { get; set; }
    [Required(ErrorMessage = "Preencha a idade")]
    [Range(0, 150, ErrorMessage="Idade inválida")]
    public int? Idade { get; set; }
    [Required(ErrorMessage = "Preencha o email")]
    [RegularExpression(@"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,4}$", ErrorMessage = "Email inválido")]
    public string Email { get; set; }
    [Required(ErrorMessage="Preencha o Cpf")]
    public string Cpf { get; set; }
    public Endereco Endereco { get; set; }
}

public class Endereco
{
    [Required]
    public string Logradouro { get; set; }
    [Required]
    public string Cidade { get; set; }
    [Required]
    public string Bairro { get; set; }
}

Veja que a propriedade Endereco não é obrigatória na classe Cliente. O que isso quer dizer? Que no Model Validation, se eu não informar nenhum atributo da classe Endereco, o endereço pode ser nulo.

Se eu mantiver o formulário como estava e não adicionar nenhuma outra caixa de texto ao enviar o formulário ele não vai validar o endereço. Agora vou alterar o formulário para que ele tenha o Cpf e a Cidade do Cliente. Assim:

<%= Html.ValidationSummary()%>
Fields
<%= Html.LabelFor(model => model.Nome)%>
<%= Html.TextBoxFor(model => model.Nome)%> <%= Html.ValidationMessageFor(model => model.Nome)%>
<%= Html.LabelFor(model => model.Idade)%>
<%= Html.TextBoxFor(model => model.Idade)%> <%= Html.ValidationMessageFor(model => model.Idade)%>
<%= Html.LabelFor(model => model.Email)%>
<%= Html.TextBoxFor(model => model.Email)%> <%= Html.ValidationMessageFor(model => model.Email)%>
<%= Html.LabelFor(model => model.Cpf)%>
<%= Html.TextBoxFor(model => model.Cpf)%> <%= Html.ValidationMessageFor(model => model.Cpf)%>
<%= Html.LabelFor(model => model.Endereco.Cidade)%>
<%= Html.TextBoxFor(model => model.Endereco.Cidade)%> <%= Html.ValidationMessageFor(model => model.Endereco.Cidade)%>

Apenas o campo Cidade foi adicionada, mas ao submeter o formulário as propriedades Logradouro e Bairro também são validados.

Ou seja, agora a validação é feita sempre no Model e não mais de acordo com os dados que são informados.

Por que isso é importante? Principalmente porque alguém mal intensionado poderia tentar enviar um request de post com apenas parte dos dados. Nas versões anteriores do MVC isso não seria validado automaticamente.

Um ponto importante a se considerar são os atributos que não podem ser nulos como no caso do Int. Imagine que na nossa classe Cliente a propriedade Idade não fosse decorada com o atributo Required. Nas versões anteriores do MVC se o campo Idade não fosse preenchido um erro “A value is required” seria retornado, isso acontece no momento do model-binding, quando framework tenta identificar se existe um valor submetido para uma propriedade, no caso idade, que por padrão não pode ser nula.

A partir do MVC RC2 essa propriedade seria preenchida com o seu valor padrão, que no caso do int é 0.

Validação com Asp.NET MVC2 parte 2 – validações customizadas

Dando sequência no post anterior sobre a validação no MVC2, vou mostrar como é simples fazer uma validação customizada além das existentes no namespace System.ComponenteModel.DataAnnotations.

Para fazer uma validação customizada tudo que precisamos fazer é herdar a classe ValidationAttribute do namespace System.ComponentModel.DataAnnotations, isso, claro, para fazer uma validação genérica, é possível também herdar as outras classes de validação desse namespace como RequiredAttribute, RegularExpressionAttribute e assim por diante.

Por exemplo, e seguindo o exemplo do post anterior, vamos imaginar que para cadastrar um novo cliente em nossa base, além das validações que já estamos fazendo, queremos garantir que o email do cliente seja único na base. Podemos fazer o seguinte, criamos uma classe ClienteUnicoAttribute que herda de ValidationAttribute e ai decoramos o atributo email da classe cliente com o nosso validador.

A classe ClienteUnicoAttribute fica assim:

 public class ClienteUnicoAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        // validação real vai aqui
        return (!value.ToString().Equals("teste@teste.com"));
    }
}

Simples, o importante aqui é sobrescrever o método IsValid com a lógica que queremos, no caso aqui, para manter simples, estou apenas verificando se o email é igual teste@teste.com então retorno false, caso contrário retorno true.

Na classe Cliente vamos decorar o atributo Email com o novo validador e fica assim:

public class Cliente
{
    public int IdCliente { get; set; }
    [Required(ErrorMessage = "Preencha o nome")]
    public string Nome { get; set; }
    [Required(ErrorMessage = "Preencha a idade")]
    [Range(0, 150, ErrorMessage="Idade inválida")]
    public int? Idade { get; set; }
    [Required(ErrorMessage = "Preencha o email")]
    [RegularExpression(@"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,4}$", ErrorMessage = "Email inválido")]
    [ClienteUnico(ErrorMessage="Email de cliente já cadastrado")]
    public string Email { get; set; }
}

Agora, se tentarmos cadastrar um cliente com email teste@teste.com veremos o erro: