Breno Ferreira

Opiniões e códigos de um desenvolvedor de software .NET.

Archive for the ‘Dev’ Category

Testando Rotas no ASP.NET MVC

leave a comment »

Olá pessoal, tudo bem?

Rotas e MVC

Uma conceito muito importante do ASP.NET MVC é o de rotas. Elas servem para mapear a URI dos requests HTTP para algum Controller e uma Action, e, opcionalmente, algum parâmetro.

A rota mais conhecida é aquela que já vem por padrão quando criamos um projeto novo:

  1. routes.MapRoute(
  2.     "Default", // Route name
  3.     "{controller}/{action}/{id}", // URL with parameters
  4.     new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
  5. );

Conforme adicionamos outras rotas ao nosso projeto, fica clara a necessidade de começarmos a testa-las, para termos certeza de que os Controllers e Actions e parâmetros estão sendo mapeados corretamente.

Definindo Rotas

Como exemplo, criei um Controller chamado ProductsController e adicionei duas rotas, uma para a Action Details, e outra para a Action ProductsInCategory, conforme os dois trechos de código abaixo:

ProductsController
  1. public class ProductsController : Controller
  2. {
  3.     // GET: /Produtos/
  4.     public ActionResult Index()
  5.  
  6.     // GET: /Produtos/123
  7.     public ActionResult Details(int id)
  8.  
  9.     //Get: /Produtos/Bebidas
  10.     public ActionResult ProductsInCategory(String category)
  11. }

 

Routes
  1. routes.MapRoute(
  2.     "ProductDetails",
  3.     "Products/{id}",
  4.     new { controller = "Products", action = "Details" },
  5.     new { id = "\\d+" }); //id must be a number
  6.  
  7. routes.MapRoute(
  8.     "ProductsInCategory",
  9.     "Products/{category}",
  10.     new { controller = "Products", action = "ProductsInCategory" },
  11.     new { category = "[a-zA-Z]+" }); //category must be chars only

 

Testando as Rotas

Para testarmos essas rotas, iremos adicionar algums UnitTests ao nosso projeto de testes. Repare que irei usar o Moq para criar um objeto Fake de um HttpContext. Irei também utilizar o FluentAssertions para fazer as Assertions nos tests.

Teste 1
  1. [TestMethod()]
  2. public void QuandoARotaEBarraProductsOControllerMapeadoDeveSerProductsEActionDeveSerIndex()
  3. {
  4.     //arrange
  5.     RouteCollection routes = new RouteCollection();
  6.  
  7.     var httpContextMock = new Mock<HttpContextBase>();
  8.     httpContextMock.Setup(httpContext => httpContext.Request.AppRelativeCurrentExecutionFilePath)
  9.         .Returns("~/Products");
  10.  
  11.     //act
  12.     MvcApplication.RegisterRoutes(routes);
  13.  
  14.     var routeData = routes.GetRouteData(httpContextMock.Object);
  15.  
  16.     //assert
  17.     routeData.Values["Controller"].Should().Be("Products");
  18.     routeData.Values["Action"].Should().Be("Index");
  19. }

 

Teste 2
  1. [TestMethod()]
  2. public void QuandoARotaEBarraProductsBarraIdOControllerMapeadoDeveSerProductsEActionDeveSerDetailsEIdDeveSer1()
  3. {
  4.     //arrange
  5.     RouteCollection routes = new RouteCollection();
  6.  
  7.     var httpContextMock = new Mock<HttpContextBase>();
  8.     httpContextMock.Setup(httpContext => httpContext.Request.AppRelativeCurrentExecutionFilePath)
  9.         .Returns("~/Products/1");
  10.  
  11.     //act
  12.     MvcApplication.RegisterRoutes(routes);
  13.  
  14.     var routeData = routes.GetRouteData(httpContextMock.Object);
  15.  
  16.     //assert
  17.     routeData.Values["Controller"].Should().Be("Products");
  18.     routeData.Values["Action"].Should().Be("Details");
  19.     routeData.Values["id"].Should().Be("1");
  20. }

 

Teste 3
  1. [TestMethod()]
  2. public void QuandoARotaEBarraProductsBarraCategoriaOControllerMapeadoDeveSerProductsEActionDeveSerProductsInCategoryECategoriaDeveSerBebidas()
  3. {
  4.     //arrange
  5.     RouteCollection routes = new RouteCollection();
  6.  
  7.     var httpContextMock = new Mock<HttpContextBase>();
  8.     httpContextMock.Setup(httpContext => httpContext.Request.AppRelativeCurrentExecutionFilePath)
  9.         .Returns("~/Products/Bebidas");
  10.  
  11.     //act
  12.     MvcApplication.RegisterRoutes(routes);
  13.  
  14.     var routeData = routes.GetRouteData(httpContextMock.Object);
  15.  
  16.     //assert
  17.     routeData.Values["Controller"].Should().Be("Products");
  18.     routeData.Values["Action"].Should().Be("ProductsInCategory");
  19.     routeData.Values["category"].Should().Be("Bebidas");
  20. }

Para testarmos as rotas, precisamos seguir 3 passos:

Arrange: Criamos um objeto novo do tipo RouteCollection. Esse objeto é passado como parâmetro para o método RegisterRoutes. Também devemos criar um objeto Fake do tipo HttpContextBase. Esse Fake irá simular um Request feito para uma URI pré-definida para o teste.

Act: Chamamos o método RegisterRoutes, passando o objeto do tipo RouteCollection, e em seguida, chamamos o método GetRouteData, passando o nosso Fake de HttpContext. Isso irá mapear as informações na URI definida no objeto Fake para Controllers, Actions e parâmetros.

Assert: Realizamos as nossas Assertions necessárias para validar que a rota foi mapeada corretamente. Os dados mapeados estão na propriedade Values do nosso objeto RouteData.

Conclusão

Não é muito difícil testar as rotas, basta conhecer um pouco de como o as rotas são definidas por debaixo dos panos, e saber um pouco sobre como criar objetos Fake. Repare que o conceito de Rotas não é exclusivo do ASP.NET MVC, podendo ser usado em outras tecnologias também, como o WCF WebAPI. Porém, a maneira de testar as rotas aqui são específicas do ASP.NET MVC.

Abraços

Breno Ferreira

Written by Breno Ferreira

11/09/2011 at 18:49

Posted in Dev

Tagged with , ,

Criando Scaffolder customizado para o WCF WebAPI

leave a comment »

Olá pessoal,

No último post falei um pouco sobre como customizar um template do MvcScaffolding. Hoje irei explicar como podemos criar um Scaffolder totalmente novo, com funcionalidades nada relacionadas ao ASP.NET MVC. Irei demonstrar rapidamente como voce pode criar um Scaffolder para gerar código para serviços REST utilizando o WCF WebAPI.

Para quem não conhece o WCF WebAPI, recomendo ler um post do Israel Aece que explica muito bem alguns conceitos básicos.

Criando um Custom Scaffolder

Para criarmos um Custom Scaffolder, basta executarmos o seguinte comando no Package Manager Console (depois de ter adicionado o pacote MvcScaffolding é claro):

Gerar Custom Scaffolder
  1. Scaffold CustomScaffolder RestfulService

Esse comando basicamente é um Scaffolder de Scaffolders, ou seja, ele gera um Scaffolder customizado.

No Solution Explorer, agora teremos a seguinte estrutura de arquivos:

image_thumb1

Os arquivos marcados em amarelo foram gerados pelo MvcScaffolding. Um arquivo é o template T4 que possui o template do arquivo de saída, e outro é um arquivo Powershell (.ps1) que irá ser chamado quando executarmos o comando para utilizarmos esse Scaffolder.

O código PowerShell ficou assim:

Default Custom Scaffolder
  1. [T4Scaffolding.Scaffolder(Description = "Enter a description of RestfulService here")][CmdletBinding()]
  2. param(        
  3.     [string]$Project,
  4.     [string]$CodeLanguage,
  5.     [string[]]$TemplateFolders,
  6.     [switch]$Force = $false
  7. )
  8.  
  9. $outputPath = "ExampleOutput"
  10. $namespace = (Get-Project $Project).Properties.Item("DefaultNamespace").Value
  11.  
  12. Add-ProjectItemViaTemplate $outputPath -Template RestfulServiceTemplate `
  13.     -Model @{ Namespace = $namespace; ExampleValue = "Hello, world!" } `
  14.     -SuccessMessage "Added RestfulService output at {0}" `
  15.     -TemplateFolders $TemplateFolders -Project $Project -CodeLanguage $CodeLanguage -Force:$Force

O Comando mais importante aqui é o Add-ProjectItemViaTemplate, que irá gerar o código baseado no Template T4. Esse comando recebe alguns parâmetros importantes:
$outputPath: caminho onde o arquivo será gerado
-Template: Nome do template T4
-Model: dados que são passados para o template T4 como parâmetros para gerar o código
 
Para gerar o serviço com o WebAPI, precisamos de alguns parâmetros para o comando PowerShell:
Service Scaffolder Parameters
  1. [T4Scaffolding.Scaffolder(Description = "Creates a Restful service using WCF WebApi")][CmdletBinding()]
  2. param(
  3.     [parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true)][string]$ModelType,
  4.     [string]$DbContextType,        
  5.     [string]$Project,
  6.     [string]$CodeLanguage,
  7.     [switch]$NoChildItems = $false,
  8.     [string[]]$TemplateFolders,
  9.     [switch]$Force = $false
  10. )

O primeiro parâmetro é o tipo do nosso Model, o segundo é o tipo de algum DbContext do EntityFramework já existente. Note que o primeiro parâmetro é obrigatório.

Não vou entrar em todos os detalhes do script Powershell, senão o post ficaria muito longo, mas o comando para gerarmos o código do serviço é o seguinte:

Gerando o c&amp;amp;#243;digo do servi&amp;amp;#231;o
  1. Add-ProjectItemViaTemplate $outputPath -Template RestfulServiceTemplate `
  2.     -Model @{
  3.         ServiceName = $serviceName;
  4.         RepositoryName = $repositoryName;
  5.         ModelType = [MarshalByRefObject]$foundModelType;
  6.         PrimaryKey = [string]$primaryKey;
  7.         DefaultNamespace = $defaultNamespace;
  8.         ModelTypeNamespace = $modelTypeNamespace;
  9.         ServiceNamespace = $serviceNamespace;
  10.          } `
  11.     -SuccessMessage "Added RestfulService output at {0}" `
  12.     -TemplateFolders $TemplateFolders -Project $Project -CodeLanguage $CodeLanguage -Force:$Force

Passamos como informações para o template T4 os seguintes dados:

  • ServiceName: Nome do tipo do nosso serviço (ex: PersonService)
  • RepositoryName: Nome do tipo do repositório (ex: PersonRepository)
  • ModelType: Tipo do modelo (ex: Person)
  • Primary Key: Propriedade que representa a chave primária no banco (ex: ID)
  • Default Namespace: Namespace padrão do projeto
  • ModelTypeNamespace: Namespace onde o tipo do nosso Model está localizado
  • ServiceNamespace: Namespace onde o serviço estará localizado (DefaultNamespace + “Services”)

Esse comando irá ler o template T4 e gerar um arquivo de saída com o serviço WCF WebAPI.

Uma parte desse template pode ser vista no código abaixo:

Code Snippet
  1.  [ServiceContract]
  2. public class <#= Model.ServiceName #> : I<#= Model.ServiceName #>
  3. {
  4.      private readonly I<#= repositoryName #> <#= repositoryVariableName #>;
  5.      
  6.      //if you are using Dependency Injection, you can delete the following constructor.
  7.      public <#= Model.ServiceName #> ()
  8.          : this(new <#= repositoryName #>())
  9.      {
  10.      }
  11.      
  12.      public <#= Model.ServiceName #> (I<#= repositoryName #> <#= repositoryVariableName #>)
  13.      {
  14.          this.<#= repositoryVariableName #> = <#= repositoryVariableName #>;
  15.      }
  16.      
  17.      [WebGet(UriTemplate="")]
  18.      public IQueryable< <#= modelName #> > GetAll()
  19.      {
  20.          return this.<#= repositoryVariableName #>.All;
  21.      }

Para quem quiser ver maiores detalhes do Scaffolder, criei um projeto no Codeplex e o código fonte complate está disponível lá. Basta entrar no site: http://servicescaffolder.codeplex.com/

Quem quiser utilizar esse Scaffolder, pode fazer uso do Nuget e referenciar o pacote:

Install-Package ServiceScaffolder

Scaffold RestfulService <Model>

Abraços

Breno

Written by Breno Ferreira

14/08/2011 at 15:16

Posted in Dev

Tagged with , , ,

Customizando os templates do MvcScaffolding

leave a comment »

Olá pessoal,

Muitos já devem ter usado, ou pelo menos ouvido falar do MvcScaffolding, uma ferramenta muito útil para nos ajudar a agilizar algumas tarefas digamos, tediosas, como criação de CRUDs. Se você não conhece, o Vinicius Quaiato tem dois posts no blog dele que explicam muito bem como você pode começar a utilizar a ferramenta.

Mas, as vezes, a funcionalidade padrão do MvcScaffolding não nos atende, e precisamos customizar o código gerado. Para fazermos isso, precisamos primeiramente, entender como que o código é gerado.

HowStuffWorks? – MvcScaffolding

O MvcScaffolding funciona utilizando duas tecnologias: T4 (Text Template Transformation Toolkit) Templates e Powershell. T4 Template, basicamente, é um código que gera código, e o alguns scripts Powershell utilizam esses templates para automatizar a geração do código que precisamos.

Por exemplo: o comando que executamos no Package Manager Console do Nuget, nada mais é do que um comando Powershell. Então, quando executamos o comando abaixo, estamos chamando um script Powershell que irá gerar o nosso código baseado nos arquivos T4 Templates apropriados.

Scaffold Controller Person –Repository
 

Se você for na pasta “%solution_folder%\packages\MvcScaffolding.1.0.0\tools”, você irá encontrar algumas pastas, e lá, voce irá encontrar alguns arquivos .ps1 e outros .t4. Esses arquivos que são utilizados quando executamos o Scaffolder que precisamos. No caso do comando acima, ele irá gerar o código baseado nos templates nas pastas: “Controller”, “RazorView” caso você esteja utilizando a Razor View Engine, ou “AspxView”, caso você esteja utilizando a WebForms View Engine.

Para podermos customizar o código gerado, há um comando do MvcScaffolding que automaticamente coloca o arquivo .t4 em uma pasta no nosso projeto. Assim, quando o Scaffolder for utilizar o template, ele irá utilizar este arquivo, e não o arquivo padrão localizado na pasta acima.

Customizando Templates T4

Para podermos customizar o arquivo .t4, basta executar o comando abaixo:

Scaffold CustomTemplate ScaffolderName TemplateName
 

Basta substituir o “ScaffolderName” e o “TemplateName” pelo template do Scaffolder que voce deseja customizar. Para isso, você pode ir na pasta onde estão localizados os templates (veja acima), ou então seguir a tabela abaixo:

ScaffolderName TemplateName
DbContext DbContext
DbContext DbContextEntityMember
Repository Repository
Action Action
Action ActionPost
Action ViewModel
ActionUnitTest TestClass
ActionUnitTest TestMethod
View Index, Create, _CreateOrEdit, Details, Edit, Delete, Empty,
Controller ControllerWithContext
Controller ControllerWithRepository

Após executar o comando, ele irá gerar um arquivo .t4 na seguinte pasta do nosso projeto:

“Code Templates\Scaffolders\<ScaffolderName>\<TemplateName>.t4”

Agora, basta editar este arquivo. A sintaxe é bem simples: os trechos que estiverem entre <#= #> irão ser executados para gerar um código de saída. Pense como uma ViewEngine do MVC, os blocos que estão com um @ (no caso do Razor) ou um <% %> (no caso do WebForms) são executados para gerar algum HTML. No template T4 é a mesma coisa, só a sintaxe que muda, e ao invés de gerar HTML, ele gera código C# (ou VB se dependendo da sua linguagem de preferência). Caso queira saber mais sobre a sintaxe dos T4 Templates, veja aqui na documentação da MSDN.

Como exemplo prático, criei um template customizado para a View Index, e substitui a <table> padrão por uma gerada utilizando o Helper WebGrid.

O código T4 ficou assim:

Antes:

   1: <table>

   2:     <tr>

   3:         <th></th>

   4: <#

   5: List<ModelProperty> properties = GetModelProperties(Model.ViewDataType, true);

   6: foreach (ModelProperty property in properties) {

   7:     if (!property.IsPrimaryKey && !property.IsForeignKey) {

   8: #>

   9:         <th>

  10:             <#= property.Name #>

  11:         </th>

  12: <#

  13:     }

  14: }

  15: #>

  16:     </tr>

  17:  

  18: @foreach (var item in Model) {

  19:     <tr>

  20: <# if (!String.IsNullOrEmpty(Model.PrimaryKeyName)) { #>

  21:         <td>

  22:             @Html.ActionLink("Edit", "Edit", new { id=item.<#= Model.PrimaryKeyName #> }) |

  23:             @Html.ActionLink("Details", "Details", new { id=item.<#= Model.PrimaryKeyName #> }) |

  24:             @Html.ActionLink("Delete", "Delete", new { id=item.<#= Model.PrimaryKeyName #> })

  25:         </td>

  26: <# } else { #>

  27:         <td>

  28:             @Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) |

  29:             @Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ }) |

  30:             @Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })

  31:         </td>

  32: <# } #>

  33: <#  

  34: foreach (ModelProperty property in properties) {

  35:     if (!property.IsPrimaryKey && !property.IsForeignKey) {

  36: #>

  37:         <td>

  38:             @<#= property.ValueExpression.Replace("Model.", "item.") #>

  39:         </td>

  40: <#

  41:     }

  42: }

  43: #>

  44:     </tr>

  45: }

  46:  

  47: </table>

Depois

   1: @{

   2:     var grid = new WebGrid(Model);

   3: }

   4:  

   5: <# List<ModelProperty> properties = GetModelProperties(Model.ViewDataType, true); #>

   6:  

   7: @grid.GetHtml(

   8:     tableStyle: "webgrid",

   9:     rowStyle: "webgrid-row",

  10:     alternatingRowStyle: "webgrid-alternaterow",

  11:     headerStyle: "webgrid-header",

  12:     columns: grid.Columns(

  13:         <# foreach(var property in properties) { #>

  14:             grid.Column("<#= property.Name #>", "<#= property.Name #>", format:@<span>@item.<#= property.Name #></span>),

  15:         <# } #>

  16:     )

  17: )

Você pode ver que estamos alternando entre código C# utilizando a ViewEngine Razor (como na linha 1 até 3), e logo em seguida, trocamos para um bloco <# #> (linha 5). Aqui, estamos executando um código que irá me retornar todas as proprieades do tipo que estamos “Scaffoldando”. Logo em seguida, voltamos para código comum, e na linha 13, executamos um foreach para gerarmos as colunas do WebGrid, e definimos o nome da coluna e seu Header baseado no nome da propriedade, e o valor no item da tabela (<#= property.Name #>). Esse bloco irá ser substituído no arquivo gerado pelo nome da propriedade (por exemplo, Email).

Quando executamos o Scaffolder agora, o código gerado agora utiliza o WebGrid para exibir os dados (neste caso, há uma classe com as propriedades ID, Name e Email:

   1: @{

   2:     var grid = new WebGrid(Model);

   3: }

   4:  

   5:  

   6: @grid.GetHtml(

   7:     tableStyle: "webgrid",

   8:     rowStyle: "webgrid-row",

   9:     alternatingRowStyle: "webgrid-alternaterow",

  10:     headerStyle: "webgrid-header",

  11:     columns: grid.Columns(

  12:                     grid.Column("ID", "ID", format:@<span>@item.ID</span>),

  13:                     grid.Column("Name", "Name", format:@<span>@item.Name</span>),

  14:                     grid.Column("Email", "Email", format:@<span>@item.Email</span>)

  15:             )

  16: )

Repare que os lugares onde haviam os blocos <#= #> agora há o nome das propriedades do meu Model, que foi gerado pelo T4 template.

Voce pode baixar o código fonte da aplicação de exemplo no link abaixo:

https://skydrive.live.com/embedicon.aspx/Blog/CustomScaffoldingTemplates.zip?cid=1498c467c14dc20b&sc=documents

Agora voce poderá customizar o código gerado pelo Scaffolder, poderá criar Scaffolders para View Engines diferentes, como NHaml, Repositórios que não utilizam Entity Framework, etc..

Abraços

Breno

Written by Breno Ferreira

20/07/2011 at 20:11

Posted in Dev

Tagged with , , ,