Code-generation DSL with T4 (Text Templates)

Sometimes you have to write many repeatable code. Good examples are data/message contracts for the web-services or data access layer. This often means writing dozens properties, classes … boring ….

One of the solutions to this problem is code generation. In short words you are taking some data, make transformation and as output you are getting code file in your favorite language. In the following post I will describe code generation with T4 (Text Templates) and DSL (Domain Specific Language) that will be the input data.

Demo Problem

For demonstration purposes I will use the real problem form my ongoing work. We are working on much componentized system. Each component has its own deployment requirements. To make deployments as flexible as possible we use .NET Configuration. The only problem with .NET Configuration is fact that 80% of the custom ConfigurationSection code is useless. Looks like a lie? Well, to prove please view here. This is regular section. Most of the stuff is duplicated. Anyway, let us return to problem.

So we need generate configuration section. The only data required as input is:

NameType
Serverstring
Databasestring
Usernamestring
Timeoutint

As you may see, most of the attributes have type string so generator should assume string as default type.

DSL

First of all we need empty T4 template (for details - T4 (Text Templates)”).

We will start with simple structure that holds support information like name of the .NET configuration section as well as list of the configuration attributes. This will be our DSL. Code worth a thousand words:

<#+
// DSL
public class Section{
	public string Namespace {get;set;}
	public string Name {get;set;}
	public string Path {get;set;}
	public IList<Property> Properties = new List<Property>();
}
public class Property{
	public Property(){
		Type = "string";		
	}
	public string Name {get;set;}
	public string Type {get;set;}
}

#>

We need to embed this DSL to the template, to do this we will use <#+ ... #> section. This is very similar to the ASPX code <script runat="server"> ... </script>.

Data defined with DSL

Next is actual data.

<# var section = 
	new Section
	{
		Namespace = "Community.Example",
		Name = "DataConnectionConfiguration",
		Path = "community/data",
		Properties = 
		{
			new Property{Name = "Server"},
			new Property{Name = "Database"},
			new Property{Name = "Username"},
			new Property{Name = "Timeout", Type = "int"}			
		}
	};
#>

As you can see, this is just variable and plain C# code. You also may noticed that there is no +. This is because this code is part of the actual template.

Template - Code Generation

So we have DSL, and we have data. Time to do actual code generation.

namespace <#= section.Namespace #>
{
	using System.Configuration;
	using System.Diagnostics.Contracts;

	public class <#= section.Name #> : ConfigurationSection
	{			
<# foreach(var property in section.Properties) { #>	
		[ConfigurationProperty(
			"<#= CamelCase(property.Name) #>",
			IsRequired = true)]
		public <#= property.Type #> <#= property.Name #>
		{
			...
		}
<# } #>			
	}
}

This is just markup code and includes of the control code. We have access to the variable section that holds our data, so we can do virtually anything.

Right now we have complete template. It generates .NET Configuration Section. You even can download the whole file from here. But wait a minute. We can make this stuff more useful.

Making stuff usable

Right now we have single file. That means when we will need another section, we have to copy this template (Copy Code Refactoring). Well, not really true. The T4 support includes. We can use it to separate template with DSL and actual data.

So at the end we will have 1 + n files. Where n is a number of sections you need:

File #1, ConfigurationSection.ttinclude

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly Name="System.Core.dll" #>
<#@ import namespace = "System.Collections.Generic" #>
<#@ output extension=".cs" #>
namespace <#= section.Namespace #>
{	
	// Bla-bla-bla, template goes here....
}
<#+
// DSL
public class Section{
	// Bla-bla-bla, DSL goes here...
}
#>
<#+
// Helper methods
private static string CamelCase(string input)
{
	// Bla-bla-bla, Helper methods goes here...
}
#>

And file #N, DataConnection.tt

<# var section = 
	new Section
	{
		// Bla-bla-bla, Data goes here...
	};
#>
<#@ include file="..\ConfigLanguage\ConfigurationSection.ttinclude" #>

F-e-e-e-w. That is all for now.

As always (second time) you can find result template and usage example on GitHub.

Let me know if this stuff was usefull.