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.
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.
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!