Generating code with Preprocessed T4 Text Templates



If you wanna be a T4 ninja, read all this: T4: Text Template Transformation Toolkit

Last Friday I was writing some code to populate a database with country/state information. I wrote the code to include Brazil and its 27 states but when I realized that I’d have to do the same thing again with USA and its states I thought that it’d be boring. US has 50 states and creating an object for each state would be a pain.

I have to populate the Name and Abbreviation properties of each State object. Each state is created this way in code:

new State()
    {
        Name = "Alabama",
        Abbreviation = "AL",
        Country = country2
    },

This sounded like a good a chance to try T4 Templates (Wikipedia article). T4 stands for Text Template Transformation Toolkit. That’s a beautiful name IMHO. I’ve read some articles about it since its inception but haven’t had a chance to try it. T4 can generate any text you want for whatever programming language.

T4 is used within Microsoft in ASP.NET MVC for the creation of the views and controllers, ADO.NET Entity Framework for entity generation, and ASP.NET Dynamic Data. Really powerful tool!

T4 came to my mind because I answered a question at StackOverflow 4 days ago: Is T4 template run every time it is requested? This proves that StackOverflow is a great tool to keep things fresh in your mind. The asker is creating e-mails with T4. I can imagine the creation of e-mail messages in a way that you pass a list of e-mail addresses an then for each e-mail address, T4 generates a .msg message. Just thinking..

So to start with T4 I created a simple Console Application as always and followed the steps described in this excellent MSDN step by step: Run-Time Text Generation by using Preprocessed T4 Text Templates.

To generate code to create each State object (50 in total) I created the following files within a Visual Studio Solution titled T4CodeGeneration:

T4 Code Generation Project in Solution Explorer viewFigure 1 - T4 Code Generation Project in Solution Explorer view

States’ information is stored within the USAStates.txt file that I got on the internet. This file have StateName, StateAbbreviation in each line. It’s a really basic CSV file…

Alabama,AL
Alaska,AK
Arizona,AZ
Arkansas,AR
California,CA
.
.
.

To read this simple .txt file I wrote this code (Program.cs):

class Program
{
    static void Main(string[] args)
    {
        States code = new States(ReadData());
        String result = code.TransformText();
        File.WriteAllText("outputCode.cs", result);
    }

    public static List<Tuple<string, string>> ReadData()
    {
        List<Tuple<string, string>> states = new List<Tuple<string, string>>();

        using (var myCsvFile =
                new TextFieldParser(@"C:\Users\Leniel\Documents\Visual Studio 2010\Projects\T4CodeGeneration\USAStates.txt"))
        {
            myCsvFile.TextFieldType = FieldType.Delimited;
            myCsvFile.SetDelimiters(",");

            while (!myCsvFile.EndOfData)
            {
                string[] fieldArray;

                try
                {
// Reading line data fieldArray = myCsvFile.ReadFields(); } catch (MalformedLineException) { // Not a valid delimited line - log, terminate, or ignore continue; } // Process values in fieldArray states.Add(new Tuple<string, string>(fieldArray[0], fieldArray[1])); } } return states; } }

I’m using the built in TextFieldParser class in the ReadData() method that’s part of the Microsoft.VisualBasic namespace. This class is used to read the CSV file. Take a look at the project’s references above. This class has everything one needs to read a simple text file.

I then read and add each line of the file to the states List<Tuple<string, string>>.

I use this states list within the T4 template to generate my custom code.

This is the content of the file (StatesCode.cs):

partial class States
{
    private readonly List<Tuple<string, string>> states;

    public States(List<Tuple<string, string>> states)
    {
        this.states = states;
    }
}

Interesting thing to note here is that it’s a partial class named States (this is the same name of the T4 template). During compilation this class code will be merged with the T4 accompanying States.cs file code (generated automatically by Visual Studio code generator). That’s why it’s a partial class… This class is used exclusively to pass data to the T4 template.

Now comes the really interesting part that lies within the States.tt file:

<#@ template language="C#" #>
var usaStates = new List<State>
{
<# foreach (Tuple<string, string> tuple in states) 
// states is declared in StatesCode.cs
{ #>
    new State()
    {
        Name = "<#= tuple.Item1 #>",
        Abbreviation = "<#= tuple.Item2 #>",
        Country = country2
    },
<# } // end of foreach #>
};

usaStates.ForEach(s => context.States.Add(s));

As you can see, the T4 template code has its own syntax and structures. C# code goes within <#  #> code blocks. To actually get a variable value you must place it within
<#=  #> code blocks. I’m using a foreach that traverses the states list. See that I’m accessing the Tuple<string, string> items (Item1 and Item2) within these code blocks.

Everything that lies outside <# #> is just static text and will be output as such in the generated result (code output). This is the case with the first two and last lines of T4 template code above.

Really important thing: check that you have set the T4 template CustomTool property appropriately to TextTemplatingFilePreprocessor as this is a critical part to make things work as expected:

T4 Template and Custom Tool Property configurationFigure 2 - T4 Template and Custom Tool Property configuration

Now to end result: when I run the project, the Main method (part of Program.cs above) is called:

static void Main(string[] args)
{
    States code = new States(ReadData());
    String result = code.TransformText();
    File.WriteAllText("outputCode.cs", result);
}

I read the data and pass it to the T4 template. I then call T4 TransformText() method that does the magic! Last but not least, the resulting code is written to a file called outputCode.cs.

I get a beautiful code that is placed in the Project’s Debug folder: C:\Users\Leniel\Documents\Visual Studio 2010\Projects\T4CodeGeneration\bin\Debug.

To accomplish my objective I just Copy/Paste this file code in my SampleData.cs class that I use in other project to seed my database.

As this is a pretty extensive code listing (that I’d have to type – oh my God!), I’ve chosen to let it for the final part of the post for your viewing pleasure. Open-mouthed smile See that the code is well formatted. To get code formatted you have to fight with your T4 template file.

Now that you have a basic overview of T4 I hope you have fun with it as I had. This is power in your hands. Now it’s up to you to give wings to your imagination!

Visual Studio 2010 Console Application
http://sites.google.com/site/leniel/blog/T4CodeGeneration.zip

Code output:

var usaStates = new List<State>
{
    new State()
    {
        Name = "Alabama",
        Abbreviation = "AL",
        Country = country2
    },
    new State()
    {
        Name = "Alaska",
        Abbreviation = "AK",
        Country = country2
    },
    new State()
    {
        Name = "Arizona",
        Abbreviation = "AZ",
        Country = country2
    },
    new State()
    {
        Name = "Arkansas",
        Abbreviation = "AR",
        Country = country2
    },
    new State()
    {
        Name = "California",
        Abbreviation = "CA",
        Country = country2
    },
    new State()
    {
        Name = "Colorado",
        Abbreviation = "CO",
        Country = country2
    },
    new State()
    {
        Name = "Connecticut",
        Abbreviation = "CT",
        Country = country2
    },
    new State()
    {
        Name = "Delaware",
        Abbreviation = "DE",
        Country = country2
    },
    new State()
    {
        Name = "Florida",
        Abbreviation = "FL",
        Country = country2
    },
    new State()
    {
        Name = "Georgia",
        Abbreviation = "GA",
        Country = country2
    },
    new State()
    {
        Name = "Hawaii",
        Abbreviation = "HI",
        Country = country2
    },
    new State()
    {
        Name = "Idaho",
        Abbreviation = "ID",
        Country = country2
    },
    new State()
    {
        Name = "Illinois",
        Abbreviation = "IL",
        Country = country2
    },
    new State()
    {
        Name = "Indiana",
        Abbreviation = "IN",
        Country = country2
    },
    new State()
    {
        Name = "Iowa",
        Abbreviation = "IA",
        Country = country2
    },
    new State()
    {
        Name = "Kansas",
        Abbreviation = "KS",
        Country = country2
    },
    new State()
    {
        Name = "Kentucky",
        Abbreviation = "KY",
        Country = country2
    },
    new State()
    {
        Name = "Louisiana",
        Abbreviation = "LA",
        Country = country2
    },
    new State()
    {
        Name = "Maine",
        Abbreviation = "ME",
        Country = country2
    },
    new State()
    {
        Name = "Maryland",
        Abbreviation = "MD",
        Country = country2
    },
    new State()
    {
        Name = "Massachusetts",
        Abbreviation = "MA",
        Country = country2
    },
    new State()
    {
        Name = "Michigan",
        Abbreviation = "MI",
        Country = country2
    },
    new State()
    {
        Name = "Minnesota",
        Abbreviation = "MN",
        Country = country2
    },
    new State()
    {
        Name = "Mississippi",
        Abbreviation = "MS",
        Country = country2
    },
    new State()
    {
        Name = "Missouri",
        Abbreviation = "MO",
        Country = country2
    },
    new State()
    {
        Name = "Montana",
        Abbreviation = "MT",
        Country = country2
    },
    new State()
    {
        Name = "Nebraska",
        Abbreviation = "NE",
        Country = country2
    },
    new State()
    {
        Name = "Nevada",
        Abbreviation = "NV",
        Country = country2
    },
    new State()
    {
        Name = "New Hampshire",
        Abbreviation = "NH",
        Country = country2
    },
    new State()
    {
        Name = "New Jersey",
        Abbreviation = "NJ",
        Country = country2
    },
    new State()
    {
        Name = "New Mexico",
        Abbreviation = "NM",
        Country = country2
    },
    new State()
    {
        Name = "New York",
        Abbreviation = "NY",
        Country = country2
    },
    new State()
    {
        Name = "North Carolina",
        Abbreviation = "NC",
        Country = country2
    },
    new State()
    {
        Name = "North Dakota",
        Abbreviation = "ND",
        Country = country2
    },
    new State()
    {
        Name = "Ohio",
        Abbreviation = "OH",
        Country = country2
    },
    new State()
    {
        Name = "Oklahoma",
        Abbreviation = "OK",
        Country = country2
    },
    new State()
    {
        Name = "Oregon",
        Abbreviation = "OR",
        Country = country2
    },
    new State()
    {
        Name = "Pennsylvania",
        Abbreviation = "PA",
        Country = country2
    },
    new State()
    {
        Name = "Rhode Island",
        Abbreviation = "RI",
        Country = country2
    },
    new State()
    {
        Name = "South Carolina",
        Abbreviation = "SC",
        Country = country2
    },
    new State()
    {
        Name = "South Dakota",
        Abbreviation = "SD",
        Country = country2
    },
    new State()
    {
        Name = "Tennessee",
        Abbreviation = "TN",
        Country = country2
    },
    new State()
    {
        Name = "Texas",
        Abbreviation = "TX",
        Country = country2
    },
    new State()
    {
        Name = "Utah",
        Abbreviation = "UT",
        Country = country2
    },
    new State()
    {
        Name = "Vermont",
        Abbreviation = "VT",
        Country = country2
    },
    new State()
    {
        Name = "Virginia",
        Abbreviation = "VA",
        Country = country2
    },
    new State()
    {
        Name = "Washington",
        Abbreviation = "WA",
        Country = country2
    },
    new State()
    {
        Name = "West Virginia",
        Abbreviation = "WV",
        Country = country2
    },
    new State()
    {
        Name = "Wisconsin",
        Abbreviation = "WI",
        Country = country2
    },
    new State()
    {
        Name = "Wyoming",
        Abbreviation = "WY",
        Country = country2
    },
};

usaStates.ForEach(s => context.States.Add(s));