1. O problema de usar a entidade direto na tela
A tentação inicial é passar a entidade de domínio (a classe que espelha a tabela
Cliente do Módulo 2) diretamente para a View. Funciona para telas
simples, mas quebra rápido: uma tela de edição não precisa de DataCadastro;
um formulário de cadastro precisa de um campo "Confirmar senha" que não existe na tabela; uma
tela de listagem quer mostrar "Total de Pedidos", um valor calculado que não é uma coluna. Três
tipos diferentes de classe resolvem essas necessidades diferentes.
| Model (entidade) | ViewModel | DTO | |
|---|---|---|---|
| Representa | Uma tabela/conceito de domínio | O que uma tela específica precisa | Dados trafegando entre camadas (ex: API ↔ cliente) |
| Onde vive | Camada de domínio/dados | Camada de apresentação (MVC) | Camada de API/integração |
| Pode ter campos calculados/agregados? | Raramente | Sim, com frequência | Sim |
| Pode ter campos "de formulário" (ex: ConfirmarSenha)? | Não | Sim | Raramente |
2. Model (entidade de domínio)
public class Cliente
{
public int Id { get; set; }
public string Nome { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public bool Ativo { get; set; }
public DateTime DataCadastro { get; set; }
}
3. ViewModel — moldado para uma tela específica
// para a tela de LISTAGEM — inclui um dado calculado que não existe na tabela
public class ClienteListaItemViewModel
{
public int Id { get; set; }
public string Nome { get; set; } = string.Empty;
public int TotalPedidos { get; set; } // calculado via JOIN/GROUP BY
public decimal ValorTotalCompras { get; set; }
}
// para a tela de CADASTRO — tem um campo que a entidade nunca teria
public class ClienteCadastroViewModel
{
[Required(ErrorMessage = "O nome é obrigatório")]
public string Nome { get; set; } = string.Empty;
[Required, EmailAddress]
public string Email { get; set; } = string.Empty;
[Required, MinLength(6)]
public string Senha { get; set; } = string.Empty;
[Compare(nameof(Senha), ErrorMessage = "As senhas não coincidem")]
public string ConfirmarSenha { get; set; } = string.Empty;
}
ClienteListaItemViewModel mapeia quase exatamente o resultado da
procedure usp_ClienteRelatorioTop que você escreveu no capítulo 7 do
Módulo 2 (que já devolvia TotalPedidos e ValorTotal
calculados via JOIN/GROUP BY) — o
ViewModel é o "molde" C# que recebe exatamente essas colunas.
4. DTO — para trafegar entre camadas/sistemas
DTO (Data Transfer Object) é conceitualmente parecido com um ViewModel — uma classe "só de dados", sem comportamento — mas usado especificamente na fronteira entre sistemas (API ↔ cliente, serviço ↔ serviço), não amarrado a uma tela específica. Você vai usá-lo intensamente no Módulo 4 (APIs).
// exposto por uma API — nunca inclui dados sensíveis/internos da entidade
public class ClienteDto
{
public int Id { get; set; }
public string Nome { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
// sem DataCadastro, sem qualquer campo interno — só o que o consumidor da API precisa
}
5. Mapeando entre eles
A conversão entre Model e ViewModel/DTO geralmente é manual (um construtor, um método de mapeamento) ou automatizada por uma biblioteca. Para projetos pequenos/médios, manual costuma ser suficiente e mais fácil de depurar:
public static class ClienteMapper
{
public static ClienteDto ParaDto(Cliente cliente) => new()
{
Id = cliente.Id,
Nome = cliente.Nome,
Email = cliente.Email,
};
public static Cliente ParaEntidade(ClienteCadastroViewModel viewModel) => new()
{
Nome = viewModel.Nome,
Email = viewModel.Email,
Ativo = true,
};
}
Além de carregar campos desnecessários, expor a entidade diretamente numa API ou formulário
cria uma vulnerabilidade conhecida como over-posting: se um formulário/JSON
malicioso incluir um campo como Ativo=true ou até um
Id arbitrário, e o model binding (próximo capítulo) preencher a
entidade inteira sem filtro, um usuário pode alterar dados que não deveria. Um ViewModel/DTO
com só os campos esperados elimina essa classe de bug.
6. Onde isso te leva
Você já viu [Required], [EmailAddress] e
[Compare] nos exemplos acima — o próximo capítulo aprofunda
exatamente isso: como o ASP.NET Core preenche esses ViewModels a partir da requisição (model
binding) e como valida os dados antes de qualquer regra de negócio rodar.