Plantillas T4. Aplicaciones prácticas en la vida diaria

Junio 2010

Autor:

Carlos Pérez  Software Developer Payvision.

Ingeniero Informático por la UPV-EHU, trabajando desde hace más de 10 años como desarrollador en diversos lenguajes y tecnologías. Actualmente enganchado a .NET desde sus primeras versiones.

Bloghttp://geeks.ms/blogs/cpsaez

Mail cpsaez@gmail.com

Uso de T4 en la vida diaria.

T4, o por su nombre, Text Template Transformation Toolkit, el framework de generación de código usado en Visual Studio y publicado hace poco por Microsoft, puede ser útil más allá de generar código fuente para nuestros proyectos en el entorno de desarrollo. En este artículo veremos cómo crear un generador de ficheros Excel a partir de una plantilla usando dicha tecnología.

Introducción a T4.

A día de hoy, cualquiera que haya usado mínimamente un entorno de desarrollo puede comprobar que el código escrito por el propio programador no es el único presente en un proyecto típico. Desde los proxies para llamar a los servicios web, los dataset tipados o los wrappers de los objetos COM son clases que no nos tenemos que preocupar ya que por su condición, la tarea de su desarrollo es fácilmente automatizable.

En esencia, estas clases siempre repiten un patrón más o menos simple y cambian en función del servicio al que vamos a llamar, del objeto que vamos a envolver o el data set que vamos a generar. Por tanto, cualquier código o texto generado automáticamente a partir de un patrón o plantilla y el acto de generar el resultado en base a las características del servicio web usando la plantilla se llama transformación.

T4 nos proporciona un entorno de trabajo con herramientas para  escribir dichas plantillas y transformarlas en base a unos parámetros de entradas.

Como funciona T4.

La filosofía de T4 recuerda mucho a las antiguas páginas ASP donde se mezclaba el contenido final de la página con código nuestro propio para transformar dicha página (plantilla), ejecutándose todo en un servidor web (transformador). Pero para comprender como funciona algo es mejor verlo con un ejemplo.

Pensemos en el típico Hola Mundo, donde queremos generar un texto con resultado “Hola $TuNombre, como estas”, donde $TuNombre sea distinto. Es evidente que el patrón o plantilla tiene que contener “Hola”, algo más, y finalmente “cómo estás”. En el formato T4 se escribiría así dicha plantilla

<#@ property processor="PropertyProcessor" name="Nombre" type="System.String" #>

Hola <#= Nombre #>, cómo estás.

Básicamente la plantilla consiste en el patrón que se va a mostrar más las modificaciones que le aplicamos a ese patrón. Normalmente esas modificaciones vienen envueltas por los símbolos <# … #>. En este caso, definimos una propiedad de la plantilla y dentro del patrón, la usaremos entre el “Hola” y el “cómo estás”.

Ahora la cuestión es cómo transformar este texto. Normalmente las transformaciones son realizadas por un motor o engine, y ese engine es ejecutado sobre un host. Cuando en Visual Studio generamos un proxy para un servicio web por ejemplo, el Host es el propio Visual Studio y el Engine es T4 en sí, para más señas, implementado en Microsoft.VisualStudio.TextTemplating.

Figure 1

 El funcionamiento de esta dll es muy sencillo. A groso modo, cuando una plantilla es pasada por el engine, es convertida a código C# (o Visual Basic según sea nuestro proyecto), y este código C# es el que generara nuestro resultado a base de prints sobre un stream de salida.

Figure 2

Como trabajar directamente con la dll es muy engorroso, existen una serie de herramientas que pueden ayudarnos en la creación de plantillas T4. En concreto dos, el T4 Toolbox [1], hospedado en Codeplex y un editor para Visual Studio. Hay varios pero a mí, personalmente, me basta con la versión gratuita del editor de la empresa Tangible Engineering [2]. Este también viene con varias plantillas para visual studio ya creadas que nos pueden ayudar a empezar nuestra plantilla desde cero y que ayudan bastante si eres un neófito en T4. Otro posible editor puede ser el Visual T4 by Clarius

Figure 3

 Para reproducir los ejemplos de este artículo conviene instalar dichas aplicaciones. Vamos a ver cómo funcionan.

T4 en visual Studio

Una vez que tenemos las herramientas instaladas, si creamos un proyecto y le damos a añadir nuevo ítem, veremos que tenemos una nueva categoría llamada Code Generation.  Si añadimos un nuevo fichero de tipo Script llamado prueba.tt, veremos que a nuestra solución se añade, además del fichero tt, un fichero llamado Prueba.txt, pues bien este último es el resultado de la transformación de la plantilla prueba.tt que ha generado Visual Studio. ¿Cómo ha pasado esto? Muy sencillo, si abrimos el fichero prueba.tt tenemos esto:

Guardamos (F6) y ejecutamos la custom tool pinchando con el botón derecho encima de prueba.tt, veremos que el valor de Prueba.txt ha cambiado a:  Hola Mundo, son las 4:09:41 PM.

T4 Stand alone.

T4 también puede ser útil para generar ficheros fuera del entorno de Visual Studio, usando el Engine incrustado directamente en nuestra aplicación. Para realizar esto necesitamos usar el proyecto CustomTemplating, también alojado en Codeplex [4]. En esencia es una Dll que envuelve las llamadas a TextTemplating de Visual Studio para poder usar dicha dll fuera del mismo.

Para realizar un ejemplo, necesitaremos agregar la referencia tanto a TC.CustomTemplating.dll y a Microsoft.VisualStudio.TextTemplating.dll que encontraremos en el descargable de CustomTemplating.

Para probarlo,  vamos a intentar transformar nuestra plantilla anterior Prueba.tt desde nuestro propio código, imitando lo que haría Visual Studio.

La base de CustomTemplating se encuentra en la clase TextTransformer, donde encontraremos el método Transform. Para usarlo, deberemos llamarlo con el contenido de un template:

 

<#@ template language="C#" hostspecific="True" debug="True" #>
<#@ output extension="txt" #>
<#@ include file="T4Toolbox.tt" #>
<#
// <copyright file="Prueba.tt" company="">
//  Copyright © . All Rights Reserved.
// </copyright>


#>

Que básicamente es una plantilla vacía preparada para ejecutarse sobre el host de Visual Studio. ¿Y cómo ha generado el fichero txt? Si pinchamos sobre el explorador de la solución, encima del fichero Prueba.tt y vemos sus propiedades, comprobamos que su propiedad Custom Tool tiene el valor TextTemplatingFileGenerator. Pues bien, esta tool es el nombre del Engine que transformará la plantilla. Si cambiamos la plantilla a esto:

<#@ template language="C#" hostspecific="True" debug="True" #>
<#@ output extension="txt" #>
<#
// <copyright file="Prueba.tt" company="">
//  Copyright © . All Rights Reserved.
// </copyright>
#>
Hola Mundo, son las <#= System.DateTime.Now.ToLongTimeString() #>

Guardamos (F6) y ejecutamos la custom tool pinchando con el botón derecho encima de prueba.tt, veremos que el valor de Prueba.txt ha cambiado a:  Hola Mundo, son las 4:09:41 PM.

T4 Stand alone.

T4 también puede ser útil para generar ficheros fuera del entorno de Visual Studio, usando el Engine incrustado directamente en nuestra aplicación. Para realizar esto necesitamos usar el proyecto CustomTemplating, también alojado en Codeplex [4]. En esencia es una Dll que envuelve las llamadas a TextTemplating de Visual Studio para poder usar dicha dll fuera del mismo.

Para realizar un ejemplo, necesitaremos agregar la referencia tanto a TC.CustomTemplating.dll y a Microsoft.VisualStudio.TextTemplating.dll que encontraremos en el descargable de CustomTemplating.

Para probarlo,  vamos a intentar transformar nuestra plantilla anterior Prueba.tt desde nuestro propio código, imitando lo que haría Visual Studio.

La base de CustomTemplating se encuentra en la clase TextTransformer, donde encontraremos el método Transform. Para usarlo, deberemos llamarlo con el contenido de un template:

static void Main(string[] args)
        {
            var engine = new TC.CustomTemplating.TextTransformer();
            var template=ReadFile("Prueba.tt");
            var result=engine.Transform(template);
            Console.Write(result);
            Console.ReadLine();
        }

        private static string ReadFile(string path)
        {
            using (StreamReader reader = new StreamReader(
                new FileStream(path, FileMode.Open)))
            {
                return reader.ReadToEnd();
            }
        }

El resultado por pantalla será el mismo que cuando lo ejecutamos desde visual studio.

Escribiendo plantillas T4

El ejemplo anterior nos ha servido para sentar las bases de cómo comenzar a escribir plantillas y ver sus resultados, pero no hemos profundizado en cómo realizar plantillas más complejas. Lo primero que llama la atención son los símbolos <#, <#@, <#=, <#+ cerrados siempre por .. #>,  que sirven para separar la plantilla del código que transformará nuestra plantilla.

El significado de cada uno de ellos es el siguiente [5]

<#@ Se usa para escribir directivas, como por ejemplo si queremos añadir alguna propiedad, importar un nombre de espacio, incluir algún fichero tt, seleccionar el lenguaje en el que estará escrita la plantilla (C#, VB) etc.

<# Sirve para escribir el código que controlará la transformación de la plantilla.

<#= Aquí podremos indicar expresiones que se imprimirán directamente en la generación de la plantilla.

<#+ Sirve para añadir código que ayudará en la generación de la plantilla, pero que no se ejecutará directamente.

Veamos un ejemplo usando todos los bloques. Yo lo he llamado Blocks.tt y para crearlo he añadido un nuevo ítem al proyecto de tipo Code Generation -> Template.

<#@ property processor="PropertyProcessor" name="Entrada" type="System.String" #>
<#  // Bloque de código transformador.
    int max=int.Parse(Entrada);
    for (int i=0;i<max;i++) {
#>
Linea <#= i.ToString() #>, es la hora <#= DameHora() #> 
<#  // bloque de código transformador.
    } 
#>
<#+ // Bloque usado desde el código transformador.
public static string DameHora() {
return System.DateTime.Now.Hour.ToString(); 
}
#>

Lo primero que nos encontramos es una directiva avisando que nuestra plantilla va a tener un parámetro o propiedad, luego encontramos un bloque de código que cada vez que se transforme esta plantilla se ejecutará. En concreto el parámetro de entrada se parseará a un tipo entero y se ejecutará un for. Dentro del for hemos metido lo que es la plantilla en sí, que es lo que formará parte del resultado y unas expresiones, el valor de la variable que controla el bucle for y unas expresiones. Seguido encontramos otro bloque de código para cerrar el for abierto y por último más código definiendo la función DameHora. Este último envuelto en un bloque de tipo <#+.

 

template = ReadFile("Blocks.tt");
engine = new TC.CustomTemplating.TextTransformer(); 
result = engine.Transform(template, "Entrada", "5");
Console.Write(result);

La novedad en esta llamada es que estamos asignando el valor de la propiedad Entrada cuando llamamos a la transformación. Podemos especificar tantos parámetros como queramos debido a que Transform tiene una tercera sobrecarga que se puede usar de la siguiente forma:

template = ReadFile("Blocks.tt");
engine = new TC.CustomTemplating.TextTransformer(); 
var arguments=new TemplateArgumentCollection();
arguments.Add(new TemplateArgument("Entrada", "5"));
result = engine.Transform(template, arguments );
Console.Write(result);

Lo que estamos haciendo  es definir una colección de argumentos y pasarlo como segundo parámetro en la llamada.

El resultado que deberíamos obtener es el siguiente.

Figure 4

 

Características avanzadas de las plantillas

En los ejemplos anteriores, la plantilla era un caso sencillo de unas pocas líneas con unos parámetros de tipo no muy complejo (string), pero en casos reales, una plantilla puede tener varios cientos de líneas y parámetros de tipos más complicados. Para ayudarnos a mantener todo en orden, T4 nos ofrece una serie de directivas que podemos incluir.

Una de las más útiles sin duda es la include.

<#@ include file="fichero.tt" #>

Nos permite inyectar directamente el contenido del fichero especificado donde la coloquemos. En mi ejemplo voy a añadir dos ficheros ParaIncluir.tt y ConInclude.tt de tipo Template. El contenido de ParaIncluir será tal que así:

PROBANDO PROBANDO a las <#= System.DateTime.Now #>

Y el ConInclude.tt así:

Esto es una prueba

<#@ include file="ParaIncluir.tt" #>

Para comprobar si se ha incluido.

Si transformamos esta plantilla, el resultado será

Figure 5

 Otro punto importante a la hora de complicar nuestras plantillas es cómo poder utilizar código .NET que está fuera de las mismas. Es decir, cómo utilizar tipos de datos propios, funciones de otras Dlls etc. La forma de hacerlo no es en absoluto complicada ya que lo único que tenemos que tener en cuenta es codificar el código como si estuviésemos trabajando sobre una clase normal, solo que no sabemos en qué espacio de nombres se ejecutará la transformación, y que para realizar un using en una plantilla hay que realizarlo con una directiva tal que así

<#@ import namespace="namespace" #>

Donde namespace es el espacio de nombres que queremos usar igual que hacemos en C# o VB. Cuidado porque por defecto, el espacio de nombres donde se ejecutará la plantilla no es el mismo que el proyecto donde la estamos manipulando. Por tanto para usar las clases que cuelgan de nuestro espacio de nombres, habrá que importar el espacio de nombres del proyecto. Veamos un ejemplo

Voy a agregar una clase llamada Datos a mi proyecto de ejemplo tal que así

namespaceT4Examples

{

    public class Datos

    {

        public string Propiedad1 { get; set; }

        public string Propiedad2 { get; set; }

        public List<string> Propiedades

        {

            get

            {

                List<string> lista = new List<string>();

                lista.Add(this.Propiedad1);

                lista.Add(this.Propiedad2);

                return lista;

            }

        }

    }

}

Y ahora voy a intentar pasarla por parámetros a una plantilla, que voy a llamar MuestraDatos.tt (de tipo Template) con el siguiente contenido:

<#@ property processor="PropertyProcessor" name="Datos" type="Datos" #>
Los datos son:
Propiedad1: <#= Datos.Propiedad1 #>
Propiedad2: <#= Datos.Propiedad2 #>
Lista:
<# foreach (string valor in Datos.Propiedades) { #>
<#= valor #>
<# } #>

Y para transformar la plantilla usaremos el siguiente código

template = ReadFile("MuestraDatos.tt");
Datos datos = new Datos() { Propiedad1 = "hola", Propiedad2 = "Mundo" };
engine = new TC.CustomTemplating.TextTransformer();
result = engine.Transform(template, "Datos", datos);
Console.Write(result);

Si ejecutamos este código, en el momento de llamar a Transform nos dará un error de tipo no encontrado

Compiling transformation: The type or namespace name 'Datos' could not be found

Esto es debido a que Datos, perteneciente al namespace T4Examples no es visto desde la clase transformadora. Para solucionarlo, o bien indicamos el nombre completo del tipo de Datos o hacemos un import en la cabecera de la plantilla.

<#@ import namespace="T4Examples" #>

Ejemplo práctico

Para ver cómo aplicar T4 en la vida real, vamos a intentar generar un fichero SVG con ciertas etiquetas parametrizables a partir de una plantilla que nos creemos. SVG es un formato de gráficos vectoriales bidimensionales libre [6] adoptado por la W3C en septiembre del 2001. Estos ficheros además pueden ser vistos desde cualquier browser, como por ejemplo IE a través de plugins. Por tanto, antes de empezar, tendremos que instalar cualquier visualizador SVG, uno válido puede ser el de Adobe [7] aunque no está recomendado por su discontinuación desde el 2009. Noticia lógica ya que cada navegador está implementando su propio visor, igual que IE 9 [8].

Por otro lado, para crear nuestro fichero SVG inicial, yo voy a usar Adobe Illustrator en su versión trial (crearlo a mano sería un poco complicado) y los pasos que seguiré son los siguientes:

  1. Generar el modelo SVG para la base de plantilla.
  2. Modificar el fichero SVG y convertirlo a una plantilla T4 añadiendo las secciones correspondientes.
  3. Codificar en C# la llamada a la transformación de la plantilla a un nuevo fichero SVG usando el engine T4.

Empecemos. Para generar un modelo SVG vistoso, usaré los ejemplos que trae Illustrator por defecto, en concreto el llamado Business Card.ait del directorio Tech, un ejemplo de tarjeta de visita. La idea es convertir esta tarjeta de visita a una template para rellenar los datos de contacto desde una clase de C#.

Figure 6 Business card example

Una vez cargado este ejemplo en Illustrator, lo guardamos como un fichero SVG marcando la opción dentro de Save As, como tipo SVG y las demás opciones por defecto.

º

Figure 7

Ahora lo que toca es cambiar la extensión de SVG a TT para empezar el paso dos y convertirlo a plantilla. Ya de paso lo añadimos a la solución de Visual Studio para poder manipularlo desde ahí.

Cuando lo abrimos dentro de Visual Studio, el contenido puede abrumar un poco, pero si nos fijamos bien no es más que un XML muy grande y con contenido a veces binario, pero no deja de ser un XML. Nuestro objetivo será, que en vez de que salga Your Name en la tarjeta, salga un dato que queramos poner nosotros y que le pasemos como parámetro a la plantilla. Lo mismo para la dirección. Lo primero que tenemos que hacer por tanto es añadir esos parámetros a la plantilla:

<#@ property processor="PropertyProcessor" name="Nombre" type="System.String" #>
<#@ property processor="PropertyProcessor" name="Direccion" type="System.String" #>

<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15
...

Ahora hay que averiguar dónde modificar la plantilla para que refleje dichos parámetros. Como examinar todo el XML sería una locura, lo que hacemos es buscar la cadena “Your Name” en el fichero y reemplazarla por la expresión siguiente allí en donde aparezca.

…… " font-size="8" letter-spacing="1.053"><#= Nombre.Trim() #></tspan><tspan x="-27.5

Y lo mismo para la dirección, salvo que aquí tenemos que buscar “123 Everywhere Avenue, Suite 000, City, St 00000” y reemplazarlo por <#= Direccion.Trim() #>

El código para el paso 3 sería el siguiente:

template = ReadFile("BusinessCardTemplate.tt");
engine = new TC.CustomTemplating.TextTransformer();
TemplateArgumentCollection businesCardArguments = new TemplateArgumentCollection();
businesCardArguments.Add(new TemplateArgument("Nombre", "Pedro Perez"));
businesCardArguments.Add(new TemplateArgument("Direccion", "Calle la Rue 28, 28001, Madrid"));
result = engine.Transform(template, businesCardArguments );
using(StreamWriter writer=new StreamWriter(Environment.CurrentDirectory + "\\salida.svg")
{
writer.Write(result);
}

Y como resultado si abrimos el fichero salida.svg con Illustrator, tenemos la siguiente figura:

Figure 8

Como vemos, el formato se ha descompensado por que en la plantilla no estaba alienado y centrada la caja de texto, todo es cuestión de cambiar estos pequeños detalles y pasarle más datos para teléfono, Email... a la plantilla. Cambiar el ejemplo para que lea los datos de entrada de una base de datos es bastante sencillo aunque realizar ese ejercicio escapa del alcance de este artículo.

Conclusión

Hemos visto como una herramienta pensada para trabajar conjuntamente con Visual Studio podemos usarla para otras tareas que requieran unos mínimos cambios en ficheros de forma repetitiva. Una vez que se conoce el funcionamiento, es muy fácil crear una plantilla T4 y cogiendo un poco de soltura, no serán pocas las ocasiones en que nos ahorremos mucho tiempo usando este tipo de herramientas.

Puedes descargar el código de ejemplo en el blog del autor: http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/cpsaez.codigoFuente/T4Examples.zip

[1]http://t4toolbox.codeplex.com/

[2] http://www.olegsych.com/2009/04/t4-editor-by-tangible-engineering/

[3]http://www.visualt4.com/

[4] http://customtemplating.codeplex.com/

[5] https://msdn.microsoft.com/en-us/library/bb126478.aspx

[6] http://es.wikipedia.org/wiki/Scalable_Vector_Graphics

[7] http://www.adobe.com/svg/viewer/install/main.html

[8] https://blogs.msdn.com/b/ie/archive/2010/01/05/microsoft-joins-w3c-svg-working-group.aspx