T4 (Text Template Transformation Toolkit) je obecný šablonovací systém pro generování textového výstupu, který je navíc přímo integrovaný do Visual Studia (jestli se nemýlím tak již od VS 2008). Přestože lze v případě potřeby transformaci T4 šablony (T4 template) spouštět i kódem v runtime, primárně je T4 využívané pro generovaní výstupu v design-time tj. právě z Visual Studia.
T4 slouží obecně pro generování jakéhokoliv textového výstupu tj. zdrojového kódu v C#, Visual Basic .NET, XAML, T-SQL, nebo XML, .config nebo jiných textových souborů. My se v této sérii ale zaměříme pouze na využití T4 šablon pro generování zdrojového kódu (konkrétně v C# tj. .cs souborů).
Soubor T4 šablony má příponu .tt a jeho vlastnost Custom Tool je nastavena na hodnotu “TextTemplatingFileGenerator”. Obsahem souboru T4 šablony jsou direktivy a střídání bloků kódu s bloky textu, které se rovnou opisují na výstup (podobně jako například u ASP). Ve Visual Studiu vytvoříme novou T4 šablonu tak, že zvolíme Add, New Item a vybereme Text Template (*).
Výchozí způsob je takový, že jedna T4 šablona generuje jeden výstupní soubor, ale později v této sérii ukáži i jak lze pomoci jedné šablony generovat výstupních souborů několik. Generovaný výstupní soubor šablony je ve VS projektu zobrazován pod souborem se šablonou.
Protože je transformace šablony realizovaná standardně jako Custom Tool, je výstup šablony přegenerován v případě že:
- Automaticky pokud změníme a uložíme soubor T4 šablony.
- Na šabloně explicitně zvolíme volbu Run Custom Tool.
Z toho plyne, že pokud šablona používá jako vstup externí soubory nebo zdroje, musíme po jejich změně spustit transformaci ručně. Případně lze tuto nutnost obejít nastavením spouštění transformace všech šablon před každým buildem.
Pro podporu při editaci T4 šablony ve Visual Studiu je potřeba doinstalovat doplněk. Přestože není jediný, já aktuálně používám tangible T4 Editor 2.1 plus modeling tools for VS 2012. Tento doplněk existuje v placené i neplacené omezené verzi.
Syntaxe T4 šablony je popsána zde nebo zde. Ve stručnosti shrnu to nejdůležitější:
<#@ direktiva #> např.: |
Direktiva šablony |
<#@ template language="C#" #> |
Definuje, že šablona bude napsána v jazyce C#. |
<#@ output extension=".generated.cs" #> |
Definuje příponu generovaného výstupního souboru (tj. pokud se šablona bude například jmenovat DataObjects.tt, bude se výstupní soubor v tomto případě jmenovat DataObjects.generated.cs). |
<#@ assembly name="System.Core" #> |
Reference na assembly. |
<#@ import namespace="System.Linq" #> |
Import namespace (obdoba using v C#). |
<#@ include file="base.tt" #> |
Vložení textu nebo kódu z jiného souboru T4 šablony na dané místo. |
Text |
Blok textu, který je opsán na výstup. Blok textu lze v šabloně použít všude tam, kde je platné volání metody Write. |
<# ... #> |
Blok kódu |
<#= … #> |
Výraz. Výsledek výrazu je opsán na výstup. |
<#+ ... #> |
Class feature blok umožňující definovat vlastnosti, pomocné metody, nested třídy apod.. Definované členy lze využívat v hlavním kódu šablony. |
Příklady použití jednotlivých bloků naleznete na výše uvedených odkazech nebo dále v příkladech v této sérii.
Bližší vysvětlení si zasluhuje podmínka platného volání metody Write u bloku textu, proto se u ní teď trochu zastavíme.
Při generování výstupu T4 šablony je nejprve na pozadí ze šablony vytvořena a zkompilována třída (**) odvozená ze základní třídy TextTransformation, následně je vytvořena její instance a na ní zavolána metoda TransformText. Návratová hodnota z tohoto volání je uložena do výstupního souboru.
Při vytváření transformační třídy:
- jsou členy definované v class feature bloku umístěny jako členy této třídy
- bloky kódu jsou umístěny do metody TransformText
- bloky textu a výrazy jsou přeloženy jako volání metody Write s daným textem nebo výsledkem výrazu.
Zmíněné volání metody Write ale nemusí být nutně volání metody Write základní třídy TextTransformation, stačí když ve třídě, kde je blok textu umístěn, existuje metoda s danou signaturou:
void Write(string textToAppend);
která bude zápis výstupu provádět. Toho je možné využít pro vyčlenění části logiky šablony do pomocné třídy mimo hlavní kód šablony.
Jednotlivé typy bloků T4 šablony i výše zmíněný postup demonstruje následující příklad:
<#@ template language="C#" debug="true" #>
<#@ output extension=".generated.cs" #>
<#
WriteHeader();
#>
namespace <#= System.Runtime.Remoting.Messaging.CallContext.LogicalGetData("NamespaceHint") #>
{
<#
string[] classes = new[] { "Alpha", "Bravo", "Charlie" };
this.PushIndent("\t");
bool isFirstClass = true;
foreach (string className in classes)
{
if (!isFirstClass)
{
WriteLine(null);
}
//Generování a zápis výstupu pro třídu
var classFormatter = new ClassFormatter(this, className);
classFormatter.WriteClassOutput();
isFirstClass = false;
}
this.PopIndent();
#>
}<#+
private void WriteHeader()
{
#>
// ------------------------------------------------------------------------------
// This code was generated by template.
//
// Changes to this file will be lost if the code is regenerated.
// -----------------------------------------------------------------------------
using System;
<#+
}
private sealed class ClassFormatter
{
#region member varible and default property initialization
private readonly Microsoft.VisualStudio.TextTemplating.TextTransformation Owner;
private readonly string ClassName;
#endregion
#region constructors and destructors
public ClassFormatter(Microsoft.VisualStudio.TextTemplating.TextTransformation owner, string className)
{
this.Owner = owner;
this.ClassName = className;
}
#endregion
#region action methods
public void WriteClassOutput()
{
#>
public class <#= this.ClassName #>
{
public override string ToString()
{
return "Hello from <#= this.ClassName #>";
}
}
<#+
}
#endregion
#region private member functions
private void Write(string textToAppend)
{
this.Owner.Write(textToAppend);
}
#endregion
}
#>
Výstup z této šablony bude:
// ------------------------------------------------------------------------------
// This code was generated by template.
//
// Changes to this file will be lost if the code is regenerated.
// -----------------------------------------------------------------------------
using System;
namespace T4Sample1
{
public class Alpha
{
public override string ToString()
{
return "Hello from Alpha";
}
}
public class Bravo
{
public override string ToString()
{
return "Hello from Bravo";
}
}
public class Charlie
{
public override string ToString()
{
return "Hello from Charlie";
}
}
}
V příkladu je právě v pomocné (nested) třídě ClassFormatter definována metoda Write. Jenom díky její existenci je možné kombinovat i kód této třídy s bloky textu a s výrazy podobně jako to lze u hlavního kódu šablony nebo u instančních metod šablony (příkladem takové metody je metoda WriteHeader).
Dále bych u tohoto příkladu ještě upozornil na následující:
- Výraz System.Runtime.Remoting.Messaging.CallContext.LogicalGetData("NamespaceHint") vrací název namespace odpovídající projektu a podadresáři, ve kterém je soubor šablony umístěn.
- Pomoci metod PushIndent a PopIndent lze řídit odsazení generovaného výstupu.
Příště: Rozebereme si možné vstupy a způsoby, které lze v T4 šabloně použít pro generování kódu.
(*) Některé doplňky do Visual Studia týkajících se rozšíření podpory pro editaci T4 šablon mohou tuto volbu změnit.
(**) S názvem GeneratedTextTransformation.