quarta-feira, 23 de abril de 2014

Dependency Injection (DI) in ASP.NET MVC

Quem desenvolve para MVC percebe que um dos recursos mais importantes é o padrão que nos habilita a usar o [SoC] - Separation of Concerns.

Nós queremos que os componentes de nossa aplicação sejam os mais independentes possíveis uns dos outros (além de efetuarmos testes e manutenção na aplicação de forma mais fácil), sendo que numa situação ideal, um componente não deveria saber da existência de outro, apenas terem uma ligação fraca uns com os outros (loose coupling) através do uso de [Interfaces].


Por exemplo, se criarmos duas classes com a necessidade de ligação fraca entre elas, a criação de uma interface nos ajuda neste sentido:




public class PasswordResetHelper {
public void ResetPassword() {
   IEmailSender mySender = new MyEmailSender();
   //...call interface methods to configure e-mail details...
   mySender.SendEmail();
   }
}

Mas como você deve ter notado no código acima, ainda temos um problema!...não existe um recurso padrão do C# para implementarmos o uso da interface [IEmailSender] sem a criação de uma instância da classe [MyEmailSender].

Portanto, mesmo usando uma Interface, ainda temos uma ligação de qualquer maneira...



Existe uma solução para isto? Sim, e se chama DI - Dependency Injection!

Uma forma rápida para melhorar o código acima seria a criação de um [construtor] e nele passaríamos a implementação da interface somente para a classe [PasswordResetHelper]


public class PasswordResetHelper {
   private IEmailSender emailSender;

   public PasswordResetHelper(IEmailSender emailSenderParam) {
   emailSender = emailSenderParam;
   }

   public void ResetPassword() {
   // ...call interface methods to configure e-mail details...
   emailSender.SendEmail();
   }
}

Com isto conseguimos quebrar a dependência entre as classes [PasswordResetHelper] e [MyEmailSender]. O que acabamos de fazer se chama [Constructor Injection].

Agora, para melhorarmos ainda mais nosso projeto MVC, podemos e devemos utilizar ferramentas sólidas para o tratamento de [DI]. Para tanto, existem algumas no mercado, como a Unit da Microsoft e NInject, a qual será usada neste artigo.

Criando as Classes de Controle

Vamos criar um novo projeto no Visual Studio, clicando em Novo -> Projeto -> [ASP.NET MVC 4 Web Application] Template e selecione a opção [Empty] para criar o projeto sem conteúdo. Dê o nome para o projeto de [EssentialTools].

A seguir, adicione a seguinte classe na pasta Models do projeto, com o nome Product.cs:


namespace EssentialTools.Models {
   public class Product {
   public int ProductID { get; set; }
   public string Name { get; set; }
   public string Description { get; set; }
   public decimal Price { get; set; }
   public string Category { set; get; }
   }
}

Também adicione na pasta Models uma classe para efetuar um cálculo na coleção de produtos, com o nome [LinqValueCalculator]


using System.Collections.Generic;
using System.Linq;

namespace EssentialTools.Models {
      public class LinqValueCalculator {
      public decimal ValueProducts(IEnumerable<Product> products) {
      return products.Sum(p => p.Price);
      }
   }
}


Agora crie a outra classe (ShoppingCart) que se relaciona com a criada anteriormente


using System.Collections.Generic;

namespace EssentialTools.Models {
   public class ShoppingCart {
      private LinqValueCalculator calc;
      
      public ShoppingCart(LinqValueCalculator calcParam) {
         calc = calcParam;
      }

      public IEnumerable<Product> Products { get; set; }
      public decimal CalculateProductTotal() {
         return calc.ValueProducts(Products);
      }
   }
}


Adicionando um Controller

Adicione um controller na pasta Controllers chamado HomeController e altere o seu conteúdo para o proposto abaixo


using EssentialTools.Models;
using System.Web.Mvc;
using System.Linq;

namespace EssentialTools.Controllers {
public class HomeController : Controller {
   private Product[] products = {
   new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
   new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
   new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
   new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
   };

   public ActionResult Index() {
      LinqValueCalculator calc = new LinqValueCalculator();
      ShoppingCart cart = new ShoppingCart(calc) { Products = products };
      decimal totalValue = cart.CalculateProductTotal();
      return View(totalValue);
   }
}
}


Note que estamos usando este controller para testar nossas classes [LinqValueCalculator] e [ShoppingCart]

Adicionando uma View

Adicione uma View para visualizar o resultado do controler. Clique com o botão direito na ação Index da HomeController e selecione [Add View] e crie uma View chamada [Index], modificando o conteúdo conforme o proposto abaixo


@model decimal
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Value</title>
</head>
<body>
<div>
Total value is $@Model
</div>
</body>
</html>


Neste ponto, execute a aplicação e veja se tudo está rodando corretamente, apresentando a tela abaixo



Usando Ninject

Até o momento criamos duas classes com acoplamento forte (tightly-coupled), na qual ShoppingCart está ligada a classe LinqValueCalculator, e a HomeController ligada as duas. Pensando na manutenção disto em um projeto real podemos imaginar que será complicado, pois, por exemplo, se alterarmos o uso da classe [LinqValueCalculator] por outra, teremos que trocar a referência desta classe no projeto.

Vamos então aplicar primeiramente a Interface, que resolve parte do processo de acoplagem. Crie uma classe chamada [IValueCalculator] na pasta [Models] com o seguinte conteúdo:


using System.Collections.Generic;

namespace EssentialTools.Models {
   public interface IValueCalculator {
      decimal ValueProducts(IEnumerable<Product> products);
   }
}


Com isto podemos agora implementar esta interface na classe [LinqValueCalculator]


using System.Collections.Generic;
using System.Linq;

namespace EssentialTools.Models {
   public class LinqValueCalculator: IValueCalculator {
      public decimal ValueProducts(IEnumerable<Product> products) {
         return products.Sum(p => p.Price);
      }
   }
}


Com isto quebramos a ligação forte entre as classes ShoppingCart e LinqValueCalculator


using System.Collections.Generic;

namespace EssentialTools.Models {
 public class ShoppingCart {
  private IValueCalculator calc;
  
  public ShoppingCart(IValueCalculator calcParam) {
   calc = calcParam;
  }
  public IEnumerable<Product> Products { get; set; }
  
  public decimal CalculateProductTotal() {
   return calc.ValueProducts(Products);
  }
 }
}


Fizemos progresso, porém o C# ainda requer que implementemos a classe [LinqValueCalculator] na nossa HomeController, o que é ruim pois ainda estaremos ligando fortemente as classes


...
public ActionResult Index() {
 IValueCalculator calc = new LinqValueCalculator();
 ShoppingCart cart = new ShoppingCart(calc) { Products = products };
 decimal totalValue = cart.CalculateProductTotal();
 return View(totalValue);
}
...


Para desacoplar de uma vez este processo usaremos então Ninject mantendo também nossa HomeController somente com a implementação de uma Interface

Adicionando Ninject no Visual Studio

A forma mais fácil é o uso do recurso integrado ao VS, o NuGet, abrindo primeiro seu projeto, depois selecionando o menu Tools - > NuGet Package Manager -> Manage NuGet Packages for Solution



Na opção [Online] do Nuget, busque pela palavra [ninject]. Clique no botão install para adicionar as bibliotecas do Ninject no seu projeto. Podemos agora utilizar este recurso em nossa HomeController, conforme mostrado abaixo


using EssentialTools.Models;
using System.Web.Mvc;
using System.Linq;
using Ninject;

namespace EssentialTools.Controllers {
 public class HomeController : Controller {
  private Product[] products = {
  new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
  new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
  new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
  new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
  };

  public ActionResult Index() {
   IKernel ninjectKernel = new StandardKernel();
   ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
   IValueCalculator calc = ninjectKernel.Get<IValueCalculator>();
   
   ShoppingCart cart = new ShoppingCart(calc) { Products = products };
   decimal totalValue = cart.CalculateProductTotal();
   
   return View(totalValue);
  }
 }
}


O primeiro estágio é o preparo para uso do Ninject, através da instância da classe [StandardKernel]. Com isto conseguimos criar um relacionamento entre as interfaces em nossa aplicação e as classes de implementação que nós queremos trabalhar. Você notou isto quando foi executado o passo descrito abaixo


...
ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
...


E como passo final o uso do Ninject para obter a implementação da Interface que queremos


...
IValueCalculator calc = ninjectKernel.Get<IValueCalculator>();
...


a este ponto você pode se perguntar, eu ainda possuo uma ligação forte entre meu Controller e a classe [LinqValueCalculator]...e é verdade. Porém, fácil de resolver pois podemos atuar no [core] do MVC adicionando o Kernel do Ninject no processo interno do MVC, de forma a desacoplar de uma vez por todas nossas classes da HomeController.

Criando um Dependency Resolver

Este recurso é interessante pois vamos adicionar nossas resoluções de classes x interfaces, no [core] do MVC, criando uma classe [resolver] que será instanciada na inicialização do projeto, por configurá-la no arquivo [Global.asax.cs] do projeto. Como passo final, alteramos nossa HomeController para usar somente a interface que desejamos. Veja todos estes passos em sequência:

1 - Adicione uma nova pasta chamada [Infrastructure] no projeto e crie uma classe chamada [NinjectDependencyResolver] com o conteúdo abaixo:


using System;
using System.Collections.Generic;
using System.Web.Mvc;
using Ninject;
using Ninject.Parameters;
using Ninject.Syntax;
using System.Configuration;
using EssentialTools.Models;

namespace EssentialTools.Infrastructure {
 public class NinjectDependencyResolver : IDependencyResolver {
  private IKernel kernel;
  
  public NinjectDependencyResolver() {
   kernel = new StandardKernel();
   AddBindings();
  }
  
  public object GetService(Type serviceType) {
   return kernel.TryGet(serviceType);
  }
  
  public IEnumerable<object> GetServices(Type serviceType) {
   return kernel.GetAll(serviceType);
  }
  
  private void AddBindings() {
   kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
  }
 }
}


2 - Registre a classe Dependency Resolver no Global.asax.cs (no método Application_Start)


using EssentialTools.Infrastructure;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;

namespace EssentialTools {
 public class MvcApplication : System.Web.HttpApplication {
  protected void Application_Start() {
   AreaRegistration.RegisterAllAreas();
   
   DependencyResolver.SetResolver(new NinjectDependencyResolver());
   
   WebApiConfig.Register(GlobalConfiguration.Configuration);
   FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
   RouteConfig.RegisterRoutes(RouteTable.Routes);
  }
 }
}


3 - Refatore sua HomeController


using EssentialTools.Models;
using System.Web.Mvc;
using System.Linq;

namespace EssentialTools.Controllers {
 public class HomeController : Controller {
  private Product[] products = {
  new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
  new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
  new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
  new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
  };

  private IValueCalculator calc;
  public HomeController(IValueCalculator calcParam) {
   calc = calcParam;
  }

  public ActionResult Index() {
   ShoppingCart cart = new ShoppingCart(calc) { Products = products };
   decimal totalValue = cart.CalculateProductTotal();
   return View(totalValue);
  }
 }
}


That's it!!

Ref: Pro ASP.NET MVC 4 - 4th Edition - Adam Freeman, aPress

sexta-feira, 11 de abril de 2014

SQL Server - Busca de Índices

Realmente não é uma tarefa trivial a análise e busca de índices no banco de dados do SQL Server, devido ao simples fato da diversidade deles.

Por exemplo, veja algumas opções abaixo:
Tipo Descrição Informação Adicional
Clustered Um índice [agrupado] ordena e armazena as linhas de dados da tabela ou visão baseado em uma chave de índice agrupada. O índice agrupado é implementado como uma estrutura na qual os dados de uma tabela poderão ser retornados rapidamente. Clustered And Nonclustered Indexes
Nonclustered Um índice [não agrupado] pode ser definido em uma tabela ou visão com um índice agrupado ou em uma pilha. Cada linha de um índice contém um valor chave não agrupado e um localizador de linha. Este localizador aponta para a linha de dados em um índice agrupado ou pilha tendo o valor da chave. Clustered And Nonclustered Indexes
Unique Um índice único assegura que a chave de índice não contém valor duplicado, portanto cada linha na tabela ou visão é unica. Create Unique Indexes
Index with included columns Refere-se a um índice [não agrupado] que é extendido para incluir colunas [não chave] em adição as colunas chave. Create Indexes with Included Columns

Além de vários outros...

Para um desenvolvedor, ter que buscar estes tipos de índices programaticamente pode ser desafiador. Então, como uma dica, segue abaixo tipos diferentes de Queries para retornar tipos diferentes de índices.

Busca de Foreign Keys

SELECT f.name AS ForeignKey,  
      TableSchema =   
           (SELECT TOP 1 s.name FROM sys.schemas s JOIN sys.tables t ON t.schema_id = s.schema_id AND t.object_id = f.parent_object_id),  
   OBJECT_NAME(f.parent_object_id) AS TableName,  
   COL_NAME(fc.parent_object_id, fc.parent_column_id) AS ColumnName,  
   ReferenceTableSchema =   
           (SELECT TOP 1 s.name FROM sys.schemas s JOIN sys.tables t ON t.schema_id = s.schema_id AND t.object_id = f.referenced_object_id),  
      OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName,  
   COL_NAME(fc.referenced_object_id, fc.referenced_column_id) AS ReferenceColumnName  
 FROM sys.foreign_keys AS f  
   INNER JOIN sys.foreign_key_columns AS fc ON f.OBJECT_ID = fc.constraint_object_id  



Busca de Primary Keys / Unique Key Constraints

 SELECT ROW_NUMBER() OVER (PARTITION BY k.name ORDER BY ic.key_ordinal, s.name, t.name, c.name) rowC,  
        s.name TABLE_SCHEMA, t.name TABLE_NAME,  
     k.name CONSTRAINT_NAME, K.type_desc CONSTRAINT_TYPE, c.name COLUMN_NAME,  
        ic.key_ordinal AS ORDINAL_POSITION  
 FROM sys.key_constraints k  
      JOIN sys.tables t ON t.object_id = k.parent_object_id  
      JOIN sys.schemas s ON s.schema_id = t.schema_id  
      JOIN sys.index_columns as ic ON ic.object_id = t.object_id  
      AND ic.index_id = k.unique_index_id  
      JOIN sys.columns as c ON c.object_id = t.object_id  
      AND c.column_id = ic.column_id  
 WHERE s.name <> 'dbo'  


Busca de [Unique Index]

 SELECT ROW_NUMBER() OVER (PARTITION BY i.name, s.name, o.name ORDER BY i.name, s.name, o.name, co.name) rowC,  
        i.name IndexName, s.name SchemaName, o.name TableName, co.name ColumnName, ic.key_ordinal ColumnOrder  
 FROM sys.indexes i   
 JOIN sys.objects o ON i.object_id = o.object_id  
 JOIN sys.index_columns ic ON ic.object_id = i.object_id   
    AND ic.index_id = i.index_id  
 JOIN sys.columns co ON co.object_id = i.object_id   
    AND co.column_id = ic.column_id  
 JOIN sys.tables t ON t.object_id = o.object_id  
 JOIN sys.schemas s ON s.schema_id = t.schema_id  
 WHERE i.[type] = 2   
 AND i.is_unique = 1   
 AND i.is_primary_key = 0  
 AND o.[type] = 'U'  


Busca de [Index Not Unique]

 SELECT ROW_NUMBER() OVER (PARTITION BY i.name, s.name, o.name ORDER BY i.name, s.name, o.name, ic.key_ordinal, co.name) rowC,  
        i.name IndexName, s.name SchemaName, o.name TableName, co.name ColumnName, ic.key_ordinal ColumnOrder  
 FROM sys.indexes i   
 JOIN sys.objects o ON i.object_id = o.object_id  
 JOIN sys.index_columns ic ON ic.object_id = i.object_id   
    AND ic.index_id = i.index_id  
 JOIN sys.columns co ON co.object_id = i.object_id   
    AND co.column_id = ic.column_id  
 JOIN sys.tables t ON t.object_id = o.object_id  
 JOIN sys.schemas s ON s.schema_id = t.schema_id  
 WHERE i.is_unique = 0   
 AND i.is_primary_key = 0


Busca de [Default Constraints]

 SELECT s.name SchemaName, t.name TableName, col_name(df.parent_object_id, df.parent_column_id) ColumnName, df.*  
 FROM sys.default_constraints df  
       JOIN sys.tables t ON df.parent_object_id = t.object_id  
       JOIN sys.schemas s ON t.schema_id = s.schema_id  


That's It!!