{tecnologia, conceitos, negócios, idéias, práticas, .NET, ruby, osx, ios e algo mais}
01/02/2011
Na última semana vi várias dúvidas surgindo sobre IoC e DI no ASP.NET MVC 3.O que poucas pessoas sabem é que no ASP.NET MVC 3 há uma forma bastante simples de resolvermos dependências.
O MVC 3 faz uso de um Service Locator do tipo IDependencyResolver.aspx). Esta interface possui 2 métodos:
Como vocês podem ver esta interface permite ao ASP.NET MVC abstrair a resolução de dependências.
A classe DependencyResolver atua como um Registry para os provedores de serviços, que como podemos imaginar são objetos do tipo IDependencyResolver.O principal método utilizado(o mais simples e trivial) é o SetResolver(IDependencyResolver).aspx).
Chega de falatório e vamos ver como podemos resolver dependências nos construtores de nossos controllers com um objeto do tipo IDependencyResolver.Vamos criar um controller bem simples:
1 public class TemDependenciasController : Controller{
2 public IDependencia1 Dependencia1 { get; set; }
3 public IDependencia2 Dependencia2 { get; set; }
4
5 public TemDependenciasController(IDependencia1 dependencia1, IDependencia2 dependencia2) {
6 Dependencia1 = dependencia1;
7 Dependencia2 = dependencia2;
8 }
9
10 public ActionResult Index() {
11 var b = this.Dependencia1.Metodo("foo");
12 this.Dependencia2.AlgumMetodo();
13
14 return View();
15 }
16 }
Este controller possui duas dependências em seu construtor. Este código compila porém não conseguimos executar esta aplicação pois no momento em que o ASP.NET MVC tentar instanciar o controller não conseguirá resolver as dependências do seu construtor. Vamos então criar uma classe que implemente IDependencyResolver que atuará como nosso SL:
1 public class MeuDependencyResolver : IDependencyResolver{
2 private static ILookup<Type, object> lookup;
3 public MeuDependencyResolver() {
4 lookup = AsDependencias().ToLookup(l => l.GetType().GetInterfaces().FirstOrDefault());
5 }
6
7 public IEnumerable<object> AsDependencias() {
8 yield return new DummyDependencia1();
9 yield return new DummyDependencia2();
10 yield return new DummyDependencia2_2();
11 }
12
13 public object GetService(Type serviceType) {
14 var ctorInfo = serviceType.GetConstructors().FirstOrDefault();
15 if(ctorInfo == null)
16 return null;
17 var paramInfo = ctorInfo.GetParameters();
18 var resolvedParams = ResolveParamTypes(paramInfo);
19 var resolvedParamDependencies = ResolveDependencies(resolvedParams).ToList();
20 return Activator.CreateInstance(serviceType, resolvedParamDependencies.ToArray());
21 }
22
23 private IEnumerable<object> ResolveDependencies(IEnumerable<type> resolvedParams) {
24 return resolvedParams.Select(paramType => lookup[paramType].FirstOrDefault());
25 }
26
27 private IEnumerable<type> ResolveParamTypes(ParameterInfo[] paramInfo) {
28 foreach(var param in paramInfo) {
29 yield return param.ParameterType;
30 }
31 }
32
33 public IEnumerable<object> GetServices(Type serviceType) {
34 return new List<object>();
35 }
36 }
Este código não possui nada muito diferente do normal. O que fazemos é configurar tipos de forma hard-coded que serão utilizados pelo ASP.NET MVC para resolver as dependências. E usamos um pouco de reflection para resolver estas dependências.
Percebam que este nosso SL é bem pobrinho. Ele só sabe resolver dois tipos de dependências(e para este exemplo é tudo o que precisamos, pois o foco aqui é a feature e não a implementação). Usamos reflection pois o type do service que é informado no método GetService não é o type da dependência e sim o type do controller que está sendo criado. Por isso descobrimos quais os parâmetros do construtor do controller, obtemos os mesmos do nosso dicionário e criamos então uma instância do controller. O código acima serve para mostrar que é sempre melhor usar um container de IoC/DI de mercado. Não tente criar o seu. Mas fiz isso pois mostrarei em outro post como é simples fazer isso com Ninject
Para configurarmos nossa classe para ser utilizada precisamos chamar o método SetResolver da classe DependencyResolver. O melhor para fazer isso neste caso é o Application_Start no Global.asax:
1 protected void Application_Start(){
2 AreaRegistration.RegisterAllAreas();
3 RegisterGlobalFilters(GlobalFilters.Filters);
4 RegisterRoutes(RouteTable.Routes);
5 DependencyResolver.SetResolver(new MeuDependencyResolver());
6 }
Na linha 5 chamamos o método SetResolver para informar ao ASP.NET MVC que o IDependecyResolver a ser utilizado é o que criamos(MeuDependencyResolver).Desta forma podemos executar nossa aplicação e ver que "magicamente" nossas dependências serão resolvidas e passadas para o construtor do nosso controller.
Confira os fontes completos aqui no Github: https://github.com/vquaiato/MVC3DependencyResolver
Todo o trabalho que tivemos aqui foi para criar uma classe que sabe resolver dependências, pois o resto a infra do ASP.NET MVC 3 cuidou de tudo.No próximo post faremos exatamente a mesma coisa porém utilizando o Ninject ao invés de criar nosso próprio container.O ASP.NET MVC 3 possui boas melhorias com relação a injeção de dependências e resolução de dependências. Muitas coisas ficaram simples. No exemplo acima não precisamos mais criar uma classe que implemente IControllerFactory.Se você ainda está por fora do ASP.NET MVC 3 confira estes posts e não perca o MVC Summit em março!
Abraços,
Vinicius Quaiato.