Continuando com a série sobre o Preview 5 do ASP.Net MVC, vou falar agora de uma das features mais legais dessa nova versão, que é a possibilidade de utilizar tipos complexos nos métodos dos controladores. (Vou continuar no exemplo da tabela de categorias do banco Northwid.) Por exemplo, antes, para salvar uma categoria nova, o método seria o seguinte:
[ActionName("Create")]
[AcceptVerbs("POST")]
public ActionResult Save(string CategoryName)
{
var cat = new Models.Category();
cat.CategoryName = CategoryName;
var db = new Models.NorthwindEntities();
db.AddToCategories(cat);
db.SaveChanges();
db.Dispose();
return RedirectToAction("Index");
}
Se eu quisesse salvar também a descrição, já teria que mudar a assinatura do método:
public ActionResult Save(string CategoryName, string Description)
Já começou a complicar… Para facilitar isso tudo, que tal utilizar um objeto complexo, um objeto de negócio, do domínio da aplicação? Poxa, faz bem mais sentido. Nesse caso, estou utilizando o Entity Framework, que tal usar a própria classe de categoria do EF? Em vez de ficar criando a categoria e adicionando os valores um a um:
var cat = new Models.Category();
cat.CategoryName = CategoryName;
Ela já podia vir direto para o método, com as propriedades preenchidas, assim:
public ActionResult Save(Models.Category cat)
Porque não? Fica bem mais coerente. Até porque, se eu tiver uns 3 ou 4 métodos que trabalham com a categoria, não precisaria ficar alterando todos eles sempre que incluo ou excluo uma propriedade nova.
Isso já é possível no Preview 5, e funciona direitinho. Eles criaram um negócio chamado de Model Binders. A idéia é ligar valores que vêm de algum lugar (qualquer lugar), ao seu objeto de negócio. De onde vêm os valores que vão ser utilizados para compor o seu objeto de negócio é você quem decide. Vem de cookies, query string, sessão, você quem manda. Tudo funciona baseado em uma simples interface: IModelBinder. Vejam sua assinatura:
public interface IModelBinder
{
object GetValue(ControllerContext controllerContext, string modelName,
Type modelType, ModelStateDictionary modelState);
}
Simples, não é? Como funciona?
Assim: você recebe um contexto do controlador, que contém tudo sobre o que está acontecendo, como informações de Request e do contexto HTTP (em forma de um System.Web.BaseHttpContextBase – do System.Web.Abstractions, e um BaseHttpRequestBase), e também do roteamento, como dados da rota, da ação sendo chamada, etc, etc. Tudo isso vem na variável controllerContext. Você tem também o nome da variável que vai ser ligada. No meu caso ela se chama “cat”:
public ActionResult Save(Models.Category cat)
Na variável modelType, você recebe os dados do tipo que deve ser criado, nesse caso, uma Category. E na variável modelState uma coleção de dados de estado do modelo. Você junta isso tudo, aquece por meia hora, coloca uma pitada de sal, e no final sai um objeto:
public class CategoryBinder : IModelBinder
{
#region IModelBinder Members
public object GetValue(
ControllerContext controllerContext,
string modelName,
Type modelType,
ModelStateDictionary modelState)
{
var cat = new Models.Category();
HttpRequestBase request = controllerContext.HttpContext.Request;
cat.CategoryName = request["CategoryName"];
cat.Description = request["Description"];
return cat;
}
#endregion
}
Vejam que nesse caso eu ignorei praticamente tudo, só usei os dados do Request, que veio via controllerContext. Acho que essa vai ser a solução mais usada.
Além da interface há também uma classe DefaultBinder, mas não sei qual seria o ganho real de utilizá-la. Vou pesquisar e se descobrir que vale a pena comento aqui com vocês. Além dessa vem também uma DateTimeModelBinder, que faz a conversão de texto para data.
E como usa?
Existem várias opções. Se você é dono do tipo do objeto de negócio (se ele está em um assembly que você controla o fonte), você pode fazer isso, que foi o que eu fiz:
[ModelBinder(typeof(Binders.CategoryBinder))]
public partial class Category
{
}
O que é isso? É uma classe parcial do meu objeto do Entity Framework. Adicionei o atributo ModelBinder, e indiquei meu super binder como resolvedor desse tipo de objeto. Qualquer método de qualquer controlador que tiver uma categoria como parâmetro ganhou de graça um conversor. Excelente!
E se você não controla o tipo? Nesse caso você tem que registar no global.asax, como mostra a primeira linha do método Application_Start:
protected void Application_Start()
{
ModelBinders.Binders[typeof(Models.Category)] =
new Models.Binders.CategoryBinder();
RegisterRoutes(RouteTable.Routes);
}
Tem o mesmo efeito de aplicar na classe. A diferença é que vai valer só nessa aplicação, enquanto que do outro jeito você poderia compartilhar entre vários projetos.
Você pode ainda aplicar o atributo direto no método:
public ActionResult Save2([Bind(typeof(CategoryBinder)]Models.Category cat)
O único problema é que não existe ainda o tal do atributo “Bind”, então essa é só uma hipótese, mas pode ser que esse item seja adicionado ao MVC até o lançamento.
Se você adicionar isso tudo e rodar, colocando um breakpoint no binder, vai ver isso:
Note na janela de locals as variáveis, tudo funciona perfeitamente, com as variáveis com os valores esperados. No Call Stack você consegue ver o caminho que o MVC fez até chegar ao model binder.
Com essa nova funcionalidade do MVC os métodos do controlador já ficam com cara de métodos de verdade, com objetos de negócio, e não só mais tipos primitivos. Fica mais fácil testar também, já que a criação do objeto de negócio está agora separada da ação de negócio, e separação de responsabilidades é sempre bem vinda para testes.
Giovanni Bassi
Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.