Este artigo foi traduzido por máquina.

Modelos de T4

Gerenciando a complexidade nas soluções de geração de código T4

Peter Vogel

 

Em meu artigo, "Reduzindo a barreiras para código geração com T4" na edição de Abril da MSDN Magazine (msdn.microsoft.com/magazine/hh882448), descrevi como a Microsoft Text Template Transformation Toolkit (T4) torna muito mais fácil para os desenvolvedores criar soluções geração de código (e como eles podem começar a reconhecer oportunidades de geração de código). No entanto, tal como acontece com qualquer ambiente de programação, T4 soluções podem crescer em soluções complexas e monolíticas que não podem ser mantidas ou estendidas. Evitar que o destino requer reconhecendo as várias maneiras que pode ser soluções de geração de código refatorado e integrado no código em soluções de T4. E isso exige algum conhecimento do processo de geração de código T4.

T4 Processo de geração de código

O coração do processo de geração de código T4 é o motor de T4, que aceita um modelo T4 consistindo de código clichê, blocos de controle, recursos de classe e das directivas. Partir dessas entradas, o mecanismo cria uma classe temporária (a "classe de transformação gerado") que herda da classe de TextTransformation de Microsoft. Um domínio de aplicativo, em seguida, é criado e, nesse domínio, a classe de transformação gerado é compilada e executada para produzir a saída, que pode ser qualquer coisa de uma página HTML para um programa c#.

O timbre e os blocos de Controlarar no modelo são incorporados em um único método (chamado TransformText) na classe gerado transformação. No entanto, o código na classe apresenta (entre < # + … # > delimitadores) não são colocados nesse método — código de recurso de classe é adicionado à classe de transformação gerado fora de quaisquer métodos cria o processo T4. Para adicionar um ArrayList, em meu artigo anterior, por exemplo, eu usei um bloco de recurso de classe — declarada fora de qualquer método — para a classe de transformação gerado. Então eu acessei esse ArrayList em blocos de controle da minha geração de código como parte do processo de geração do código. Além de adicionar campos, como ArrayList, recursos de classe também podem ser usados para adicionar métodos privados que podem ser chamados de seus blocos de código.

O motor é o coração do processo, mas dois outros componentes também participarem no processo de geração de código T4: o processador a directiva e o host. Directivas fornecem uma maneira baseada no parâmetro de adicionar código ou caso contrário controlar o processo. Por exemplo, a diretiva de Import T4 tem um parâmetro de Namespace para a criação de uma instrução using ou Imports na classe gerado transformação; Incluir diretriz tem um parâmetro do arquivo para recuperar texto de outro arquivo e adicioná-lo para a classe de transformação gerado. O processador a Directiva padrão manipula as directivas que vêm com o T4.

O processo de geração de código T4 também precisa de um host para integrar o processo com o ambiente que o motor está sendo executado no. O host, por exemplo, fornece um conjunto padrão de módulos (assemblies) e namespaces no motor para que não todos os assemblies do código da classe gerado transformação precisa tem que ser especificado no modelo. Quando solicitado pelo motor ou o processador a directiva, o host adiciona referências a módulos (assemblies); Recupera (e às vezes lê) arquivos para o motor; e pode mesmo recuperar processadores a Directiva personalizados ou fornecer valores padrão para as directivas cujos parâmetros foram omitidos. O host também fornece o AppDomain Classe gerado transformação executa e exibe quaisquer erros e mensagens de alerta geradas pelo mecanismo de. Visual Studio pode hospedar o mecanismo de T4 (usando a ferramenta personalizada TextTemplatingFileGenerator), como pode o utilitário de linha de comando TextTransform que processa modelos T4 fora do Visual Studio.

Como um exemplo deste processo, tomar olhar o modelo T4 com uma combinação de código estático, blocos de controle e uma classe de recurso mostrado na Figura 1.

Figura 1 exemplo de modelo de T4

<#@ template language="VB" #>
public partial class ConnectionManager
{
<#
  For Each conName As String in Connections
#>
  private void <#= conName #>(){}
<#
  Next
#>
<#+
  Private Function GetFormattedDate() As String
    Return DateTime.Now.ToShortDateString()
  End Function
#>

A classe de transformação gerado ficaria algo como o que é mostrado na Figura 2.

Figura 2 A classe de transformação gerado

Public Class GeneratedTextTransformation
  Inherits Microsoft.VisualStudio.TextTemplating.TextTransformation
  Public Overrides Function TransformText() As String
    Me.Write("public partial class ConnectionManager{")
    For Each conName As String in Connections
      Me.Write("private void ")
      Me.Write(Me.ToStringHelper.ToStringWithCulture(conName))
      Me.Write("(){}")    
    Next
  End Function
  Private Function GetFormattedDate() As String
    Return DateTime.Now.ToShortDateString()
  End Fuction
End Class

Dada essa descrição do processo de geração de código T4, você pode refatorar potencialmente monolíticas soluções em componentes mais passível de manutenção usando qualquer uma dessas três opções:

  1. Recursos de classe
  2. Estender a classe base de TextTransformation
  3. Processadores a Directiva personalizados

Esses mecanismos também permitem que você reutilize o código através de várias soluções de geração de código. Vou usar um estudo de caso trivial simple para demonstrar estas opções: Adicionar um aviso de copyright para o código gerado. O "meta-ness" de escrever código para gerar o código cria suficiente complexidade sem um estudo de caso complicado de separação — você pode fazer isso por conta própria.

Há, pelo menos, uma outra opção que eu não estou indo para discutir: desenvolvendo seu próprio host assim que você pode chamar recursos específicos de anfitrião do seu modelo de T4. Criar um novo host é realmente necessário somente se você pretende chamar T4 processamento de fora do Visual Studio e não quiser usar a ferramenta de linha de comando TextTransform.

Recursos de classe

Classe recursos fornecem a maneira mais fácil de reduzir a complexidade no processo de T4 e reutilização de código. Recursos de classe permitem que você encapsular partes do processo de geração de código para métodos que você pode chamar o método TransformText que forma o "principal" do seu código. Você também pode aproveitar a incluir diretriz reutilizar recursos da classe através de várias soluções.

O primeiro passo para usar um recurso de classe é adicionar um arquivo de T4 para seu projeto que contém os recursos de classe você deseja reutilizar em várias soluções de geração de código, colocadas entre o T4 < # + … # > delimitadores. Isso pode incluir métodos, propriedades e campos. Os arquivos de modelo também podem conter recursos T4-específicos, como as directivas. Porque seu arquivo ainda for um T4, ele irá gerar código que será compilado em seu aplicativo, assim que você deve suprimir a geração de código para o arquivo. A maneira mais fácil de fazer isso é limpar a propriedade do ferramenta Personalizar seus arquivos de recurso de classe. Este exemplo define um método chamado ReturnCopyright como um recurso de classe, escrito em Visual Basic:

<#+
 Public Function ReturnCopyright() As String
   Return "Copyright by PH&V Information Services, 2012"
 End Function
#>

Depois de definir uma característica de classe, você pode adicioná-lo a um modelo usando o incluir diretriz e usar a funcionalidade de um Controlarar de bloquear em um modelo de T4. O próximo exemplo, o que pressupõe que o recurso de classe anterior foi definido em um arquivo chamado CopyrightFeature.tt, usa o método ReturnCopyright como uma expressão:

<#@ Template language="VB"   #>
<#@ Output extension=".generated.cs" #>
<#= ReturnCopyright() #>
<#@ Include file="CopyrightFeature.tt" #>

Como alternativa, você pode simplesmente use a sintaxe de clichê normal de T4 para gerar um código semelhante:

<#+
  Public Function ReturnCopyright() As String
#>
  Copyright by PH&V Information Services, 2012
<#+
  End Function
#>

Também você pode usar os métodos Write e WriteLine dentro de sua característica de classe para gerar o código, como esse recurso de classe faz:

<#+
  Public Sub WriteCopyright()
    Write("Copyright by PH&V Information Services, 2012")
  End Function
#>

Estas duas abordagens usaria o seguinte código para chamar o método uma vez uma incluir diretriz adicionou ao modelo:

<# WriteCopyright() #>

Você pode parametrizar recursos de classe usando argumentos de função normal e daisy-chain juntos usando a diretiva Include no T4 arquivos que são incluídos em outros arquivos de T4 para construir uma biblioteca bem estruturada, reutilizável.

Comparado a outras soluções, usar recursos de classe tem pelo menos uma vantagem e uma desvantagem. Você experimentar o benefício que você desenvolver sua solução de geração de código: características de classe (e inclui em geral) não envolvem assemblies compilados. Por motivos de desempenho, o motor de T4 pode bloquear os assemblies que ele usa quando ele carrega a classe de transformação gerado em seu domínio de aplicativo. Isto significa que como você testar e modificar código compilado, você pode achar que você não pode substituir os módulos relevantes sem desligar e reiniciar Visual Studio. Isso não vai acontecer com os recursos de classe. Observe que isso foi abordado no Visual Studio 2010 SP1, que já não bloqueia assemblies.

Os desenvolvedores podem encontrar a desvantagem, no entanto, quando eles usam sua solução de geração de código: Eles devem adicionar não somente seu modelo T4 para seu projeto, mas também todos os seus T4 incluir arquivos de suporte. Este é um cenário onde você pode considerar a criação de um modelo de Visual Studio que contém todos os arquivos de T4 um desenvolvedor precisa para a sua solução de geração de código para que eles podem ser adicionados como um grupo. Ele também faria sentido separar seus incluir arquivos em uma pasta dentro da solução. O exemplo a seguir usa a diretiva Include para adicionar um arquivo que contém recursos de classe de uma pasta chamada modelos:

<#@ Include file="Templates\classfeatures.tt" #>

Não, evidentemente, está limitado a apenas usando recursos de classe em arquivos de inclusão — você pode incluir um conjunto de blocos de controle e o texto que você deseja integrar a classe de transformação gerado TransformText Método arbitrário. No entanto, como evitar GoTos, usando membros bem definidos em incluir arquivos ajuda a gerenciar a complexidade conceitual de sua solução.

Estender a classe de TextTransformation

Substituir o TextTransformation classe com uma classe de seu próprio permite incorporar funcionalidade personalizada em métodos nessa classe que pode ser chamado na classe gerado transformação. Estender a classe TextTransformation é uma boa opção quando você tem código que será usado em muitos (ou todos) de suas soluções de geração de código: essencialmente, você fator que o código fora de suas soluções e para o mecanismo de T4.

O primeiro passo para estender a classe TextTransformation é criar uma classe abstrata que herda da classe:

public abstract class PhvisT4Base:
  Microsoft.VisualStudio.TextTemplating.TextTransformation
{
}

Se você não tiver o Microsoft.VisualStudio.TextTemplating DLL que contém a classe de TextTransformation, baixe o SDK para sua versão do Visual Studio antes de adicionar a referência.

Dependendo da versão do T4, talvez você precise fornecer uma implementação do método TransformText como parte da herança da TextTransformation. Se assim, o método deve retornar a Cadeia de caracteres que contém a saída da classe gerado transformação, que realiza-se na classe de TextTransformation geração­propriedade de ambiente. A substituir do método TransformText, se necessário, deve olhar como este:

public override string TransformText()
{
  return this.GenerationEnvironment.ToString();
}

Com recursos de classe, você tem duas opções para adicionar código à classe de transformação gerado. Você pode criar métodos que retornam valores de Cadeia de caracteres, como neste exemplo:

protected string Copyright()
{
  return @"Copyright PH&V Information Services, 2012";
}

Esse método pode ser usado em uma expressão ou com o T4 Write ou WriteLine métodos, como nos seguintes exemplos:

<#= CopyRight() #>
<#  WriteLine(CopyRight()); #>

Como alternativa, você pode usar os métodos de Write ou WriteLine da classe base TextTransformation diretamente nos métodos de que adicionar a classe base de TextTransformation, como neste exemplo:

protected void Copyright()
{
  base.Write(@"Copyright PH&V Information Services, 2012");
}

Esse método pode então ser chamado de um bloco de controle, como este:

<# CopyRight(); #>

A etapa final em substituir a classe de TextTransformation padrão é especificar no seu modelo T4 que a classe de transformação gerado é herdar sua nova classe. Você pode fazer isso com o parâmetro de Inherits atributo modelo:

<#@ Template language="C#" inherits="PhvT4Utils.PhvisT4Base" #>

Você também deve adicionar ao seu modelo T4 um Diretiva de assembly que referencia a DLL que contém a classe base, usando o nome do caminho físico completo para a DLL:

<#@ Assembly name="C:\T4Support\PhvT4Utils.dll" #>

Como alternativa, você pode adicionar seu classe base para o global assembly cache (GAC).

No Visual Studio 2010, você pode usar o ambiente e as variáveis de macro para simplificar o caminho para a DLL. Por exemplo, durante o desenvolvimento, você pode usar a macro de $(ProjectDir) para referenciar a DLL que contém sua classe base:

<#@ Assembly name="$(ProjectDir)\bin\PhvT4Utils.dll" #>

Em tempo de execução, supondo que você instala os arquivos de classe para a pasta arquivos de programas, você pode usar o ambiente variável % programfiles % em versões de 32 bits do Windows ou, em versões de 64 bits, % ProgramW6432% para a pasta arquivos de programas ou em % ProgramFiles % para os arquivos de programa (x86) pasta. Este exemplo assume que a DLL tenha sido colocada em C:\Program Files\PHVIS\T4Tools em uma versão de 64 bits do Windows:

<#@ Assembly name="%ProgramW6432%\PHVIS\T4Tools\PHVT4Utils.DLL" #>

Como com outras soluções com código compilado, Visual Studio pode bloquear a DLL que contém seu código durante a execução da classe gerado transformação. Se isso acontecer enquanto você estiver desenvolvendo sua classe TextTransformation, você precisará reiniciar Visual Studio — e recompilar seu código — antes que você possa fazer mais mudanças.

Barra lateral: Erros e avisos

Qualquer processo robusto fornece feedback sobre seu progresso. Em um recurso de classe ou ao estender a classe TextTransformation, você pode relatar problemas na execução da classe gerado transformação Adicionando chamadas para a classe de TextTransformation erro e métodos de aviso. Ambos os métodos aceitar um simples cadeia de caracteres de entrada e passar a Cadeia de caracteres para o host de Microsoft Text Template Transformation Toolkit (T4) (o host é responsável por decidir como exibir as mensagens). No Visual Studio, as mensagens aparecem na janela lista de erros.

Este exemplo reporta um erro em um recurso de classe:

< # = GetDatabaseData("") # >
< # + private string GetDatabaseData (string ConnectionString)
{
se (ConnectionString = = "")
    {
base.Erro ("nenhuma conexão seqüência fornecida.");
    }
Return "";
    }
...

Em um recurso de classe de erro e aviso não podem ser usado para relatar problemas no processo de T4, apenas sobre erros que ocorrem quando a classe de transformação gerada executa. Para colocá-lo outra forma, em uma classe recurso, mensagens de erro e de aviso aparecerá somente se a classe de transformação gerados pode ser montada corretamente de seu arquivo de T4, compilado e executado.

Se você estiver criando um personalizado processador a directiva, você ainda pode integrar com o erro de T4 processo de geração de relatórios adicionando compilador­objetos de erro para a classe de erros propriedade. O objeto de CompilerError suporta passando vários pedaços de informações sobre o erro (incluindo linha número e nome do arquivo), mas este exemplo simplesmente define a propriedade ErrorText:

System.CodeDom.Compiler.CompilerError err = new
System.CodeDom.Compiler.CompilerError();
Err.ErrorText = "Missing parâmetro a Directiva";
isso.Errors.Add(Err);*
— B.L.*

Processadores de directiva personalizado

A forma mais poderosa e flexível para gerenciar a complexidade do processo de geração de código é usar processadores a Directiva personalizados. Entre outras características, a Directiva processadores podem adicionar referências para a classe de transformação gerado e inserir código em métodos que são executados antes e depois da classe gerado transformação TransformText método. As directivas são, também, os desenvolvedores a usar: eles só têm que fornecer valores para parâmetros da directiva e, em seguida, usar os membros que disponibilizar as directivas.

A principal questão com processadores a directiva é que você deve adicionar uma chave no registro do Windows para que um desenvolvedor usar seu processador. Aqui, novamente, vale a pena considerar a embalagem até sua solução T4 para que a entrada do registro pode ser feita automaticamente. Em versões de 32 bits do Windows, a chave deve ser:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\
  visualstudioversion\TextTemplating\DirectiveProcessors

Para versões de 64 bits do Windows, a chave é:

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\
  VisualStudio\10.0\TextTemplating\DirectiveProcessors

O nome da chave é o nome de sua classe de processador a directiva, que, seguindo a Convenção de nomenclatura do Microsoft, deve terminar com "DirectiveProcessor." Você terá as seguintes subchaves:

  • Padrão: Vazio ou uma descrição da sua directiva.
  • Classe: O nome completo de sua classe no formato ClassName.
  • Montagem/CodeBase: O nome da sua DLL se você colocou DLL da directiva no GAC (Assembly) ou o caminho completo para a DLL da Directiva (CodeBase).

Ao desenvolver seu processador, se você não conseguir esta entrada direita, Visual Studio irá gerar um erro que foi incapaz de encontrar seu processador a directiva ou resolver seu tipo. Depois de corrigir todos os erros em suas entradas de registro, talvez você precise reiniciar Visual Studio para pegar as alterações.

Para usar o seu processador a directiva, um desenvolvedor adiciona uma directiva com qualquer nome um modelo T4 e vincula a directiva para o processador através do parâmetro de processador da Directiva (que faz referência a chave de registro do Windows). Quando executa o modelo T4, seu processador a directiva é passado o nome da directiva, que o desenvolvedor utilizados juntamente com parâmetros o desenvolvedor especificado.

Este exemplo vincula uma diretriz chamada Copyright para um processador chamado CopyrightDirectiveProcessor e inclui um parâmetro chamado ano:

<#@ Copyright Processor="CopyrightDirectiveProcessor" Year=”2012” #>

Com uma característica de classe, a saída de um processador a directiva é adicionada para a classe de transformação gerados fora do método TransformText. Como resultado, você usará seu processador para adicionar novos membros para a classe de transformação gerado que devel­desenvolvedores podem usar em seus blocos de controle de modelo. A Directiva de exemplo anterior pode ter adicionado uma propriedade ou uma variável de Cadeia de caracteres que um desenvolvedor poderia usar em uma expressão, como este:

<#= Copyright #>

Naturalmente, o próximo passo é criar uma directiva processo para processo esta directiva.

Criando um personalizado Directiva processador

T4 a Directiva processadores herdam da classe Microsoft.VisualStudio.TextTemplating.DirectiveProcessor (baixar o SDK do Visual Studio para obter a biblioteca de TextTemplating). Do seu método de GetClassCodeForProcessingRun, seu processador a directiva deve, retornar o código será adicionado para a classe de transformação gerado. No entanto, antes de chamar o método GetClassCodeForProcessingRun, o mecanismo de T4 irá chamar IsDirective do processador­suporte para o Método (passando o nome da sua directiva) e o método de ProcessDirective (passando o nome da directiva e os valores dos seus parâmetros). O método de IsDirectiveSupported, você deve retornar false se a directiva não deve ser executado e caso contrário, true.

Porque o método de ProcessDirective é passado todas as informações sobre a directiva, que é onde você normalmente cria o código que irá retornar o GetClassCodeForProcessingRun. Você pode extrair os valores dos parâmetros especificados na directiva, lendo-os do segundo parâmetro do Método (chamado de argumentos). Esse código no método ProcessDirective, olha para um parâmetro chamado ano e usa-o para construir uma Cadeia de caracteres que contém uma declaração de variável. A Cadeia de caracteres é retornada de GetClassCodeForProcessingRun:

string copyright = string.Empty;
public override void ProcessDirective(
  string directiveName, IDictionary<string, string> arguments)
{
  copyright = "string copyright " +
              "= \"Copyright PH&V Information Services, " +
              arguments["Year"] +"\";";
}
public override string GetClassCodeForProcessingRun()
{
  return copyright;
}

Seu processador a directiva também pode adicionar referências e usando /­instruções de importação para a classe de transformação gerado para apoiar o código adicionado através de GetClassCodeForProcessingRun. Para adicionar referências para a classe de transformação gerado, você só precisará retornar os nomes das bibliotecas em uma matriz de Cadeia de caracteres do método GetReferencesForProcessingRun. Se, por exemplo, o código que está sendo adicionado à classe gerado transformação necessária classes do namespace System. XML, use código como este:

public override string[] GetReferencesForProcessingRun()
{
  return new string[] {"System.Xml"};
}

Da mesma forma, você pode especificar espaços para nome a ser adicionado à classe de transformação gerados (como usando ou instruções de Imports), retornar uma matriz de Cadeia de caracteres do método GetImportsForProcessingRun.

A classe de transformação do código gerado também inclui pré e pós-inicialização métodos que são chamados antes que o método de TransformText. Você pode retornar o código será adicionado para esses métodos dos métodos GetPreInitializationCodeForProcessingRun e GetPostInitializationCodeForProcessingRun.

Como você Depurar, lembre-se que usar executar Personalizar ferramenta não faz com que Visual Studio para criar uma solução. Conforme você fizer alterações para seu processador a directiva, você precisará criar uma solução para pegar as alterações mais recentes antes de executar seu modelo. E, novamente, porque T4 bloqueia os assemblies que ele usa, você pode achar que você tem que reiniciar Visual Studio e recompilar seu código repetidos.

T4 reduz significativamente as barreiras para integrar o seu kit de ferramentas de geração de código. No entanto, como com qualquer outro aplicativo, você precisa considerar como você vai arquitetar soluções completas para oferecer suporte a extensibilidade e capacidade de manutenção. O processo de geração de código T4 fornece vários locais — cada um com seus próprios custos e os benefícios — onde você pode refatorar seu código e inseri-lo no processo. Não importa qual mecanismo que você usar, você garantirá que suas soluções de T4 são arquitetadas.

Barra lateral: Usando T4 em tempo de execução

Você pode obter um vislumbre do que a classe gerada transformação parece ser por aproveitar modelos de texto processado, que permitem que você gere texto em tempo de execução. Compilar ou executar os resultados do Microsoft Text Template Transformation Toolkit (T4) de geração de código em tempo de execução provavelmente não é algo que a maioria dos desenvolvedores gostaria de abordar. No entanto, se você precisa de várias versões "semelhantes mas diferentes" de um XML ou um documento HTML (ou algum outro texto), modelos de texto processado permitem usar T4 para gerar esses documentos em tempo de execução.

Como com outras soluções de T4, é o primeiro passo para usar T4 em tempo de execução adicionar um arquivo de T4 para seu projeto na caixa de diálogo Novo Item. Mas, em vez de adicionar um arquivo de modelo de texto, você adiciona um modelo de texto processado (também listado na caixa de diálogo Visual Studio Novo Item). Um modelo de texto processado é idêntico a um arquivo de modelo de texto T4 padrão exceto que a propriedade do ferramenta Personalizar é definida como TextTemplatingFileProcessor em vez da TextTemplatingFileGenerator usual.

Ao contrário com um modelo de texto, o arquivo filho que contém o código gerado de um modelo de texto processado não contém a saída de código pela classe transformação gerada. Em vez disso, o arquivo mantém algo que parece muito com uma de suas classes de transformação gerado: uma classe com o mesmo nome como o arquivo de modelo de texto processado com um método chamado TransformText. Chamar esse método de TransformText em tempo de execução retorna, como uma Cadeia de caracteres, o que você esperaria encontrar no arquivo de código de um modelo de T4: o código gerado. Assim, para um arquivo de modelo de texto processado chamado GenerateHTML, você deve recuperar o texto gerado usando código como este em tempo de execução:

GenerateHTML HtmlGen = novo GenerateHTML();
Cadeia de caracteres html = HtmlGen.TransformText();*
— B.L.*

 

Peter Vogel é um principal em PH & serviços de informações da V. Seu último livro foi "geração de código prático em.NET"(Addison-Wesley Professional, 2010). PH & serviços de informações da v especializa-se em facilitar a concepção de arquiteturas baseadas no serviço e na integração.Tecnologias NET para essas arquiteturas. Além de sua prática de consultoria, Vogel escreveu curso de design de arquitetura orientada a serviço da aprendizagem árvore internacional, ensinado em todo o mundo.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Gareth Jones