Generación de texto en tiempo de ejecución con plantillas de texto T4

Puede generar cadenas de texto en la aplicación en tiempo de ejecución mediante plantillas de texto en tiempo de ejecución de Visual Studio. El equipo en el que se ejecuta la aplicación no necesariamente tiene que tener Visual Studio. A veces, las plantillas en tiempo de ejecución se denominan "plantillas de texto preprocesadas", ya que en tiempo de compilación, la plantilla genera código que se ejecuta en tiempo de ejecución.

Cada plantilla es una mezcla del texto, tal como aparecerá en la cadena generada, y fragmentos de código de programa. Los fragmentos del programa proporcionan valores a las partes variables de la cadena y también controlan partes condicionales y repetidas.

Por ejemplo, la plantilla siguiente se podría usar en una aplicación que crea un informe en formato 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 la plantilla es una página HTML en la que las partes de las variables se han reemplazado por código de programa. El diseño de esta página se puede iniciar escribiendo un prototipo estático de la página HTML. Después, puede reemplazar la tabla y otras partes variables por código de programa que genera el contenido que varía de una ocasión a la siguiente.

El uso de una plantilla en la aplicación facilita la visualización del formulario final de la salida en la que podría tener, por ejemplo, una larga serie de instrucciones de escritura. Realización de cambios en el formulario de la salida es más fácil y confiable.

Creación de una plantilla de texto en tiempo de ejecución en cualquier aplicación

Para crear una plantilla de texto en tiempo de ejecución

  1. En el Explorador de soluciones, en el menú contextual del proyecto, elija Agregar>Nuevo elemento.

  2. En el cuadro de diálogo Agregar nuevo elemento, seleccione Plantilla de texto en tiempo de ejecución (en Visual Basic, busque en Elementos comunes>General.)

  3. Escriba un nombre para el archivo de plantilla.

    Nota

    El nombre del archivo de plantilla se usará como nombre de clase en el código generado. Por consiguiente, no debe tener espacios ni signos de puntuación.

  4. Haga clic en Agregar.

    Se crea un archivo que tiene la extensión .tt. Su propiedad Herramienta personalizada se establece en TextTemplatingFilePreprocessor. Contiene las siguientes líneas:

    <#@ template language="C#" #>
    <#@ assembly name="System.Core" #>
    <#@ import namespace="System.Linq" #>
    <#@ import namespace="System.Text" #>
    <#@ import namespace="System.Collections.Generic" #>
    
  5. Agregue una referencia al paquete System.CodeDom de NuGet.

Conversión de un archivo existente en una plantilla en tiempo de ejecución

Una buena forma de crear una plantilla es convertir un ejemplo existente de la salida. Por ejemplo, si la aplicación va a generar archivos HTML, puede empezar creando un archivo HTML sin formato. Asegúrese de que funciona correctamente y de que su apariencia es correcta. Luego, inclúyalo en el proyecto de Visual Studio y conviértalo en una plantilla.

Para convertir un archivo de texto existente en una plantilla en tiempo de diseño

  1. Incluya el archivo en un proyecto de Visual Studio. En el Explorador de soluciones, en el menú contextual del proyecto, elija Agregar>Elemento existente.

  2. Establezca la propiedad Herramientas personalizadas del archivo en TextTemplatingFilePreprocessor. En el Explorador de soluciones, en el menú contextual del archivo, elija Propiedades.

    Nota

    Si la propiedad ya está establecida, asegúrese de que es TextTemplatingFilePreprocessor, no TextTemplatingFileGenerator. Esto puede ocurrir si incluye un archivo que ya tiene la extensión .tt.

  3. Cambie la extensión de nombre de archivo a .tt. Aunque este paso es opcional, le ayuda a evitar abrir el archivo en un editor incorrecto.

  4. Quite los espacios o signos de puntuación de la parte principal del nombre de archivo. Por ejemplo, "My Web Page.tt" sería incorrecto, pero "MyWebPage.tt" es correcto. El nombre de archivo se usará como nombre de clase en el código generado.

  5. Inserte la siguiente línea al principio del archivo. Si trabaja en un proyecto de Visual Basic, reemplace "C#" por "VB".

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

  6. Agregue una referencia al paquete System.CodeDom de NuGet.

El contenido de la plantilla en tiempo de ejecución

Directiva de plantilla

Mantenga la primera línea de la plantilla tal como estaba al crear el archivo:

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

El parámetro language dependerá del lenguaje del proyecto.

Contenido sin formato

Edite el archivo .tt para que contenga el texto que desea que genere la aplicación. Por ejemplo:

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

Código de programa insertado

Puede insertar código de programa entre <# y #>. Por ejemplo:

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

Observe que se insertan instrucciones entre <# ... #> y que se insertan expresiones entre <#= ... #>. Para obtener más información, consulte Escribir una plantilla de texto T4.

Uso de la plantilla

El código creado a partir de la plantilla

Al guardar el archivo .tt, se genera un archivo .cs o .vb subsidiario. Para ver este archivo en el Explorador de soluciones, expanda el nodo de archivo .tt. En un proyecto de Visual Basic, en primer lugar elija Mostrar todos los archivos en la barra de herramientas del Explorador de soluciones.

Observe que el archivo subsidiario contiene una clase parcial que contiene un método denominado TransformText(). Puede llamar a este método desde la aplicación.

Generación de texto en tiempo de ejecución

En el código de la aplicación, se puede generar el contenido de la plantilla mediante una llamada como esta:

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

Para colocar la clase generada en un espacio de nombres determinado, establezca la propiedad Espacio de nombres personalizado de la herramienta del archivo de plantilla de texto.

Depuración de plantillas de texto en tiempo de ejecución

Depure y pruebe las plantillas de texto en tiempo de ejecución de la misma manera que el código normal.

Puede establecer un punto de interrupción en una plantilla de texto. Si inicia la aplicación en modo de depuración desde Visual Studio, puede recorrer el código y evaluar las expresiones de inspección de la manera habitual.

Uso de parámetros en el constructor

Normalmente, las plantillas debe importar datos de otras partes de la aplicación. Para facilitar esta tarea, el código que crea la plantilla es una clase parcial. Puede crear otra parte de la misma clase en otro archivo del proyecto. Ese archivo puede incluir un constructor con parámetros, propiedades y funciones a los que pueden acceder tanto el código insertado en la plantilla como el resto de la aplicación.

Por ejemplo, podría crear un archivo independiente, MyWebPageCode.cs:

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

En el archivo de plantilla MyWebPage.tt, puede escribir:

<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 esta plantilla en la aplicación:

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

Parámetros de constructor en Visual Basic

En Visual Basic, el archivo independiente MyWebPageCode.vb contiene:

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

El archivo de plantilla puede contener:

<#@ 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>

La plantilla se puede invocar utilizando el parámetro en el constructor:

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)

Uso de datos en las propiedades de las plantillas

Una manera alternativa de pasar datos a la plantilla es agregar propiedades públicas a la clase de la plantilla en una definición de clase parcial. La aplicación puede establecer las propiedades antes de invocar a TransformText().

También se pueden agregar campos a una clase de plantilla en una definición parcial. Esto le permite pasar datos entre ejecuciones sucesivas de la plantilla.

Uso de clases parciales para el código

Muchos desarrolladores prefieren evitar escribir grandes cuerpos de código en plantillas. En su lugar, puede definir los métodos en una clase parcial que tenga el mismo nombre que el archivo de plantilla. Llame a esos métodos desde la plantilla. De este modo, la plantilla muestra con más claridad el aspecto de la cadena de salida de destino. Las discusiones sobre la apariencia del resultado se pueden separar de la lógica de creación de los datos que muestra.

Ensamblados y referencias

Si desea que el código de plantilla haga referencia a un .NET o a otro ensamblado como System.Xml.dll, agréguelo a las referencias del proyecto de la forma habitual.

Si desea importar un espacio de nombres de la misma manera que una instrucción using, puede hacerlo con la directiva import:

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

Estas directivas deben colocarse al principio del archivo, inmediatamente después de la directiva <#@template.

Contenido compartido

Si tiene texto compartido entre varias plantillas, puede colocarlo en un archivo independiente e incluirlo en cada archivo en el que debería aparecer:

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

El contenido incluido puede contener cualquier combinación de código de programa y texto sin formato, y puede contener tanto otras directivas de inclusión como otras directivas.

La directiva de inclusión se puede usar en cualquier parte del texto de un archivo de plantilla o de un archivo de inclusión.

Herencia entre plantillas de texto en tiempo de ejecución

Para compartir contenido entre las plantillas en tiempo de ejecución, escriba una plantilla de clase base, que puede ser abstracta. Use el parámetro inherits de la directiva <@#template#> para hacer referencia a otra clase de plantilla en tiempo de ejecución.

Patrón de herencia: fragmentos en los métodos base

En el patrón que se usa en el ejemplo siguiente, observe los siguientes puntos:

  • La clase base SharedFragments define métodos dentro en los bloques de características de clase <#+ ... #>.

  • La clase base no contiene texto libre. En su lugar, todos sus bloques de texto se producen dentro de los métodos de características de clase.

  • La clase derivada invoca los métodos definidos en SharedFragments.

  • La aplicación llama al método TextTransform() de la clase derivada, pero no transforma la clase base SharedFragments.

  • Tanto la clase base como la clase derivada son plantillas de texto en tiempo de ejecución; es decir, la propiedad Herramienta personalizada se establece en 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);

El resultado que se obtiene:

begin 1
    Shared Text 2
end 1

Patrón de herencia: texto en el cuerpo base

En este enfoque alternativo para usar la herencia de plantillas, la mayor parte del texto se define en la plantilla base. Las plantillas derivadas proporcionan fragmentos de datos y texto que se ajustan al contenido 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 de aplicación:

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

Resultado que se obtiene:

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.

Plantillas en tiempo de diseño: si desea usar una plantilla para generar código que forme parte de una aplicación, consulte Generación de código en tiempo de diseño mediante plantillas de texto T4.

Las plantillas en tiempo de ejecución se pueden usar en cualquier aplicación en la que las plantillas y su contenido se determinen en tiempo de compilación. Pero si desea escribir una extensión de Visual Studio que genere texto a partir de plantillas que cambian en tiempo de ejecución, consulte Invocación de transformación de texto en una extensión de VS.