Click here to Skip to main content
15,860,972 members
Articles / Programming Languages / C#
Article

T4: Extending the Generated Template Class

Rate me:
Please Sign up or sign in to vote.
4.38/5 (5 votes)
15 Nov 2008CPOL5 min read 73.6K   281   32   15
Taking advantage of the inherits directive in text templates to allow for a clean extension of the generated template class.

Introduction

This article tackles a specific challenge you may encounter when using the Text Templating engine shipped with Microsoft's DSL Tools (which by the way can also be completely used without any DSL definition).

As soon as you start creating your own text templates, you often like to have additional functionality available, e.g., to navigate the data model (domain model) processed by the template, or some kind of advanced string manipulation. If the functionality comes as part of what is called the Generated Template Class (GTC), you could simply write something like this in your template file:

C#
String aValue = <#= ComplexStringManipulation( modelElement ) #>;

In order to be able to call this function, you can add the appropriate extensions to the template file itself in the form of what's called a Class Feature Block. It typically has the form:

XML
<#+
public String ComplexStringManipulation( ModelElement modelElement )
{
    // Do something special here.
}
#>

This approach has two disadvantages: while it certainly is a quick way to add new functionality and in the case of third-party processors that cannot be modified is actually the only way to go about this, the method starts mixing functionality and presentation features. In advanced scenarios, this will not be well maintainable for well-known reasons. Secondly, the template writer has (at the time of writing) no syntax highlighting or syntax check for this code. So to see whether what really is just plain text will actually even compile, you have to run the template. Finally, it may not be desirable to expose such functionality to clients, for reasons like IP protection or to keep support costs down, as at some point, they will start changing the template. One of the features added to DSL Tools for Visual Studio 2010 will be to support precompiled templates, which will reduce the mentioned disadvantage to some degree.

Another approach also mentioned in the DSL Tools book is to create a custom directive processor and extend methods like GenerateTransformCode(), GeneratePostInitializationCode() and so forth. Since the directive processor is the class generating the GTC, you have the possibility to add literal code statements by appending the appropriate strings to the directive processor's string buffer. This leads to statements like:

C#
codeBuffer.AppendLine( @"public String " + 
           @"ComplexStringManipulation( ModelElement modelElement )" );
codeBuffer.AppendLine( @"{" );
codeBuffer.AppendLine( @"    // Do something special here." );
codeBuffer.AppendLine( @"}" );

While this approach removes the disadvantages of exposing and mixing functional code in the template, it is obviously very error prone, will never be nicely highlighted (since the lexer will always treat the statements as pure strings), and feels like we are in code generation stone age. The DSL Tools book then suggests to use CodeDOM, since this will also remove the dependency on a particular programming language, as CodeDOM can emit various .NET languages.

Well, I am too lazy to go through the exercise of listing the CodeDOM statements required to define the function above and do at least some minimal string manipulation inside. Doing it that way is extremely verbose, and not at all something you want to spend your Sunday afternoon with. (If you like, you can send me the CodeDOM code for the function that at least calls one or two string functions to manipulate, e.g., modelElement.Name.)

The next section shows you yet another option which is built into DSL Tools, but does not seem to have been mentioned much so far.

Injecting your Custom Class

This is actually pretty simple: instead of writing plain text that hopefully compiles later on or to spend hours emitting code using CodeDOM, you just create your own regular C# (or any other .NET language) class, which:

  • inherits from TextTransformation, defined in the Microsoft.VisualStudio.TextTemplating namespace, and
  • exposes the new functionality with at least protected visibility.

For our sample above, this would look like so:

C#
namespace MyNamespace
{
    public abstract class MyGtcBase : TextTransformation
    {
        public String ComplexStringManipulation( ModelElement modelElement )
        {
            // Do something special here.
        }
    }
}

To be able to take advantage of methods and properties defined in the new class, you can use the inherits directive in your text template:

XML
<#@ template inherits="MyNamespace.MyGtcBase" #>

The latter directive will result in the fact that your base class is now injected into the inheritance line of the generated template class, i.e., all protected or public features may be used directly in the text template.

The approach shown above gives you full IDE support during development of extensions to the generated template class. It also keeps this code away from the template, and protects the code from being looked at or being changed. The extra effort of creating the class is minimal compared to just using a mixed-in class feature control block. We are using this approach in production generators.

Update: There is an issue with this approach as Oleg Sych explains in his blog: If used inside Visual Studio, the IDE seems to lock the assembly containing the base class for up to 25 times for performance reasons. One way around this is to run another template not referencing the base class. In our project we are actually using a different transformation host (not Visual Studio), so the problem does not occur either.

Using the Code

To see the sample solution in action, do the following:

  1. Make sure you have Visual Studio 2008 and the corresponding SDK installed.
  2. Download the solution provided at the beginning of the article.
  3. Open it and Build All. This step will also register the created assembly in the GAC.
  4. Modify the sample template in the test project and save the template file.

As a result, you will see the output text file (shown as code-behind in the project tree) has changed accordingly.

I hope with this sample, the approach becomes somewhat easier to use.

History

  • 2008-07-10: Initial creation
  • 2008-08-20: Added sample solution and instructions on how to use it
  • 2008-11-14: Modified article

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) BMW AG
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionDo we have to register the base class in the GAC? Pin
FerretOfWrath13-Dec-08 19:57
FerretOfWrath13-Dec-08 19:57 
AnswerRe: Do we have to register the base class in the GAC? Pin
Mike Pagel15-Dec-08 4:17
Mike Pagel15-Dec-08 4:17 
QuestionDoes this work with plain VS 2008? Pin
Richard Maw22-Jul-08 11:39
Richard Maw22-Jul-08 11:39 
AnswerRe: Does this work with plain VS 2008? Pin
Mike Pagel22-Jul-08 21:16
Mike Pagel22-Jul-08 21:16 
AnswerRe: Does this work with plain VS 2008? Pin
Richard Maw23-Jul-08 5:12
Richard Maw23-Jul-08 5:12 
GeneralRe: Does this work with plain VS 2008? Pin
Mike Pagel23-Jul-08 9:31
Mike Pagel23-Jul-08 9:31 
AnswerRe: Does this work with plain VS 2008? Pin
Richard Maw23-Jul-08 10:05
Richard Maw23-Jul-08 10:05 
GeneralRe: Does this work with plain VS 2008? Pin
Mike Pagel20-Aug-08 1:04
Mike Pagel20-Aug-08 1:04 
GeneralRe: Does this work with plain VS 2008? Pin
Richard Maw20-Aug-08 4:32
Richard Maw20-Aug-08 4:32 
GeneralRe: Does this work with plain VS 2008? Pin
Mike Pagel20-Aug-08 5:05
Mike Pagel20-Aug-08 5:05 
AnswerRe: Does this work with plain VS 2008? Pin
Richard Maw20-Aug-08 5:31
Richard Maw20-Aug-08 5:31 
GeneralNo source code Pin
leppie9-Jul-08 22:20
leppie9-Jul-08 22:20 
GeneralRe: No source code Pin
Mike Pagel9-Jul-08 23:50
Mike Pagel9-Jul-08 23:50 
GeneralT4 Pin
The Wizard of Doze12-Jul-08 1:15
The Wizard of Doze12-Jul-08 1:15 
GeneralRe: No source code Pin
Mike Pagel20-Aug-08 3:51
Mike Pagel20-Aug-08 3:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.