T4 Tutorial: Reusing code generators on multiple projects


This post is a part of the series that introduces code generation with Text Templates (also known as T4 Templates) in Visual Studio using C# and Visual Basic; explains how to create reusable templates and combine them in complex code generators. In order to follow examples in this article, you need to have Visual Studio 2008 Standard Edition or higher, SQL Server 2005 or later, T4 Toolbox and T4 Editor installed on your computer.

Introduction

Code Generation Solution In the previous article of this series, we created CrudProcedureGenerator.tt – a reusable code generator that produces CRUD stored procedures for all tables in a given SQL Server database. Each type of stored procedure is produced by a separate templateDeleteProcedureTemplate.tt, InsertProcedureTemplate.tt or UpdateProcedureTemplate.tt. The Generator and Templates use SMO (SQL Server Management Objects) to retrieve table and column information from the database. We have also created a simple code generation file shown below to generate CRUD stored procedures for all tables in the Northwind sample database.

C#
<#@ template language="C#v3.5" hostspecific="True" debug="True" #>
<#@ output extension="txt" #>
<#@ include file="T4Toolbox.tt" #>
<#@ include file="CrudProcedureGenerator.tt" #>
<#
    CrudProcedureGenerator generator = new CrudProcedureGenerator();
    generator.DatabaseName = "Northwind";
    generator.Run();
#>

Visual Basic
<#@ template language="VBv3.5" hostspecific="True" debug="True" #>
<#@ output extension="txt" #>
<#@ include file="T4Toolbox.tt" #>
<#@ include file="CrudProcedureGenerator.tt" #>
<#
    Dim generator As CrudProcedureGenerator = New CrudProcedureGenerator()
    generator.DatabaseName = "Northwind"
    generator.Run()
#>

In its current form, this code generation solution will work well for producing CRUD stored procedures in a single application development project. This article will illustrate how it can be reused across multiple projects and multiple teams.

Source control

It is important to keep both generated source code and code generation files under source control. During initial development of the code generation solution, the code generation files are typically stored side-by-side with the generated files.

image

However, as soon as the code generation files need to be reused outside of the original project where they were developed, they need to be stored separately and treated as a separate project.

Separate code generation files from project files

  • Create a new folder called CodeGeneration and move reusable code generation files from the original project folder into the new folder.

image

Note that NorthwinProcedures.tt remained in its original location. This file relies on reusable code generation files to generate SQL scripts specific to a particular project, Northwind database in this example. In other words, NorthwinProcedures.tt is project-specific and should be stored with the rest of this project’s source code.

Use relative file paths

It is important to rely on relative file paths in team environment to allow for differences in computer configurations of different team members. However, after moving reusable code generation files out of their original location, we will get an error trying to regenerate NorthwinProcedures.tt.

image

As you can see in the source code of NorthwinProcedures.tt at the top of this article, it uses include directive with a relative path to pull in the contents of CrudProcedureGenerator.tt. Although we can fix the immediate problem by changing the NorthwinProcedures.tt to refer to CrudProcedureGenerator.tt as “..\CodeGeneration\CrudProcedureGenerator.tt”, support for relative file paths in the include directive is limited just one level of nesting and it will not be able to find DeleteProcedureTemplate.tt, IncludeProcedureTemplate.tt and UpdateProcedureTemplate.tt included by CrudProcedureGenerator.tt. We can work around this limitation by adding path to shared code generation files to the list of known IncludeFolders.

  • Add IncludeCodeGeneration string value to the HKLM\Software\Microsoft\VisualStudio\9.0\TextTemplating\IncludeFolders\.tt registry key. On a 64-bit OS, this registry key is located under HKLM\Software\Wow6432Node.
  • Enter full path to the CodeGeneration folder as the value data.

image

Visual Studio will attempt to resolve relative file paths used in the include directive by combining them with the known IncludeFolders. As long as each developer working on the project has the path to the CodeGeneration folder added to the registry, they will be able to use the shared code generation files without having to modify them or using absolute paths in files themselves.

Reusing code generator on another project

At this point, all we need to start using CrudProcedureGenerator on another project is create a code generation file similar to NorthwinProcedures.tt and specify a different database in it.

C#
<#@ template language="C#v3.5" hostspecific="True" debug="True" #>
<#@ output extension="txt" #>
<#@ include file="T4Toolbox.tt" #>
<#@ include file="CrudProcedureGenerator.tt" #>
<#
    CrudProcedureGenerator generator = new CrudProcedureGenerator();
    generator.DatabaseName = "Pubs";
    generator.Run();
#>

Visual Basic
<#@ template language="VBv3.5" hostspecific="True" debug="True" #>
<#@ output extension="txt" #>
<#@ include file="T4Toolbox.tt" #>
<#@ include file="CrudProcedureGenerator.tt" #>
<#
    Dim generator As CrudProcedureGenerator = New CrudProcedureGenerator()
    generator.DatabaseName = "Pubs"
    generator.Run()
#>

imageThe resulting structure of folders and projects in your solution may look as shown on the right. Complete source code is available for download at the bottom of the article.

Reusing code generator on another team

In this article we assumed that both projects using the shared code generator are stored in the same source control repository. This may be the case when the same team is working on both projects. If separate teams are working on these projects, they may be using separate source control repositories. Only one repository should be used for development and maintenance of the code generation files. This repository stores the “master” copy of the code generation files. Other teams will typically store a copy of these files in their own source control repository to preserve complete source code history of their project. As improvements are made to the master copy, other teams can upgrade their copy when it’s appropriate for their project.

Conclusion

Code generators themselves are also code that you have to maintain. All coding best practices apply to code generators as well. In particular, it’s a good idea to store code generators in your source code repository; it’s a good idea to use relative path references and it’s a good idea to treat code generators as separate deliverables as soon as you start using them on multiple projects. T4 makes these tasks easier by allowing you to create code generators as pure text source files that don’t need to be compiled or have an installation program. It does have some limitations that present challenges, namely the resolution of relative file paths in the include directive. However, the workarounds are simple enough to manage.

Code generators are also software tools used by people. As such, they need to be easy to use. T4 Toolbox helps by allowing you to use a single code generator that produces multiple output source files and thus reducing the number of code generators you have to use in a given project. It also helps by adding generated output files to source control automatically and by checking them out when a regenerated output file changes.

One particular aspect that affects ease of use is error handling. As a code generator gets adopted by other people, it becomes increasingly important for it to handle error conditions in a manner that helps the users. This is the topic of the next article in this series.

Download

Source code, C#: Initial, Completed

Source code, Visual Basic: Initial, Completed


Write a Comment

Take a moment to comment and tell us what you think. Some basic HTML is allowed for formatting.

Reader Comments

The approach with the registry is great but it only seems to work if you use visual studio to do the generation (i.e. ctrl+s on the NorthwindStoredProcedures.tt). If you try and run NorthwindStoredProcedures.tt via the command prompt using TextTransform.exe, it doesn’t work.

Note in, the end I do not want to execute the templates via the command prompt, but I am creating my own wrapper around which I can trigger a generation via a thick client app.

[…] Sych on T4 Tutorial: Reusing code generators on multiple projects: and even more grist for the […]

I appreciate the workaround. This is a BIG problem in the core T4 implementation at the VS.NET level, IMHO– they really need to fix that. To have to tell all devleopers on the development team to hack their Registry to do this is not nice and will likely meet with resitance. Can one use absolute paths in the include paths? I know this might sound silly; but, our dev machine build guarantees all our code is in “C:\Code” on every machine; so, if absolute paths work, then I might go that route. Thoughts?

Yes, include directive supports absolute paths. I hope this limitation will be addressed in Visual Studio 2010.

Nice. Absolute patsh work like a charm. This stuff is great. Thanks so much for pointing it out, etc, Oleg. I now have a base class generator for tables with a CRUD implementation and a GetByUniqueKey option, along with a ViewBaseClass generator, and a Utility generator, and etc. Nice. I will try to find time to package it and upload it at http://www.codeplex.com/t4toolbox/ if I can make it “useful to others”. Thank you. — Mark Kamoski

BTW, in case anyone is interested, I followed Oleg’s instructions above but I added the “reusable files” to a new custom class library project so I could “Add Existing Project” to my Solution and that works great. That “class libary” is just a placeholder to hold the reusable TT files and it makes adding them to a solution a bit easier, IMHO. There is no DLL created, probably because it has no classes in it. Anyway, that works for me– but if anyone sees an issue, then please let me know. Thank you. — Mark Kamoski

Updated source code to be compatible with version 9.10 of the T4 Toolbox.

Thank you for the articles. They have been most useful.

If you are running a 64-bit OS, the key is [HKLM\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\9.0\TextTemplating\IncludeFolders\.tt]

It took me a little while to determine why your advice did not work.

Thanks again.

Thanks, Bill. This is a good point. I have updated the article.

Great articles Oleg!
I have 2 questions regarding this post:
1. It’s very clear to me that I need to store the code generation files in the source control, but I don’t understand why I need to keep the *generated code* there too. I would think that the code generator should be run in a pre-build step.
2. Isn’t it possible to compile all the reusable stuff (everything except for the script) into a dll, and thus avoid the path issue? Also, it will be easier to deploy and re-use between projects and avoid different teams from evloving the code in different directions (because the source code of the generators won’t be copied between source repositories, only the binary).

Arnon, to answer your questions,

1. My goal is to be able to pull a labeled version from the source control repository and recreate the build successfully. If I have to re-generate the code, I want both model and code generation templates in repository. If I can’t accomplish that – let’s say that my model is a database that I cannot store in the repository, my next best option is to store the generated code itself under source control.

2. Yes, you can compile the code generation templates and redistribute them in binary form. In that case, you have to treat it as an external binary reference in every project that uses them. Unless you store these binaries under source control, this approach actually increases the risk of you not being able to reproduce a build later. I suspect your preference in choosing the approach will be based on whether you need to support prior versions of whatever you are building or not.