22. August 2014 · von Steffen Nörtershäuser

Erklärt: Codegenerierung mit T4-Templates

Entwickler kennen das sicher: Sie haben verschiedene Metadaten in verschiedenen Quellen, die Sie ebenfalls in Ihrem Quellcode brauchen. Ohne passende Hilfsmittel müssen Sie Ihren Quellcode per Hand nachziehen, wenn sich die die Metadaten ändern. Das ist fehleranfällig, zeitaufwendig und frustrierend. Hier können T4-Templates Abhilfe schaffen. Sie bieten eine flexible Möglichkeit an um aus verschiedensten Metadaten Quellen Sourcecode zu generieren. Wie das funktioniert möchte ich in diesem Beitrag kurz vorstellen.

Das Beispiel Szenario für diesen Artikel ist dabei direkt aus einer aktuellen Projektsituation gegriffen. Wobei ich die Umsetzung für diesen Artikel vereinfacht habe, um mich auf das wesentliche konzentrieren zu können.

SQL Struktur

Es geht dabei darum das Nutzerzugriffe protokolliert werden müssen. Bei einem Internetportal ist es beispielsweise interessant und wichtig zu speichern von welcher IP-Adresse sich ein Nutzer einloggt. Dies kann beispielsweise bei einem Missbrauch dabei helfen die Situation zu klären. Hier für wird in unserem Beispiel ein Access-Log im SQL Server angelegt. Die Datenbank sieht hierbei wie folgt aus:

db_overview

In der Tabelle „LogEntries“ werden dabei die konkreten Logeinträge mit den Verbindungsinformationen des Nutzers gespeichert. Gleichzeitig besitzt jeder Eintrag einen Verweis auf einen Typen. Diese Typen werden in der Tabelle „LogTypes“ angelegt. Ein Typ ist beispielsweise „Login durchgeführt“ oder „Neues Konto angelegt“. Hierüber werden die Logeinträge gruppiert. In meinem Beispiel sind die folgende Einträge in der LogTypes Tabelle zu finden:

db_log_types

C# Logging

Ein neuer Log Eintrag kann nun im C# Code über den Aufruf folgender Funktion angelegt werden:

public void CreateLogEntry(int typeId, string connectionInfoXml);

Auf die konkrete Implementierung dieser Funktion möchte ich nicht eingehen. Prinzipiell erzeugt sie jedoch lediglich einen neuen Eintrag in der LogEntries Tabelle. Ich stelle sie an dieser Stelle nur vor, um das Problem nochmal hervorzuheben. Wie stellt man nun sicher das die typeId mit denen in der SQL Tabelle übereinstimmen? Zieht man diese immer per Hand nach? Was, wenn man dies mal vergisst? Oder eine Änderung falsch übernimmt, weil der Zeitdruck hoch ist?

T4 Templates

An dieser Stelle kommen die T4-Templates zur Hilfe. Diese sind bereits seit einiger Zeit Bestandteil von Visual Studio, jedoch habe ich erst selten erlebt das sie in der Praxis genutzt wurden.

Im Rahmen dieses Beispiels habe ich lediglich eine Visual Studio Konsolen Anwendung angelegt. T4-Templates sind jedoch mit allen Visual Studio Projekten nutzbar.

Hierfür legen wir ein neues T4-Template in Visual Studio über Hinzufügen -> Neues Element ein.
Anschließend wählen wir Textvorlage aus:

add_t4_template

Der fertige Code des T4-Templates sieht nun wie folgt aus:

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Data" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ output extension=".cs" #>

namespace T4.LoggingSample
{
    /// <summary>
    /// Stellt Konstanten für mögliche Logging Typen bereit
    /// </summary>
    class LoggingTypes
    {
<#
        // Verbindung an SQL Server herstellen
        SqlConnection connection = new SqlConnection("Server=.;Database=LoggingSample;Trusted_Connection=True;");
        connection.Open();

        // Alle Logging Typen abfragen
        SqlCommand cmd = new SqlCommand("SELECT ID, Name FROM LogTypes ORDER BY ID ASC");
        cmd.Connection = connection;
        cmd.CommandType = CommandType.Text;

        bool isFirst = true;
        using (SqlDataReader reader = cmd.ExecuteReader())
        {
            while (reader.Read())
            {
                int id = reader.GetInt32(reader.GetOrdinal("ID"));
                string name = reader.GetString(reader.GetOrdinal("Name"));

                // Gültigen Variablen Namen erzeugen
                string constName = name.Replace(' ', '_');
                constName = constName.Replace('?', '_');
                constName = constName.Replace('#', '_');
                constName = constName.Replace("ä", "ae");
                constName = constName.Replace("ö", "ae");
                constName = constName.Replace("ü", "ae");
                constName = constName.Replace("Ä", "ae");
                constName = constName.Replace("Ö", "ae");
                constName = constName.Replace("Ü", "ae");

                // Wert in T4-Template schreiben
                if (!isFirst)
                {
                    WriteLine("");
                }
                isFirst = false;
                WriteLine("		/// <summary>");
                WriteLine("		/// " + id.ToString() + ": " + name);
                WriteLine("		/// </summary>");
                WriteLine("		public const int " + constName + " = " + id + ";");
            }
        }

        connection.Close();
#>
    }
}

Nun zur Erklärung dieses Templates:

  • Über das „<#@ assembly“-Schlüsselwort kann eine Assembly in das T4-Template eingebunden werden. Dies ist mit dem hinzufügen einer Referenz in Visual Studio vergleichbar.
  • Über „<#@ import namespace“ wird ein Namespace eingebunden. Dies ist mit dem using Statement in C# vergleichbar.
  • Über „<#@ output“ können gewisse Parameter für die zu generierende Datei festgelegt werden. In unserem Beispiel wird hier die Endung der Datei auf „.cs“ festgelegt, da wir eine C# Datei generieren.
  • Jeder Text der nicht in „<# #>“ steht wird eins zu eins in die Ausgabe Datei übernommen. In unserem Beispiel ist das der Rahmen der Klasse in welche die Konstanten geschrieben werden.
  • Alles was innerhalb von „<# #>“ steht wird als C# Code interpretiert. In welcher Sprache das Template geschrieben ist findet sich im „<#@ template“-Schlüsselwort.

Auf den C# Code innerhalb des Templates möchte ich an dieser Stelle nicht näher eingehen. Grundsätzlich fragt er jedoch alle Einträge aus der „LogTypes“ Tabelle ab und schreibt diese anschließend in das Template.

Die generierte CS-Datei sieht nun wie folgt aus:

namespace T4.LoggingSample
{
    /// <summary>
    /// Stellt Konstanten für mögliche Logging Typen bereit
    /// </summary>
    class LoggingTypes
    {
        /// <summary>
        /// 1: Nutzer hat sich eingeloggt
        /// </summary>
        public const int Nutzer_hat_sich_eingeloggt = 1;

        /// <summary>
        /// 2: Fehlerhafte Login Daten wurden eingegeben
        /// </summary>
        public const int Fehlerhafte_Login_Daten_wurden_eingegeben = 2;

        /// <summary>
        /// 3: Neues Benutzerkonto wurde erstellt
        /// </summary>
        public const int Neues_Benutzerkonto_wurde_erstellt = 3;
    }
}

Wie man sehen kann spiegelt diese Datei nun den aktuellsten Stand aus der oben vorgestellten Tabelle wieder. Die Codegenerierung durch das T4-Template wird immer dann gestartet wenn das Template gespeichert wird oder über Rechtsklick auf die TT-Datei „Benutzerdefiniertes Tool ausführen“ ausgewählt wird:
run_t4_template

Fazit

Wie in diesem Beitrag aufgezeigt bieten T4-Templates eine sehr flexible und bequeme Möglichkeit um Code aus verschiedensten Metadaten Quellen zu generieren. Auf diese Weise werden Fehler ausgemerzt, Zeit gespart und die Entwickler entlastet.



Diesen Blogeintrag bewerten:


Haben Sie Fragen zu diesem Artikel oder brauchen Sie Unterstützung?

Nehmen Sie mit uns Kontakt auf!

Wir unterstützen Sie gerne bei Ihren Vorhaben!


Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Kontakt
Lassen Sie sich von uns beraten
Wir freuen uns über Ihr Interesse an unseren Leistungen. Hinterlassen Sie
uns Ihren Namen, Ihre Telefonnummer und E-Mail Adresse – wir melden
uns schnellstmöglich bei Ihnen.
Kontakt aufnehmen