03 fev 2016

T4 Text Template

Desde a versão 2005 do Visual Studio, a Microsoft incluiu um framework de geração de textos dentro da IDE conhecido como T4 (acrônimo para Text Template Transformation Toolkit).

O T4 permite a produção mecânica de qualquer tipo de texto, mas é principalmente relevante para gerar códigos fontes e scripts. Além disso, ele é fácil de usar porque mistura blocos de texto puro com controles lógicos em linguagem C#.

Há duas formas de trabalhar com T4.

Uma é através do Design-time, que é composto de um arquivo .tt aonde colocamos as instruções e regras de geração, e ao salvá-lo, ele escreverá o resultado em um arquivo cuja extensão é configurável. Dependendo de sua intenção, o arquivo resultante pode ser um .cs já vinculado à solution e pronto para ser compilado como parte do projeto.

CTA-ebook-transformação-digital

A outra forma é o Runtime, que também é composto por um arquivo .tt, mas que ao invés de gerar o texto imediatamente, ele produzirá interfaces de desenvolvimento para serem utilizadas durante a execução do programa.

Vamos demonstrar como usar o T4 em Runtime para gerar classes e propriedades a partir de uma estrutura de modelo de dados.

Primeiramente, crie ou abra o seu projeto no Visual Studio e selecione Add > New Item… no menu de opções. O tipo de item a ser escolhido é o Runtime Text Template, como pode ser visto na imagem abaixo.

TextTemplate

Em princípio o arquivo .tt só possui definições de namespaces básicos. Vamos então adicionar dois parâmetros de entradas, um chamado NamespaceBase e o outro Schemas conforme o trecho de código a seguir. Em breve, iremos implementar as estruturas desses parâmetros.

<#@ parameter name="NamespaceBase" type="System.String" #>
<#@ parameter name="Schemas" type="System.Collections.Generic.List<SchemaModel>" #>

Adicionalmente, vamos incluir o código que consuma esses parâmetros e produza o texto final desejado.

using System; 
<# foreach (var schema in this.Schemas) { #> 
namespace <#= this.NamespaceBase #>.<#= schema.Name #> 
{ 
<# foreach (var table in schema.Tables) { #> 
    public class <#= table.Name #>  
    { 
<# foreach (var column in table.Columns) { #> 
        public <#= column.Type #> <#= column.Name #> { get; set; } 
<# } #> 
    }  
<# } #> 
} 
<# } #>

Repare que a estrutura do Text Template é muito similar ao que temos na engine do ASP.NET MVC. Elementos entre os marcadores <# #> são blocos de código e <#= #> representam a escrita de valores. Tudo que estiver fora dos marcadores são interpretados como texto puro. Os controles de fluxos, como laços e condicionais também estão presentes e é possível utilizar qualquer biblioteca do .NET, desde que a declaração do respectivo namespace seja feita no começo do arquivo.

Ao salvá-lo, a interface de desenvolvimento é gerada. Vamos então implementar uma aplicação exemplo que consuma essa interface de forma a exemplificar seu funcionamento.

Começaremos montando a estrutura dos parâmetros declarados anteriormente (a definição das classes foi omitida por questão de brevidade)

var namespaceBase = "Sample"; 
var schemas = new List<SchemaModel> { 
    new SchemaModel { Name = "Sales", Tables = new List<TableModel> { 
        new TableModel { Name = "Currency", Columns = new List<ColumnModel> { 
            new ColumnModel { Name = "Code", Type = "int" }, 
            new ColumnModel { Name = "Name", Type = "string" }, 
            new ColumnModel { Name = "ModifiedDate", Type = "DateTime" }, 
        }}, 
        new TableModel { Name = "CurrencyRate", Columns = new List<ColumnModel> { 
            new ColumnModel { Name = "Id", Type = "int" }, 
            new ColumnModel { Name = "FromCurrencyCode", Type = "Currency" }, 
            new ColumnModel { Name = "ToCurrencyCode", Type = "Currency" }, 
            new ColumnModel { Name = "AverangeRate", Type = "decimal" }, 
            new ColumnModel { Name = "EndOfDayRate", Type = "decimal" }, 
        }} 
}}};

E finalmente, o código para executar o T4 Text Template em Runtime. Importante notar que o nome da classe a ser consumida é exatamente o nome do arquivo .tt. No caso, eu mantive o nome default RuntimeTextTemplate1.tt, que gerou a classe RuntimeTextTemplate1.

var template = new RuntimeTextTemplate1(); 
 
// Adiciona os parâmetros requeridos pelo template
template.Session = new Dictionary<string, object>(); 
template.Session.Add("NamespaceBase", namespaceBase); 
template.Session.Add("Schemas", schemas); 
 
template.Initialize(); 
 
// Executa a geração do texto e obtém o resultado 
var output = template.TransformText(); 
 
// Grava o conteúdo em um arquivo e o abre com notepad 
File.WriteAllText("Sample.cs", output); 
Process.Start("notepad", "Sample.cs").WaitForExit();

O resultado disso tudo são as classes e propriedades a seguir, gravadas no arquivo Sample.cs

using System; 
namespace Sample.Sales 
{ 
    public class Currency 
    { 
        public int Code { get; set; } 
        public string Name { get; set; } 
        public DateTime ModifiedDate { get; set; } 
    } 
    public class CurrencyRate 
    { 
        public int Id { get; set; } 
        public Currency FromCurrencyCode { get; set; } 
        public Currency ToCurrencyCode { get; set; } 
        public decimal AverangeRate { get; set; } 
        public decimal EndOfDayRate { get; set; } 
    } 
}

Como comentado, o T4 é bem flexível e há diversas oportunidades para usá-lo. Com pouco esforço é possível carregar XMLs com definições básicas do seu modelo de dados e produzir partes de código que são repetitivas e massantes, como acontece quando precisamos implementar DTOs. Um outro cenário interessante é gerar automaticamente stored procedures de banco de dados a partir da leitura da própria estrutura do banco. 

Ficou com alguma dúvida? Escreva nos comentários!

Daniel Henrique da Silva
About Daniel Henrique da Silva

Coder, estudante e encanado com diversas coisas.

Leave a Comment