Compartilhar via


Geração de texto de tempo de execução com modelos de texto T4

Você pode gerar cadeias de caracteres de texto em seu aplicativo em tempo de execução usando modelos de texto de runtime do Visual Studio. O computador em que o aplicativo é executado não precisa ter o Visual Studio. Às vezes, modelos de runtime são chamados de "modelos de texto pré-processados" porque, em tempo de compilação, o modelo gera o código executado em tempo de execução.

Cada modelo é uma combinação do texto como ele aparecerá na cadeia de caracteres gerada e de fragmentos do código do programa. Os fragmentos do programa fornecem valores para as partes variáveis da cadeia de caracteres e também controlam partes condicionais e repetidas.

Por exemplo, o modelo a seguir pode ser usado em um aplicativo que cria um relatório HTML.

<#@ template language="C#" #>
<html><body>
<h1>Sales for Previous Month</h2>
<table>
    <# for (int i = 1; i <= 10; i++)
       { #>
         <tr><td>Test name <#= i #> </td>
             <td>Test value <#= i * i #> </td> </tr>
    <# } #>
 </table>
This report is Company Confidential.
</body></html>

Observe que o modelo é uma página HTML na qual as partes variáveis foram substituídas pelo código do programa. Você pode iniciar o design dessa página escrevendo um protótipo estático da página HTML. Em seguida, você pode substituir a tabela e outras partes variáveis pelo código do programa que gera o conteúdo que varia de uma ocasião para outra.

Usar um modelo no aplicativo torna mais fácil ver a forma final da saída do que você poderia ver, por exemplo, em uma longa série de instruções de gravação. Fazer alterações na forma da saída é mais fácil e confiável.

Criando um modelo de texto em tempo de execução em qualquer aplicativo

Para criar um modelo de texto em tempo de execução

  1. No Gerenciador de Soluções, no menu de atalho do projeto, escolha Adicionar>Novo Item.

  2. Na caixa de diálogo Adicionar Novo Item, selecione Modelo de Texto de Runtime. (No Visual Basic, procure em Itens Comuns>Geral.)

  3. Digite um nome para o arquivo de modelo.

    Observação

    O nome do arquivo de modelo será usado como um nome de classe no código gerado. Portanto, ele não deve ter espaços nem pontuação.

  4. Escolha Adicionar.

    Um arquivo é criado com a extensão .tt. A propriedade Ferramenta Personalizada é definida como TextTemplatingFilePreprocessor. Ela contém as seguintes linhas:

    <#@ template language="C#" #>
    <#@ assembly name="System.Core" #>
    <#@ import namespace="System.Linq" #>
    <#@ import namespace="System.Text" #>
    <#@ import namespace="System.Collections.Generic" #>
    
  5. Adicione uma referência para o pacote NuGet System.CodeDom.

Convertendo um arquivo existente em um modelo em tempo de execução

Uma boa maneira de criar um modelo é converter um exemplo existente da saída. Por exemplo, se o aplicativo gerar arquivos HTML, você poderá começar criando um arquivo HTML sem formatação. Verifique se ele funciona corretamente e se sua aparência está correta. Em seguida, inclua-o em seu projeto do Visual Studio e converta-o em um modelo.

Para converter um arquivo de texto existente em um modelo de tempo de execução

  1. Inclua o arquivo em seu projeto do Visual Studio. No Gerenciador de Soluções, no menu de atalho do projeto, escolha Adicionar>Item Existente.

  2. Defina a propriedade Ferramentas Personalizadas do arquivo como TextTemplatingFilePreprocessor. No Gerenciador de Soluções, no menu de atalho do arquivo, escolha Propriedades.

    Observação

    Se a propriedade já estiver definida, verifique se ela é TextTemplatingFilePreprocessor e não TextTemplatingFileGenerator. Isso pode acontecer se você incluir um arquivo que já tenha a extensão .tt.

  3. Altere a extensão de nome de arquivo para .tt. Embora essa etapa seja opcional, ela ajuda a evitar abrir o arquivo em um editor incorreto.

  4. Remova quaisquer espaços ou pontuação da parte principal do nome do arquivo. Por exemplo, "My Web Page.tt" estaria incorreto, mas "MyWebPage.tt" está correto. O nome do arquivo será usado como um nome de classe no código gerado.

  5. Insira a linha a seguir no início do arquivo. Se estiver trabalhando em um projeto do Visual Basic, substitua "C#" por "VB".

    <#@ template language="C#" #>

  6. Adicione uma referência para o pacote NuGet System.CodeDom.

O conteúdo do modelo de tempo de execução

Diretiva de modelo

Mantenha a primeira linha do modelo como era quando você criou o arquivo:

<#@ template language="C#" #>

O parâmetro de linguagem dependerá da linguagem do projeto.

Conteúdo sem formatação

Edite o arquivo .tt para conter o texto que você deseja que seu aplicativo gere. Por exemplo:

<html><body>
<h1>Sales for January</h2>
<!-- table to be inserted here -->
This report is Company Confidential.
</body></html>

Código do programa inserido

Você pode inserir o código do programa entre <# e #>. Por exemplo:

<table>
    <# for (int i = 1; i <= 10; i++)
       { #>
         <tr><td>Test name <#= i #> </td>
             <td>Test value <#= i * i #> </td> </tr>
    <# } #>
 </table>

Observe que as instruções são inseridas entre <# ... #> e as expressões e são inseridas entre <#= ... #>. Para obter mais informações, consulte Gravando um Modelo de Texto T4.

Usando o modelo

O código criado a partir do modelo

Quando você salva o arquivo .tt, um arquivo .cs ou .vb secundário é gerado. Para ver esse arquivo no Gerenciador de Soluções, expanda o nó de arquivo .tt. Em um projeto do Visual Basic, primeiro escolha Mostrar Todos os Arquivos na barra de ferramentas do Gerenciador de Soluções.

Observe que o arquivo secundário contém uma classe parcial que contém um método chamado TransformText(). Você pode chamar esse método de seu aplicativo.

Gerando texto em tempo de execução

No código do aplicativo, você pode gerar o conteúdo do modelo usando uma chamada como esta:

MyWebPage page = new MyWebPage();
String pageContent = page.TransformText();
System.IO.File.WriteAllText("outputPage.html", pageContent);

Para colocar a classe gerada em um namespace específico, defina a propriedade Namespace da Ferramenta Personalizada do arquivo de modelo de texto.

Depurando modelos de texto de tempo de execução

Depure e teste modelos de texto de tempo de execução da mesma forma que o código comum.

Você pode definir um ponto de interrupção em um modelo de texto. Se você iniciar o aplicativo no modo de depuração do Visual Studio, poderá percorrer o código e avaliar expressões de inspeção da maneira usual.

Passando parâmetros no construtor

Normalmente, um modelo deve importar alguns dados de outras partes do aplicativo. Para facilitar isso, o código criado pelo modelo é uma classe parcial. Você pode criar outra parte da mesma classe em outro arquivo em seu projeto. Esse arquivo pode incluir um construtor com parâmetros, propriedades e funções que podem ser acessados pelo código inserido no modelo e pelo restante do aplicativo.

Por exemplo, você pode criar um arquivo separado MyWebPageCode.cs:

partial class MyWebPage
{
    private MyData m_data;
    public MyWebPage(MyData data) { this.m_data = data; }}

No arquivo de modelo MyWebPage.tt, você pode escrever:

<h2>Sales figures</h2>
<table>
<# foreach (MyDataItem item in m_data.Items)
   // m_data is declared in MyWebPageCode.cs
   { #>
      <tr><td> <#= item.Name #> </td>
          <td> <#= item.Value #> </td></tr>
<# } // end of foreach
#>
</table>

Para usar este modelo no aplicativo:

MyData data = ...;
MyWebPage page = new MyWebPage(data);
String pageContent = page.TransformText();
System.IO.File.WriteAllText("outputPage.html", pageContent);

Parâmetros de construtor no Visual Basic

No Visual Basic, o arquivo separado MyWebPageCode.vb contém:

Namespace My.Templates
  Partial Public Class MyWebPage
    Private m_data As MyData
    Public Sub New(ByVal data As MyData)
      m_data = data
    End Sub
  End Class
End Namespace

O arquivo de modelo pode conter:

<#@ template language="VB" #>
<html><body>
<h1>Sales for January</h2>
<table>
<#
    For Each item In m_data.Items
#>
    <tr><td>Test name <#= item.Name #> </td>
      <td>Test value <#= item.Value #> </td></tr>
<#
    Next
#>
</table>
This report is Company Confidential.
</body></html>

O modelo pode ser invocado passando o parâmetro no construtor:

Dim data = New My.Templates.MyData
    ' Add data values here ....
Dim page = New My.Templates.MyWebPage(data)
Dim pageContent = page.TransformText()
System.IO.File.WriteAllText("outputPage.html", pageContent)

Passando dados em propriedades de modelo

Uma maneira alternativa de passar dados para o modelo é adicionar propriedades públicas à classe de modelo em uma definição de classe parcial. Seu aplicativo pode definir as propriedades antes de invocar TransformText().

Você também pode adicionar campos à classe de modelo em uma definição parcial. Isso permite que você passe dados entre execuções sucessivas do modelo.

Usar classes parciais para código

Muitos desenvolvedores preferem evitar escrever grandes corpos de código em modelos. Em vez disso, você pode definir métodos em uma classe parcial que tenha o mesmo nome que o arquivo de modelo. Chame esses métodos do modelo. Dessa forma, o modelo mostra mais claramente como será a cadeia de caracteres de saída de destino. Discussões sobre a aparência do resultado podem ser separadas da lógica de criação dos dados exibidos.

Assemblies e referências

Para que seu código de modelo faça referência a um .NET ou outro assembly, como System.Xml.dll, adicione-o às Referências do projeto da maneira usual.

Se você quiser importar um namespace da mesma forma que uma instrução using, poderá fazer isso com a diretiva import:

<#@ import namespace="System.Xml" #>

Essas diretivas devem ser colocadas no início do arquivo, imediatamente após a diretiva <#@template.

Conteúdo compartilhado

Se você tiver um texto compartilhado entre vários modelos, poderá colocá-lo em um arquivo separado e incluí-lo em cada arquivo no qual ele deve aparecer:

<#@include file="CommonHeader.txt" #>

O conteúdo incluído pode conter qualquer combinação de código do programa e texto sem formatação, e pode conter outras diretivas include e outras diretivas.

A diretiva include pode ser usada em qualquer lugar dentro do texto de um arquivo de modelo ou de um arquivo incluído.

Herança entre modelos de texto de tempo de execução

Você pode compartilhar conteúdo entre modelos de tempo de execução escrevendo um modelo de classe base, que pode ser abstrato. Use o parâmetro inherits da diretiva <@#template#> para fazer referência a outra classe de modelo de runtime.

Padrão de herança: fragmentos em métodos base

No padrão usado no exemplo a seguir, observe os seguintes pontos:

  • A classe base SharedFragments define métodos dentro dos blocos de recursos de classe <#+ ... #>.

  • A classe base não contém texto livre. Em vez disso, todos os blocos de texto ocorrem dentro dos métodos de recurso da classe.

  • A classe derivada invoca os métodos definidos em SharedFragments.

  • O aplicativo chama o método TextTransform() da classe derivada, mas não transforma a classe base SharedFragments.

  • As classes base e derivada são modelos de texto de runtime, ou seja, a propriedade Ferramenta Personalizada é definida como TextTemplatingFilePreprocessor.

SharedFragments.tt:

<#@ template language="C#" #>
<#+
protected void SharedText(int n)
{
#>
   Shared Text <#= n #>
<#+
}
// Insert more methods here if required.
#>

MyTextTemplate1.tt:

<#@ template language="C#" inherits="SharedFragments" #>
begin 1
   <# SharedText(2); #>
end 1

MyProgram.cs:

...
MyTextTemplate1 t1  = new MyTextTemplate1();
string result = t1.TransformText();
Console.WriteLine(result);

A saída resultante:

begin 1
    Shared Text 2
end 1

Padrão de herança: texto no corpo base

Nessa abordagem alternativa ao uso da herança de modelo, a maior parte do texto é definida no modelo base. Os modelos derivados fornecem dados e fragmentos de texto que se encaixam no conteúdo base.

AbstractBaseTemplate1.tt:

<#@ template language="C#" #>

Here is the description for this derived template:
  <#= this.Description #>

Here is the fragment specific to this derived template:
<#
  this.PushIndent("  ");
  SpecificFragment(42);
  this.PopIndent();
#>
End of common template.
<#+
  // State set by derived class before calling TextTransform:
  protected string Description = "";
  // 'abstract' method to be defined in derived classes:
  protected virtual void SpecificFragment(int n) { }
#>

DerivedTemplate1.tt:

<#@ template language="C#" inherits="AbstractBaseTemplate1" #>
<#
  // Set the base template properties:
  base.Description = "Description for this derived class";

  // Run the base template:
  base.TransformText();

#>
End material for DerivedTemplate1.

<#+
// Provide a fragment specific to this derived template:

protected override void SpecificFragment(int n)
{
#>
   Specific to DerivedTemplate1 : <#= n #>
<#+
}
#>

Código do aplicativo:

...
DerivedTemplate1 t1 = new DerivedTemplate1();
string result = t1.TransformText();
Console.WriteLine(result);

Saída resultante:

Here is the description for this derived template:
  Description for this derived class

Here is the fragment specific to this derived template:
     Specific to DerivedTemplate1 : 42
End of common template.
End material for DerivedTemplate1.

Modelos de tempo de design: se quiser usar um modelo para gerar código que se torna parte do aplicativo, consulte Geração de código em tempo de design usando modelos de texto T4.

Modelos de tempo de execução podem ser usados em qualquer aplicativo em que os modelos e seu conteúdo sejam determinados em tempo de compilação. Porém, se você quiser escrever uma extensão do Visual Studio que gera texto de modelos que mudam em tempo de execução, consulte Invocando a Transformação de Texto em uma Extensão do VS.