Šablony T4

Text Template Transformation Toolkit, neboli T4, je .NETová technologie pro generování různých textových souborů, včetně zdrojových kódů. Umožňuje míchat statický text a procesní instrukce (podobně jako třeba PHP) a tyto procesní instrukce mohou obsahovat běžný kód C# nebo VB. Jedním příkladem použití je generování entity tříd z databázového schématu v ADO.NET Entity Frameworku.

Velmi jednoduchá šablona může vypadat třeba takto:

Tento soubor byl vygenerován <#= DateTime.Now #>.

Zde můžete vidět prostý text plus použití .NET třídy DateTime a její vlastnosti Now, uzavřené ve speciálních závorkách.

Protože je tento blog primárně o Dynamics AX, ukážu jeden trochu komplexnější příklad v AX. Bude generovat novou testovací třídu dle definice jiné třídy existující v AOT. (Příklad používá Dynamics AX 2012 a Visual Studio 2010).

Nejprve musíte vytvořit projekt (class library) ve Visual Studio, přidat ho do AOT a vytvořit několik proxy tříd: SysDictClass, SysDictMethod, Set a SetEnumerator. Pak přidejte nový objekt typu Preprocessed Text Template. To je jeden z typů T4 šablon.

Vložte do šablony následující kód:

<#@ template language="C#" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="Microsoft.Dynamics.AX.ManagedInterop" #>
Exportfile for AOT version 1.0 or later
Formatversion: 1
 
***Element: CLS
 
; Microsoft Dynamics AX Class: <#= ClassName #>Test unloaded
; --------------------------------------------------------------------------------
  CLSVERSION 1
 
  CLASS #<#= ClassName #>Test
    PROPERTIES
      Name                #<#= ClassName #>Test
    ENDPROPERTIES
 
    METHODS
      SOURCE #classDeclaration
        #class <#= ClassName #>Test extends SysTestCase
        #{
        #}
      ENDSOURCE
<# foreach (SysDictMethod method in Methods){
#>      SOURCE #test<#= Capitalize(method.name()) #>
        #public void test<#= Capitalize(method.name()) #>()
        #{
        #    //TODO: implement unit test
        #}
      ENDSOURCE<#}#>
    ENDMETHODS
  ENDCLASS
 
***Element: END
<#+ public string ClassName {get; set;}
private IEnumerable<SysDictMethod> Methods
{
    get
    {
        SysDictClass dictClass = SysDictClass.newName(ClassName);
        SetEnumerator enumerator = dictClass.methods(true, true, false).getEnumerator();
        while (enumerator.moveNext())
        {
            yield return new SysDictMethod((Microsoft.Dynamics.AX.ManagedInterop.Object)enumerator.current());
        }
    }
}
private string Capitalize(string s)
{
    return s.Substring(0, 1).ToUpper() + s.Substring(1);
}#>

Jak můžete vidět, šablona generuje normální soubor .xpo. Pojďme se podívat blíže na některé její části.

Začíná hlavičkou šablony a deklarací páru jmenných prostorů, které budeme chtít v šabloně používat.

<#@ template language="C#" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="Microsoft.Dynamics.AX.ManagedInterop" #>

Pak je zde nějaký statický text s občasnými konstrukcemi <#= ClassName #>. <#= znamená že výstup příkazu bude přidán jako text do vygenerovaného souboru. A ClassName je jméno vlastnosti, která je definovaná dále v šabloně.

Další zajímavá sekce je:

<# foreach (SysDictMethod method in Methods){
#>

Uvnitř můžete snadno rozeznat běžný C# kód (Methods je další vlastnost definovaná později). Všimněte si, že tato část je uvozena <#, ne <#=. To znamená, že obsah by měl být jen spuštěn, aniž by se cokoli přidávalo do výstupu. Iterujeme zde přes všechny metody v kolekci a pro každou metodu opakujeme celou sekci mezi foreach a uzavírací závorkou v <#}#>.

V těle foreach používáme už známý konstrukt vracející hodnotu, v tomto případě volající nějaké metody:

 <#= Capitalize(method.name()) #>

Na konci šamblony je jiná speciální část uvozená <#+. Tato sekce slouží k deklaraci členů třídy – v našem případě vlastností ClassName a Methods a metody Capitalize().

Shrnutí: Šablona očekává, že dostane jméno nějaké AX třídy, a vygeneruje testovací třídu a jednu testovací metodu pro každou (nezděděnou) metodu zdrojové třídy. (V reálné implementaci by to bylo složitější, ale pro ukázku je to dostatečné.)

Nyní to pojďme využít v Dynamics AX. Sestavte a nasaďte projekt s T4 šablonou, jděte do AX a vytvořte job s nějakým podobným kódem:

str className = 'Stack';
str xpoText;
TestClassGenerator.TestClassTemplate template;
SysImportElements sysImportElements = new SysImportElements();
FileName fileName;
TextIo io;
 
//Vytvoří instanci T4 šablony (jméno záleží na tom, jak jste pojmenovali projekt a šablonu ve VS)
template = new TestClassGenerator.TestClassTemplate();
//Použije vlastnost šablony k nastavení jména třídy
template.set_ClassName(className);
//Nechá šablonu vygenerovat obsah .xpo souboru
xpoText = template.TransformText();
 
//Zapíše .xpo do dočasného souboru
fileName = WinAPI::getTempFilename(WinApi::getTempPath(), '');
io = new TextIo(fileName, 'w');
io.write(xpoText);
 
//Import .xpo
sysImportElements = new SysImportElements();
sysImportElements.newFile(fileName);
sysImportElements.parmAddToProject(false);
sysImportElements.parmImportAot(true);
sysImportElements.import();
 
//Otevře nově vytvořenou třídu
TreeNode::findNode(strFmt(@'Classes\%1Test', className)).AOTnewWindow();

Když job spustíte, měl by se vygenerovat následující třída:

Jak je vidět, T4 šablony mohou být opravdu užitečné. Obzvláště pokud cílový soubor obsahuje velké množství statického textu, T4 je skvělá volba. Možná nebudete T4 šablony používat každý den, ale je dobré o nich vědět.