Neste post vou descrever um assunto bem legal mas antes, queria deixar um recado aos que acompanham o Programando em .Net por feed. Voltei aos posts e espero que a partir de agora sem essas longas pausas.
Bom, vamos ao que interessa!
Nos últimos meses tenho trabalhado integralmente com ASP.NET 3.5, com o Adobe Flex, com o framework FluorineFx, com NHibernate, com o ASP.NET MVC, com o Unity, enfim… muitas tecnologias e tenho bastante coisa para falar. Posts interessantes estão sendo criados. Aguardem.
E hoje resolvi comentar sobre um post em que conheci um método bem legal de trabalharmos com ModelBinders do ASP.NET MVC.
Um dos patterns que na minha opinião mais combinam com o ASP.NET MVC é o a Inversão de Controle (conhecido também como IoC), mais precisamente o Injeção de Dependências (DI). Esse pattern torna nosso trabalho incrivelmente produtivo, uma aplicação extremamente elegante e uma forma de arquitetura muito funcional. Injetar minhas dependências através de construtores se tornou nas minhas aplicações algo padrão. Porém cheguei em uma sinuca de bico esses dias que me deixou confuso. “Como injetar dependência em nossos modelbinders (custom modelbinder)?”
A mágica veio de um artigo bem simples do Fredrik Kalseth. O artigo é o Constructor Injection for ASP.NET MVC Model Binders.
No caso do artigo do Fredrik Kalseth ele usa como framework de IoC o NInject, eu uso o Unity Application Block mas qualquer framework faz a mesma coisa. Eu já usei também o StructureMap onde comentei neste post, StructureMap – Uma Injeção de Dependências
Vamos imaginar um controller com uma action semelhante a essa:
public class ProdutoController : Controller
{
[AcceptVerbs(HttpVerbs.Post)]
public void Novo(Produto produto)
{
}
}
Suponha que no formulário para o cadastro de um novo produto seja postado o código da marca desse produto (através de um select). Como carregar a entidade da marca desse produto dentro do meu model binder? Vamos dar uma olhada nas variáveis dessa questãop.
Já vimos que temos uma classe em nosso modelo chamada Produto que em algum momento registramos um model binder que será responsável por fazer de um post transformá-a em uma classe concreta da entidade “Produto”. A primeira questão a se pensar para termos injeção de dependência no model binder é pensar em “quem vai resolver minhas dependências?”.
O Asp.Net MVC tem um “model binder default”. Esse model binder é bem simples e para modelos extremamente simples ele resolve nosso caso, como por exemplo uma entidade que necessite apenas de “Nome” e “Endereço”. Esse “model binder default” resolve esse problema pois é simples. Porém, no nosso caso, precisamos de um “model binder genérico” que resolva qualquer model binder que criarmos que necessite de dependências. Esse mesmo “model binder gernérico” irá receber em seu construtor o container com nossas dependências e ele mesmo irá resolve-las. Veja o exemplo de uma classe GenericBinderResolver, descrito no post do Fred:
public class GenericBinderResolver : DefaultModelBinder
{
private readonly IUnityContainer _resolver;
private static readonly Type BinderType = typeof(ModelBinder<>);
public GenericBinderResolver(IUnityContainer resolver)
{
_resolver = resolver;
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
Type genericBinderType = BinderType.MakeGenericType(bindingContext.ModelType);
var binder = _resolver.Resolve(genericBinderType) as IModelBinder;
if (null != binder) return binder.BindModel(controllerContext, bindingContext);
return base.BindModel(controllerContext, bindingContext);
}
}
Vejam que ele herda de DefaultModelBinder, recebe como parametro no construtor um container (no nosso caso um UnityCOntainer) e sobreescreve o método BindModel que é encarregado de resultar no nosso model binder que necessita de uma injeção de dependência no construtor. Quando uma requisição for “postada” para a action “Novo” do nosso constroller “Produto”, esse cara ai em cima que irá ser chamado para achar o model binder que irá bindar o parametro “Produto” dessa action.
Masta sobreescrevermos agora que o model binder padrão da nossa aplicação é GenericModelBinder e passarmos para ele o container do Unity.
ModelBinders.Binders.DefaultBinder = new GenericBinderResolver(_container);
Até aqui tudo bem, mas vemos que no nosso GenericBinderResolver temos um “objeto não identificado”. Quem é aquele “ModelBinder<>”?
Nossos model binders personalizados eram criados como uma implementação de IModelBinder e no método “GetValue”, criávamos nossa lógica onde faziamos do post de um formulário, uma entidade concreta. Como aqui, nosso caso é mais complexo, precisamos de um modelo de model binder que tenha uma “caracteristica” mais particular, por exemplo, a que entidade ele é responsável. Por isso temos ModelBinder<T> descrita abaixo:
public abstract class ModelBinder<T> : IModelBinder
{
protected abstract T BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext);
object IModelBinder.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
return BindModel(controllerContext, bindingContext);
}
}
Veja que ela tambem herda de IModelBinder, porém ela é uma classe abstrata e com um método que retorna “T”, que é definido em sua declaração. Nossos model binders personalizados agora devem ser herdados dessa classe abstrata ModelBinder<T>. Veja o exemplo abaixo:
public class ProdutoBinder : ModelBinder<Produto>
{
private readonly IMarcaRepository _marcaRepository;
public ImovelBinder(IMarcaRepository marcaRepository)
{
_marcaRepository = produtoRepository;
}
protected override Produto BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var post = controllerContext.HttpContext.Request.Form;
Produto produto = new Produto();
produto.Marca = _marcaRepository.ObterMarcaPorId(post["Marca"]);
return produto;
}
}
Nesse caso, não necessitamos mais registrar o model binder, apenas registramos o GenericBinderResolver que esse se encarrega de resolver todos nossos binders que registramos agora, no container do nosso framework de inversão de controle. Veja bem, você não precisa mais registrar o model binder na classe estática ModelBinders (algo parecido com ModelBinders.Binders[typeof (Produto)] = new ProdutoBinder() ). APenas registramos o typo e sua implementação no container de IoC. Abaixo um exemplo de como eu registro o model binder no container do Unity.
_container.RegisterType<ModelBinder<Produto>, ProdutoBinder>(new RequestContextLifeTimeManager(typeof(ModelBinder<Produto>)));
Acima fica mais claro o porquê da classe abstrata ModelBinder<T>. É apenas para especificarmos cara binder no container com sua caracteristica particular.
Vemos também que ao registrarmos no Unity o tipo e sua implementação, definimos seu tempo de vida no container com a classe “RequestContextLifeTimeManager” que não existe no Unity. É uma classe onde foi criado um tempo de vida de duração “por requisição”. A ao final de toda requisição, toda instancia existente no container definida com esse tempo de vida, é excluida do container. Mas é assunto para outro post.
Espero que o post seja útil!
[]´s
no comment untill now