Generating Smarter Visual Studio Code Snippets Using T4 Templates

The last formal tip in The Pragmatic Programmer urges the reader to “Sign Your Work”, and states that, at the very least, each code file should have the author’s name and the creation date as a comment at the top. I was looking for ways to do this routinely in Visual Studio, and came up with a trick where the ends probably no longer justifies the means, but maybe somebody else can use this concept for something more complex than generating header comments.

Visual Studio Snippets

Right-clicking in a code file in Visual Studio allows you to Insert Snippet. Default Snippets include things like constructors and class definition boilerplates. You can also create your own snippets – one way that I’ve used before is the Snippet Editor originally by Bill McCarthy. The simplest way is to highlight some code in Visual Studio, right-click and select Export as Snippet. XML code defining your Snippet is generated and stored, by default, in the Code Snippets folder in your Visual Studio documents folder.

I wanted to have a Snippet for inserting simple author headers like these into my code files:

// <author>Pieter Muller</author>
// <date>2012-11-09</date>

However, Snippets cannot execute method calls, and so I had no way of retrieving the current date for export. This is where the T4 templates come in…

T4 Templates

T4 Templates offer built-in code generation in Visual Studio. The templates support a great deal of flexibility, as they can access a number of data sources, and allow the execution of .NET code while generating output. They are frequently used to dynamically generate database interaction code depending on a schema. I’m not an expert, but if you would like to be one, Oleg Sych’s tutorials here would probably be a good place to start. Scott Hanselman has also blogged about them here and here.

The T4 code that I am interested in, is this:

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".txt" #>
//    <author>Pieter Muller</author>
//    <date><#=DateTime.Now.ToString("yyyy-MM-dd")#></date>

This generates exactly the header I want, using DateTime to get the current date. However, there is a catch – T4 Templates are intended to output entire files, and I could not find a way to insert a snippet of T4 output in a code file, except to copy & paste, despite asking about it on StackOverflow

Using T4 to Generate a Snippet

The solution I came up with is to write a T4 template that doesn’t generate the desired code directly, but instead generates a Snippet that can insert the desired code. The T4 code then writes the generated Snippet definition to the Visual Studio Snippet folder, allowing me to right-click and Insert Snippet wherever I want.

First, write the header comment the way you want it, then right-click and Export as Snippet. Change the name to something sensible, and save the result. Open the saved file in a text editor. For simplicity, I removed the unused XML elements, and was left with this:

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
      <Title>Author and Date Header</Title>
      <Author>user</Author>
    </Header>
    <Snippet>
      <Code Language="csharp"><![CDATA[// <author>Pieter Muller</author>
// <date>2012-11-09</date>]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

Next, right click your project in the Solution Explorer, click Add, select New Item, and add a Text Template to your project. Paste the Snippet XML code from above into the template.

Note that the xml tag in the Snippet definition’s first line will confuse the T4 template parser, so we need to replace it with a manual WriteLine call, which we wrap in <##> tags, as shown in line 3 below.

We also replace the hard coded date with a call to DateTime.Now, shown below in line 15, this time wrapped in <#= #> tags. Your T4 file should now look like this:

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".txt" #>
<# WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); #>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
      <Title>Author and Date Header</Title>
      <Author>user</Author>
    </Header>
    <Snippet>
      <Code Language="csharp"><![CDATA[// <author>Pieter Muller</author>
// <date><#=DateTime.Now.ToString("yyyy-MM-dd")#></date>]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

You can test this by opening the .txt file nested under the template file in the Solution Explorer – the result should be identical to your original Snippet XML.

Using T4 to Copy the Snippet Code

Lastly, we add another set of commands inside <# #> tags at the bottom of the T4 Template, that reads the output file and writes it to the Snippets folder. Unfortunately, this requires some hard coded paths – if you have any ideas on improving this, let me know:

<#  
  System.IO.StreamReader sr = new System.IO.StreamReader(@"C:\Users\Pieter\Documents\Visual Studio 2010\Projects\SnippetDemo\SnippetDemo\AuthorAndDateHeaderSnippetGenerator.txt");
  string sourceText = sr.ReadToEnd();
  sr.Close();

  System.IO.StreamWriter sw = new System.IO.StreamWriter(@"C:\Users\Pieter\Documents\Visual Studio 2010\Code Snippets\Visual C#\My Code Snippets\AuthorAndDateHeader.snippet");
  sw.WriteLine(sourceText);
  sw.Close();
#>

Keep in mind that you have to compile the T4 Template twice to push changes to the Snippet – the first run generates the Snippet definition, and the second run writes the first run’s output to the Snippets folder.

The complete T4 Template is shown below. Have fun, and if you use this for something interesting, please leave a comment!

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".txt" #>
<# WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); #>
  <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
      <Header>
        <SnippetTypes>
          <SnippetType>Expansion</SnippetType>
        </SnippetTypes>
        <Title>Author and Date Header</Title>
        <Author>user</Author>
      </Header>
      <Snippet>
        <Code Language="csharp">
          <![CDATA[// <author>Pieter Muller</author>
// <date><#=DateTime.Now.ToString("yyyy-MM-dd")#></date>]]>
        </Code>
    </Snippet>
    </CodeSnippet>
  </CodeSnippets>
<#  
  System.IO.StreamReader sr = new System.IO.StreamReader(@"C:\Users\Pieter\Documents\Visual Studio 2010\Projects\SnippetDemo\SnippetDemo\AuthorAndDateHeaderSnippetGenerator.txt");
  string sourceText = sr.ReadToEnd();
  sr.Close();

  System.IO.StreamWriter sw = new System.IO.StreamWriter(@"C:\Users\Pieter\Documents\Visual Studio 2010\Code Snippets\Visual C#\My Code Snippets\AuthorAndDateHeader.snippet");
  sw.WriteLine(sourceText);
  sw.Close();
#>
Advertisements
This entry was posted in Software Development and tagged , , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s