Artigo - Injeção de dependência com StructureMap

Falaremos aqui, neste artigo sobre um recurso de desenvolvimento de software chamado Injeção de dependência, criaremos um projeto em MVC ( Model-View-Controller ) com 3 camadas (camada de dados, camada de negóci

Injeção de dependência ?

A injeção de dependência é geralmente utilizada quando se deseja diminuir o acoplamento entre diferentes componentes/módulos de um sistema, de forma que o controle de instanciação das classes dependentes é realizado fora das classes, ou seja, todas as dependências entre os componentes/módulos não são definidos programaticamente, mas através da configuração de um container, onde o mesmo é responsável por injetar em cada componente/módulo suas dependências necessárias declaradas no código da classe. Mais claramente, utilizando a injeção de dependência é possível instanciar diversas classes concretas, sem manter-se aclopado a mesma.

Criação do projeto

Vamos criar um projeto do tipo ASP.NET MVC com o nome de Site.Apresentação no respectivo caminho C:\Site conforme a figura 1.0.


Figura 1.0


Sem criar um projeto de testes, vamos renomear nossa solução para Site e criar mais dois projetos do tipo Class Library com os nomes de Site.Dados, que será o projeto onde ficara nosso Model e todas as chamadas ao banco de dados, e Site.Servicos, que ficara responsável por disponibilizar os recursos e serviços pertinentes a aplicação, ao final nossa aplicação deve ficar igual a figura 1.1.


Figura 1.1


Agora no projeto de Dados, vamos criar uma pasta com o nome de Model, como utilizaremos LinqToSQL, dentro da mesma criamos um arquivo do tipo LINQ to SQL Classes com o nome de SiteDB. Para nosso projeto eu criei uma tabela no banco chamada tbUsuarios, então arrastamos nossa tabela para o arquivo criado e a renomeamos para Usuario, igual à figura 1.2.


Figura 1.2


Ainda no projeto de dados vamos criar uma interface na raiz do projeto com o nome de IUsuarioDB com o seguinte código:

using System.Linq;

using Site.Dados.Model;

 

namespace Site.Dados

{

    public interface IUsuarioDB

    {

        IQueryable<Usuario> SelecionaUsuarios();

    }

}

 

Em seguida criamos uma classe chamada UsuarioDB que ira implementar a interface criada anteriormente da seguinte forma:

using System.Linq;

using Site.Dados.Model;

 

namespace Site.Dados

{

    public class UsuarioDB : IUsuarioDB

    {

        SiteDBDataContext _db;

 

        public UsuarioDB(SiteDBDataContext db)

        { this._db = db; }

 

        public IQueryable<Usuario> SelecionaUsuarios()

        {

            return _db.Usuarios;

        }

    }

}

 

Note que é criado um construtor que recebe como parâmetro um objeto do tipo SiteDBDataContext onde atualiza o objeto da classe que é utilizado para realizar as consultas no DB. 

Agora no projeto de Serviços vamos adicionar uma referencia ao projeto Site.Dados conforme a figura 1.3.


Figura 1.3


Criamos agora uma interface na raiz do projeto com o nome de IUsuarioServ com o seguinte código:

using Site.Dados.Model;

 

namespace Site.Servicos

{

    public interface IUsuarioServ

    {

        Usuario SelecionaUsuario(int codUsuario);

    }

}

 

Em seguida criamos uma classe chamada UsuarioServ que implementara a interface criada conforme é exibido abaixo:

using Site.Dados.Model;

using Site.Dados;

using System.Linq;

 

namespace Site.Servicos

{

    public class UsuarioServ : IUsuarioServ

    {

        IUsuarioDB _db;

 

        public UsuarioServ(IUsuarioDB db)

        { this._db = db; }

 

        public Usuario SelecionaUsuario(int codUsuario)

        {

            return _db.SelecionaUsuarios()

                      .Where(u => u.codUsuario == codUsuario)

                      .SingleOrDefault();

        }

    }

}


Note que também é criado um construtor, porém, este recebe como parâmetro um objeto do tipo IUsuarioDB que atualiza o objeto da classe que é utilizado para chamar os métodos declarados na interface IUsuarioDB do projeto de Dados.

Vamos agora adicionar duas referências no projeto de Apresentação, uma do projeto de Serviços e outra do projeto de Dados.

Em seguida modificaremos o HomeController para que fique da seguinte forma:

using System.Web.Mvc;

using Site.Servicos;

using Site.Dados.Model;

 

namespace Site.Apresentacao.Controllers

{

    [HandleError]

    public class HomeController : Controller

    {

        IUsuarioServ _serv;

 

        public HomeController(IUsuarioServ serv)

        { this._serv = serv; }

 

        public ActionResult Index()

        {

            Usuario u = _serv.SelecionaUsuario(1);

 

            ViewData["Message"] = u.nome;

 

            return View();

        }

 

        public ActionResult About()

        {

            return View();

        }

    }

}

 

Note novamente o construtor que atualiza o objeto da interface IUsuarioServ referente ao projeto de Serviços. Repare que se compilarmos a aplicação não é exibido nenhum erro, porém, se a executarmos é gerada uma Exception do tipo InvalidOperationException, conforme é mostrado na figura 1.4.


Figura 1.4


Este erro ocorre, pois, como definimos que o construtor do Controller recebera um objeto do tipo IUsuarioServ como parâmetro, algo deve injetar esta dependência ao mesmo, ai entra o StructureMap.


StructureMap

Vamos salvar o arquivo StructureMap.dll no diretório C:\Site de nossa aplicação. Agora adicionaremos a referencia da DLL nos projetos de Apresentação e Dados.

Já no projeto de Dados vamos criar uma classe chamada DBServiceRegistry na raiz do projeto com o seguinte conteúdo:

using StructureMap.Configuration.DSL;

using Site.Dados.Model;

 

namespace Site.Dados {

   

    public class DBServiceRegistry:Registry {

        protected override void configure() {

 

            ForRequestedType<SiteDBDataContext>()

                .TheDefault

                .Is

                .ConstructedBy(() => new SiteDBDataContext());

           

        }

    }

}

 

Novamente no projeto de Apresentação dentro do diretório View vamos criar duas classes, uma chamada Bootstrapper com o conteúdo:

using StructureMap;

using StructureMap.Configuration.DSL;

using Site.Dados;

using Site.Servicos;

using Site.Dados.Model;

 

namespace Site.Apresentacao

{

    public static class Bootstrapper {

 

        public static void ConfigureStructureMap()

        {

            ObjectFactory.Initialize(structureMap =>

            {

                structureMap.AddRegistry(new DBServiceRegistry());

                structureMap.AddRegistry(new SiteRegistry());

            });

        }

    }

 

    public class SiteRegistry : Registry

    {

        protected override void configure()

        {

            ForRequestedType<IUsuarioDB>()

                .TheDefaultIsConcreteType<UsuarioDB>();

 

            ForRequestedType<IUsuarioServ>()

                .TheDefaultIsConcreteType<UsuarioServ>();

        }

    }

}

 

E outra chamada ViewEngine com o conteúdo:

using System.Web.Mvc;

 

namespace Site.Apresentacao.Views

{

    public class ViewEngine : WebFormViewEngine

    {

        public ViewEngine()

        {

            MasterLocationFormats = new[] {

                "~/Views//.master",

                "~/Views/Shared/.master"

            };

            ViewLocationFormats = new[] {

                "~/Views//.aspx",

                "~/Views//.ascx",

                "~/Views/Shared/.aspx",

                "~/Views/Shared/.ascx",

            };

            PartialViewLocationFormats = ViewLocationFormats;

 

        }

    }

}

 

Desta vez dentro do diretório Controllers criamos outra classe, agora chamada StructureMapControllerFactory com o seguinte código implementado:

using System;

using System.Web.Mvc;

using StructureMap;

 

namespace Site.Apresentacao.Controllers

{

    public class StructureMapControllerFactory : DefaultControllerFactory

    {

        protected override IController GetControllerInstance(Type controllerType)

        {

            IController result = null;

 

            if (controllerType != null)

            {

                try

                {

                    result = ObjectFactory.GetInstance(controllerType) as System.Web.Mvc.Controller;

                }

                catch (StructureMapException)

                {

                    System.Diagnostics.Debug.WriteLine(ObjectFactory.WhatDoIHave());

                    throw;

                }

            }

 

            return result;

        }

    }

}

 

E por fim alteramos o método Application_Start do arquivo Global.asax para que fique da seguinte forma:

protected void Application_Start()

    {

        RegisterRoutes(RouteTable.Routes);

 

        Bootstrapper.ConfigureStructureMap();

 

        ControllerBuilder.Current.SetControllerFactory

        (new Site.Apresentacao.Controllers.StructureMapControllerFactory());

 

        ViewEngines.Engines.Add(new Views.ViewEngine());

    }

Agora sim. Vamos iniciar nossa aplicação e note que, desta vez é exibido o nome do usuario enviado como parametro pelo controller até o projeto de serviço, que por sua vez chama o projeto de dados e retorna o usuário em questão.


Figura 1.5


Conclusão

Já sabemos que, quanto maior o acoplamento entre os componentes do projeto, maior é a dependência entre um módulo e outro, que pode resultar em alterações e manutenções em diversas partes do sistema. Obviamente que, o desejável é que o acoplamento seja o menor possível entre os componentes, para que, quando efetuada qualquer alteração em código tenha o menor impacto possível nas classes dependentes e vice versa.

Espero ter ajudado a perceber como a injeção de dependência é um ótimo recurso para diminuirmos o acoplamento em um projeto.

Artigos relacionados