T4 - Text Template Transformation Toolkit CvsReader
While watching Scot Hansleman presentation about MVC I discovered great feature in Visual Studio 2010. T4 is an code generation engine. It translates template files (“*.tt”) , using scripts created in C# or VB , to C# , VB , T-SQl , Txt files etc.
You can generate a lot of different things :
-
Dynamic classes which are changing based on the settings file
-
CRUD procedures which are generated dynamically
-
Generated Unit Tests (that would be cool)
I have made a simple template which generates Class that automates reading the CVS files. It’s a simple project created just 4fun.
CVS format file :
ID,Nazwa,Cena,Ilosc,Data,Imie
int,string,decimal,int,DateTime,string
1,Truskawki,100,1,10-10-2010,Michal
2,Pomarancze,200,2,11-02-2011,Stefan
First line defines column names and second line specifies their types.
Template
Templates are stored in “.tt” files. Every “.tt” files starts with a “template” directive containing options. You can specify language used to analyze file . You also need to load the assemblies used by the script blocks in template. I just need System assembly for IO operations.
With Visual Studio 2010 there is a special item type “Text Template”. When you create this item VS will ask you if you want to run the template. T4 engine generates new files every time you save “changes to the “*.tt” file.
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System" #>
Simple Class
Let’s create a simple container class which stores data defined in the CVS . Column name and type is defined in the first two lines.
<#
string columns = null;
string types = null;
System.IO.StreamReader re = null;
string absolutePath = Host.ResolvePath("Data.cvs");
if(System.IO.File.Exists(absolutePath))
{
re = System.IO.File.OpenText(absolutePath);
columns = re.ReadLine();
types = re.ReadLine();
}
#>
This Script loads first two lines from a file and loads them into the columns and types string. As you can see this code is in “C#”. Every script in the template must be within the “<# #>”.
With column names and types we can start the container class generation. Let’s call it “CvsData”.
using System;
using System.Collections.Generic;
public class CvsData
{
<#
string [] columntype = types.Split(',');
int counter = 0;
foreach(string s in columns.Split(','))
{
Write("tpublic " + columntype[counter] + " " + s + " {get;set;}n");
counter++;
}
#>
}
Code outside the “<# #>” scope is treated just like simple text. Inside script I m using the Write() function to create text. For each column script generates property.
Generated “*.cs” file.
using System;
using System.Collections.Generic;
public class CvsData
{
public int ID {get;set;}
public string Nazwa {get;set;}
public decimal Cena {get;set;}
public int Ilosc {get;set;}
public DateTime Data {get;set;}
public string Imie {get;set;}
}
Now I can compile this file and use the CvsData class.
Simple Reader
I just want to read data and return it as a List with CvsData objects.
public static class CvsReader
{
public static List<CvsData> LoadData(string filePath)
{
List<CvsData> returnData = new List<CvsData>();
if(System.IO.File.Exists(filePath))
{
System.IO.StreamReader re = System.IO.File.OpenText(filePath);
string line = String.Empty;
//Miss first two lines
re.ReadLine();
re.ReadLine();
while((line = re.ReadLine()) != null)
{
string [] values = line.Split(',');
returnData.Add(new CvsData(){
<#
counter = 0;
foreach(string s in columns.Split(','))
{
Write("tttttt"+ s + " =");
if(!columntype[counter].Contains("string"))
{
Write(columntype[counter]+".Parse");
}
Write("(values["+counter+"]),n");
counter++;
}
#>
});
}
re.Close();
}
return returnData;
}
}
Generated reader:
public static class CvsReader
{
public static List<CvsData> LoadData(string filePath)
{
List<CvsData> returnData = new List<CvsData>();
if(System.IO.File.Exists(filePath))
{
System.IO.StreamReader re = System.IO.File.OpenText(filePath);
string line = String.Empty;
//Miss first two lines
re.ReadLine();
re.ReadLine();
while((line = re.ReadLine()) != null)
{
string [] values = line.Split(',');
returnData.Add(new CvsData(){
ID =int.Parse(values[0]),
Nazwa =(values[1]),
Cena =decimal.Parse(values[2]),
Ilosc =int.Parse(values[3]),
Data =DateTime.Parse(values[4]),
Imie =(values[5]),
});
}
re.Close();
}
return returnData;
}
}