Comparison of Text Templates and Code DOM

In this post I would to present a basic text template, and show how it could be easier to generate code using text templates than with the code DOM.

 

A text template is a document with special escape sequences to indicate where variable portions of text should be inserted. The escape sequences can also be more complex by having code to generate the text that is to be included. This is quite useful in the dynamic generation of web pages but it can also be applied to other types of text documents. In particular, the ability to enable automatic generation of code could ease the burden of repetitive coding tasks.

Why not use the Code DOM?

You may be thinking that .Net framework already has a way to generate code files, the code DOM (System.CodeDOM). It even can render in more than one language. Why not use it?

The code DOM is a .Net API that allows you to build an object model that represents your code, and gives you the ability to either compile it directly to a binary or generate source code as output. This is very powerful, and there are cases where the code DOM is useful, but there are cases where use of text templates is better.

I would like to illustrate that it could take you more time, and more code, to generate the code DOM equivalent of some existing code than creating a text template. As an example, let’s take the common hello world console application shown below as a starting point:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

namespace HelloWorld

{

    class Program

    {

        static void Main(string[] args)

        {

            Console.WriteLine("Hello world!");

        }

    }

}

To generate these 15 lines of code, it requires 73 lines of code to generate those lines from the code DOM. The generation code is shown below:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.CodeDom;

using System.CodeDom.Compiler;

using System.IO;

using Microsoft.CSharp;

namespace MakeHelloWorld

{

    class Program

    {

        static void Main(string[] args)

        {

            CodeCompileUnit unit = new CodeCompileUnit();

            CodeNamespace ns = new CodeNamespace("HelloWorld");

            unit.Namespaces.Add(ns);

      // Using directives

            ns.Imports.Add(new CodeNamespaceImport("System"));

            ns.Imports.Add(new CodeNamespaceImport("System.Collections.Generic"));

            ns.Imports.Add(new CodeNamespaceImport("System.Linq"));

            ns.Imports.Add(new CodeNamespaceImport("System.Text"));

            // Add class Program

            CodeTypeDeclaration myClass = new CodeTypeDeclaration("Program");

            myClass.IsClass = true;

           

            // Add Main method

      CodeMemberMethod method = new CodeMemberMethod();

            method.Name = "Main";

            method.Attributes = MemberAttributes.Static;

            CodeParameterDeclarationExpression param =

                new CodeParameterDeclarationExpression(typeof(string[]), "args");

            method.Parameters.Add(param);

            // Add statement to main method

            CodeMethodInvokeExpression statement = new CodeMethodInvokeExpression(

                new CodeTypeReferenceExpression("System.Console"),

                "WriteLine",

                new CodePrimitiveExpression("Hello World!"));

            method.Statements.Add(statement);

            myClass.Members.Add(method);

            ns.Types.Add(myClass);

            // Render the code

            CSharpCodeProvider provider =

               (CSharpCodeProvider) CodeDomProvider.CreateProvider("csharp");

            

            // Create a TextWriter to a StreamWriter to the output file.

         using (StreamWriter sw = new StreamWriter("HelloWorld.cs", false))

            {

                IndentedTextWriter tw = new IndentedTextWriter(sw, " ");

           

                // Generate source code using the code provider.

                CodeGeneratorOptions options = new CodeGeneratorOptions();

                options.BracingStyle = "C";

                options.BlankLinesBetweenMembers = false;

                provider.GenerateCodeFromCompileUnit(unit, tw, options);

                // Close the output file.

                tw.Close();

            }

        }

    }

}

It took me about 20 minutes to write the code with the documentation open to get the result I wanted. I always forget where the setting to change the bracing formatting to the "C" style instead of the block style is and have to look it up. In any case you should see that it may not be an enjoyable task to generate 1000 lines of code like this. Note that you don't have much flexibility over the formatting of the code that is generated. For those that are curious, this is what I got as output of this program:

//------------------------------------------------------------------------------

// <auto-generated>

// This code was generated by a tool.

// Runtime Version:2.0.50727.4952

//

// Changes to this file may cause incorrect behavior and will be lost if

// the code is regenerated.

// </auto-generated>

//------------------------------------------------------------------------------

namespace HelloWorld

{

  using System;

    using System.Collections.Generic;

    using System.Linq;

    using System.Text;

   

    public class Program

    {

        static void Main(string[] args)

        {

            System.Console.WriteLine("Hello World!");

        }

    }

}

If you have a lot of code, or code that doesn't vary a lot, use of text templates can help save time by concentrating only on the code that does vary. If I only wanted to change the message that was printed on the console in the message above, only one line of code would vary in my generation program, but I still had to spend time writing the rest of the lines.

Although the code works, I do have one concern that I don’t see has a clear solution. If you build a nice code DOM and persist it using binary serialization, is it guaranteed that you will be able to read it back with a later version of the .Net framework? This seems like a grey area and I'd rather be able to use XML serialization so that I have a backup plan using XSL or text templates to change the format and load my data in a later version of my application. Last time I checked, the code DOM didn't easily support serialization to XML or some other format where a plan B could be formulated for migrating the data.

There may be tricks in use of the code DOM that I missed, but in general I hope that you see how use of the code DOM could be tedious in some circumstances and that you are now motivated to understand text templates.

Text Templates

The text template tools are included with the Visualization and Modeling tools SDK. You can download them at the following address for Visual Studio 2010:

https://www.microsoft.com/downloads/en/details.aspx?FamilyID=0def949d-2933-49c3-ac50-e884e0ff08a7

Previous versions of the SDK are named the DSL or Domain Specific Language SDK. A domain specific language is a language designed to be applied in a domain specific scenario and is different from a general purpose language like C# which can be used to write software in any domain. DSL's are an interesting topic to explore in another post. For now, let's get back to text templates.

Template Example

Let's take a look at a sample template for the hello world code we were using previously:

<#@ output extension=".cs" #>

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

namespace HelloWorld

{

    class Program

    {

        static void Main(string[] args)

        {

            Console.WriteLine("<#= Environment.MachineName #>");

        }

    }

}

The template looks a lot like the default hello world console application we saw earlier except for two areas enclosed in <# #>. These are areas of the template have special behavior. In this case, '<#@' starts a directive to the processor that indicates the output file extension should be for C# code files instead of the default '.txt' for text files. '<#=' indicates that the expression enclosed should be evaluated and inserted in the template at this point in the file. Since expressions are written in C#, this gets the machine name and inserts it into the template at this location. To process the template, I issued the following command at a power shell prompt:

PS> .\TextTransform.exe .\HelloWorld.tt

The resulting file if issued on a computer with name MYMACHINE is:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

namespace HelloWorld

{

    class Program

    {

        static void Main(string[] args)

        {

            Console.WriteLine("MYMACHINE");

        }

    }

}

Summary

This example begins to show why it can be easy to create templates, especially when the output varies only slightly as in this case. In the template, the default behavior is to copy everything in the template to the output file exactly as it is unless there are special instructions or variable portions marked with <# and #>. White space is significant in the template portion and has to be carefully considered around insertion areas.

 

In the next post I will continue with text templating, and show how to do more advanced things with templates.

 

Series

Start of series previous next