Udostępnij za pośrednictwem


Two ways to use T4 templates: support code vs. one-time generation

T4 templates have proven to be useful is a whole range of scenarios, and more and more developers are finding interesting things to do with them.

For the most part, all those scenarios fall under two very distinct categories: “support code” versus one-time generation.  Unlike my previous post on CodeDom vs. T4, here we’re not talking about making a choice between two competing technologies, but simply about using T4 in the way that makes sense for a given scenario.

Let’s start with a brief description of the two usage patterns:

Support code: here, a T4 template generates a file that you rarely need to look at, and you should never modify.  Instead, it contains “support code” that you can code against.  A great example of this is my T4MVC template.

One-time generation: here, you are executing a T4 template to generate a file that then becomes part of your project.  This file can then safely be modified.  The T4 template just gave you a starting point.  A great example of this is the Add View dialog in ASP.NET MVC.

I purposely gave two examples that relate to ASP.NET MVC to make a point that the two scenarios are not exclusive, and can play different parts within the same application.

The rest of this post will discuss various aspects of the two scenarios in more details.

 

Using T4 templates to generate “Support Code”

This is the scenario that you are most likely to have encountered when you got introduced to T4 templates.  It has a pretty low barrier of entry as it is directly supported by Visual Studio 2008.  The general flow here is:

  • You add a file with a .tt extension to your VS project
  • VS knows about those files, so it instantly sets it up to use the ‘TextTemplatingFileGenerator’ custom tool (you can see this in the .tt file’s property grid).
  • Whenever you save the .tt file, the template executes, and the generated file becomes a sub-file of the .tt file.

e.g. suppose your Hello.tt file has:

 <#@ Template Language="C#v3.5" #>
<#@ Output Extension=".cs" #>

public static class Hello {
    public static void SayHello() {
<# for (int i=0; i<5; i++) { #>
        System.Console.WriteLine("Hello <#= i #>");
<# } #>
    }
}

In the solution explorer, you will see:

image

And Hello.cs will contain:

 public static class Hello {
    public static void SayHello() {
        System.Console.WriteLine("Hello 0");
        System.Console.WriteLine("Hello 1");
        System.Console.WriteLine("Hello 2");
        System.Console.WriteLine("Hello 3");
        System.Console.WriteLine("Hello 4");
    }
}

Pretty simplistic stuff as far as code generation goes, but there are some key points that need to be made about it.

First and foremost, you should never modify the generated file.  You certainly can go in there and make changes, and no one will stop you.  You can even run your project and your hand modified code will execute.  But guess what will happen the next time you open Hello.tt and save it?  It will re-execute and blow away your carefully modified Hello.cs.  So resist the temptation, and don’t mess with the generated file!

That’s why I named this scenario “Support Code”.  What gets generated is code that is meant to be used by you in the rest of your app.  e.g. in the example above, you could simply call Hello.SayHello() from your console app’s Main.

Now you can take a look at the T4MVC template, which has 1000 lines and does pretty complex things to make it easier to write your MVC app.  But in essence, it works the exact same way as this trivial example: it’s a .tt file which generates a .cs file.  You rarely look at the .cs file, and you never modify it, but instead you call into the bits and pieces that it generates from your controllers and views.  It will frequently regenerate as you project changes, and that’s really the whole point: it keeps itself up to date so you don’t code against stale code.

 

Using T4 files for one-time generation

In this scenario, our purpose is to help the user create a new file for their project by pre-generating it with some useful content instead of having them start with some generic empty file.

As mentioned above, a great example of this is the ASP.NET MVC Add View functionality.  It is described in quite some detail in this post, so you may want to check it out.  I’ll use this as an illustration in this section, though of course a similar technique can be applied to other domains.  Here the general flow is:

  • You would like to generate a new View for their app

  • Instead of giving you a generic new view (which a regular item template would do), it asks you a bunch of questions:

    • What kind of view would you like, e.g. List, Details, Edit, …
    • What kind of entity is it for, e.g. Product, Customer, …
    • What master page you want to use, etc…
  • It takes all that knowledge and feeds it into a T4 template (suing a custom T4 host; a bit more on this later), which generates the view and adds it where it belongs in your project

In both the “Support Code” scenario and here, a T4 file is used to generate another file, but from here on things become totally different.  In this one-time generation scenario, the resulting file becomes a completely normal file that’s part of your project.  As such, you can freely modify it without fear of it getting overwritten, since the T4 template is no longer in the picture.  It was simply used to create the file to get you started.  It was a “one-time” generation.

One weakness of this model is that you can’t easily rerun the template.  e.g. suppose your Product class gets a new member and you want to update the view to account for it.  If you hadn’t touched to previous generated view, you can certainly delete it and rerun the Add View dialog.  But if you had made any modifications, you’re mostly out of luck, and probably better off updating your view by hand without help from the T4 template.  In spite of this weakness, the model is still very useful to get the user quickly started with their code and guiding them towards the pit of success.

Note that I didn’t say much about exactly how the T4 template gets executed in this scenario.  Unlike the “Support Code” scenario, VS is not the one doing it.  Instead, you have to do some things that are a bit more involved to make it happen.  I won’t go into details about how to write a custom T4 host here as it is beyond the scope of this post, and is well covered by other posts (in particular, check out Oleg Sych’s blog).

One last thing I’ll mention about this model is that the .tt file is normally not part of your project.  Instead, it lives somewhere else, and only its output becomes part of your project.  Well, technically, the .tt file can be in your project for easy editing, but you then have to remove the ‘TextTemplatingFileGenerator’ custom tool, because you really don’t want it to execute on its own (it would surely fail with the custom host).

 

Conclusion

To reemphasize what I said early on, these are not two scenarios that compete with each other and for which you must “pick a side”.  They are just two completely different ways of making use of T4 files.  Both are being used successfully in various projects, and they can live together happily ever after :)

Comments

  • Anonymous
    July 16, 2009
    I would strongly advise against the 'one-time generation' use, and suggest an alternative. One time use results in a whole load of code that you couldn't be bothered to write being generated, which means you now have a load of code you won't be able to maintain. If the generated code needs to change, you can't rerun the script as you'll lose your modifications. Instead what you should do is have it generate code that is extensible, rather than requiring modification (like MS do with partial methods in Linq to SQL). Even better than that is to improve the cleverness of the generator so that it can generate the necessary tweaks from the input the t4 template takes. e.g. if you are generating code by reflecting over an assembly, and one class in particular needs customisation, flag the class with an attribute so the generator can treat it differently. If you don't have control over the assembly, pass an argument to the t4 template.

  • Anonymous
    July 16, 2009
    I guess I didn't really suggest an alternative, just the first method, but what I'm trying to get at is that the second shouldn't really ever be used, with the possible exception of views as designers like to tweak the heck out of views (even then I'd rather have a clever convention based generation system to provide truly consistent look n feel). Don't do 1 time generation in code YOU are going to have to maintain.

  • Anonymous
    July 17, 2009
    Harry, I don't really agree that one-time generation is necessarily a bad thing, although it can be. It all depends how you look at it. If you look at it as a bunch of generated code that you now have to maintain, then indeed it seems bad. Instead, you should look at it as a smarter Item Template, then it becomes a good thing. Think of how you create new files in VS using Add / New. Whether it's a code file, aspx file, or just about any file format, VS doesn't give you an empty file. It gives you a starter file containing a C# class or an aspx file with some basic directives and some markup. But this 'starter content' is pretty static, and not easily customizable. If you instead use T4 to generate this starter file, you can give smarter much starter/dynamic content, and by customizing the T4 file the user can tailor the starter content to be what they prefer.

  • Anonymous
    July 17, 2009
    I use T4 templates in my project to generate teh repeating data-access code from a metadata file and I am loving it.

  • Anonymous
    July 17, 2009
    Hi David, I like you you code listings is that something new on your msdn blog or a new adding ofr live writer? Steve

  • Anonymous
    July 19, 2009
    Steve, I switched to using SyntaxHighlighter, per Hanselman's blog: http://www.hanselman.com/blog/BestCodeSyntaxHighlighterForSnippetsInYourBlog.aspx. It's a totally different approach to code snippets, as it gets formatted on the fly on the client.