T4 Tutorial: Making code generators extensible


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

When different developers or teams use the same code generator on different projects, it is likely that they will want to generate code differently. Consider the CRUD stored procedure generator we created previously. Here is a DELETE stored procedure it generates for Orders table in the Northwind sample database.

create procedure Orders_Delete
    @OrderID int
as
    delete from Orders
    where
        OrderID = @OrderID

Note that this T-SQL code will successfully create the stored procedure if it doesn’t exist. However, it will fail if the stored procedure has already been created. In order to handle both scenarios, we could change this code to look like this.

if  exists (select * from sys.objects
            where object_id = object_id(‘Orders_Delete’)
              and type in (‘P’, ‘PC’))
    drop procedure Orders_Delete
go  

create procedure Orders_Delete
    @OrderID int
as
    delete from Orders
    where
        OrderID = @OrderID

Although the new T-SQL file will handle both scenarios, the extra code it now contains is not necessary if you are using Database edition of Visual Studio Team System. The Database edition automatically generates different deployment scripts depending on the current schema in the target database. This additional code would only add extra code and complexity to the database project, making it not only unnecessary but undesirable.

Let’s assume that author of the original generator of CRUD stored procedures was targeting the Database edition of Visual Studio and did not implement code that drops existing procedures. How would a developer who doesn’t have the Database edition add this capability to the code generator?

It is always possible to simply modify code of the original generator and it work the exact way you need it. However, by modifying the code someone else produced, you are taking on the responsibility of maintaining it. If you need to use a newer version of the original generator, you will have to re-implement your customizations in the new version of its code. For small code generators or short-term projects, this may be a valid approach. However, modifying complex code generators in a long-term project may significantly increase the effort required to maintain it over time. Luckily, we can use proven object-oriented design techniques such as inheritance and encapsulation to allow customizing code generators without resorting to hacking their source code.

Using virtual methods to make templates extensible

Here is an abbreviated version of template that generates delete stored procedure in the original CRUD generator.

C#
<#+
public class DeleteProcedureTemplate : Template
{
    // …
    public override string TransformText()
    {
        // …
#>
create procedure <#= table.Name #>_Delete
<#+
        // …
#>
as
    delete from <#= table.Name #>
    where
<#+
        // …
        return this.GenerationEnvironment.ToString();
    }
}
#>

Visual Basic
<#+
Public Class DeleteProcedureTemplate
    Inherits Template 

    ‘ …
    Public Overrides Function TransformText() As String
        ‘ …
#>
create procedure <#= table.Name #>_Delete
<#+
        ‘ …
#>
as
    delete from <#= table.Name #>
    where
<#+
        ‘ …
        Return Me.GenerationEnvironment.ToString()
    End Function
End Class
#>

Notice that TransformText method is virtual. We can change its behavior by creating a descendant class and overriding the method like so:

C#
<#+
class MyDeleteProcedureTemplate: DeleteProcedureTemplate
{
    public override string TransformText()
    {
#>
if  exists (select * from sys.objects
            where object_id = object_id(’<#= this.TableName #>_Delete’)
              and type in (’P', ‘PC’))
    drop procedure <#= this.TableName #>_Delete
go
<#+
        return base.TransformText();
    }
}
#>

Visual Basic
<#+
Class MyDeleteProcedureTemplate
    Inherits DeleteProcedureTemplate 

    Protected Overrides Function TransformText()
#>
if  exists (select * from sys.objects
            where object_id = object_id(’<#= Me.TableName #>_Delete’)
              and type in (’P', ‘PC’))
    drop procedure <#= Me.TableName #>_Delete
go
<#+
        Return MyBase.TransformText()
    End Function
End Class
#>

Now we only need to replace the original template before running the CRUD generator in NorthwindProcedures.tt.

C#
<#
    CrudProcedureGenerator generator = new CrudProcedureGenerator();
    generator.DeleteTemplate = new MyDeleteProcedureTemplate();
    generator.DatabaseName = "Northwind";
    generator.Run();
#> 
Visual Basic
<#
    Dim generator As CrudProcedureGenerator = New CrudProcedureGenerator()
    generator.DeleteTemplate = New MyDeleteProcedureTemplate()
    generator.DatabaseName = "Northwind"
    generator.Run()
#>

In other words, we have changed behavior of a third-party code generator defined in CrudProcedureGenerator.tt and DeleteProcedureTemplate.tt without directly modifying the third-party source code. All of our customizations are made in NorthwindProcedures.tt, which is a source file in our project. When new version of the CRUD generator becomes available, we can simply replace the older source files and our customizations will continue to work.

We can repeat these steps to override behavior of the InsertProcedureTemplate and UpdateProcedureTemplate. Completed working source code is available for download at the bottom of this article.

Conclusion

The customization described in this article was made possible by two important design aspects of our CRUD generator. First, generation of each type of stored procedures is encapsulated in a separate Template class with a virtual TransformText method we could override. Second,  the generator class exposed templates as public fields, allowing us to replace individual templates without having to modify or override the generator. This design is a form of Strategy pattern, with template classes serving as Strategies and generator playing the role of Context.

In simple scenarios, when a single Template generates a single logical unit of code, such as a stored procedure, it is sufficient to simply place the code generation logic inside of the overridden TransformText method. In more complex scenarios, a Template may generate multiple units of code, such as a C# class with multiple methods and properties. To allow users to customize generation of a particular unit of code without having to reimplement the entire TransformText, consider creating a separate virtual method to generate each logical unit of code and calling it from the TransformText method. This approach helps authors to reduce the size and complexity of the TransformText method and allows the users to override these virtual methods individually. This design is a form of Template Method pattern, with TransformText serving as Template Method.

In the next article of this tutorial series, we will review some of the additional features the T4 Toolbox framework provides for code generator extensibility, such as conditional code generation and output redirection.

Download


Write a Comment

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

Reader Comments

[…] T4 Tutorial: Making code generators extensible - Oleg Sych looks at implementing extensibility in your T4 templates for code generation. […]

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

Is it possible to do this in another way besides inheritance? so I don’t have to write a template… write a template that extends the template, then write a script to actually generate the file.

It would be nice if you include the Next Article link like in the others articles :D

Thanks for the tip, Rodrigo. I’ve updated the article to include the link.

Oleg

Is it possible to use T4 to generate code for C or C++ ?

Yes, from a C# or Visual Basic project.

[…] template just by overriding a method as you would with any other O-O system.  Oleg Sych has an example of such a system on his excellent blog that works well with previous versions of T4 using nested classes, but I like […]