Using T4 to Create an AppSettings Wrapper, Part 7

Update 29 July 2013: Refer to this link for updated code supporting Visual Studio 2013 Preview.


In this final article in the series we’re finishing up the deployment projects started last time.  When we’re complete we’ll have a set up projects that can be used to build and deploy T4 templates where needed.  The projects will be able to support any number of templates so maintenance will be simple even as more templates are added.  In the previous article we moved the core template functionality into a library that we can expand upon.  We also set up an item template project to store the root T4 template that someone would use.  In this article we’re going to create the project to store the nested templates (we’ll discuss why later) along with the Visual Studio Extension (vsix) file that will be used to install everything.


Corrections


There are a couple of corrections that need to be made from the source in the last article to prepare for the setup project.


In the AppSettingsTemplate.tt file the assembly reference for the shared assembly needs to include the .dll otherwise the T4 host will assume it is coming from the GAC.  An assembly reference for EnvDTE is also missing so it needs to be added.


<#@ assembly name="TemplateLib.dll" #>
<#@ assembly name="EnvDTE" #>

In the nested template there were still some hard-coded references to the original class name rather than using the ClassName property in the template.  Do the replacement so the template behaves properly based upon the name that is ultimately used.


Nested Template Project


For the nested templates we need to create a new VS Project Template project.  When a developer uses one of the item templates they will only add the core template to their project.  The nested template, and support assembly, will need to be stored in a shared location that the T4 host can find.  The easiest way to do this is to use a VS Project Template project.  All the shared templates will be stored in this project.  We could technically even store the base template class code in this project but I find it easier to keep them separate. 


Add a new project to the solution (Visual C#\Extensibility\C# Project Template) called TextTemplates (the name is not really relevant).  Once the project has been added remove all the files from the project as they will not be needed.  Add a reference to the shared assembly project that was created earlier.


This project mirrors the item template project so set up the same folder structure for each template as needed.  The nested template file will be moved to this project.  Every time a new nested template is added the following steps need to be followed.


  1. Create a subfolder in the project
  2. Add the nested .tt file to the subfolder
  3. For each .tt file set the following properties for the item
    • Build Action = Content
    • Copy to Output = Copy Always
    • Custom Tool = (blank)
    • Include in VSIX = True

There is only one issue with the approach we’re taking – T4 does not know where to find our nested templates and custom assembly.  Therefore we need to update the search path that T4 uses to include the installation path of the package.  The simplest way to do that is to add it to the package definition (.pkgdef) file.  When a package is installed the package definition is processed to allow the package to do any customization it needs.  We can use this feature to update the T4 include path.


Add a new text file to the project.  It’s name must match the name of the project and it should have an extension of .pkgdef (i.e. TextTemplates.pkgdef).  Change the following properties for the item.


  • Build Action = Content
  • Copy to Output = Copy Always
  • Include in VSIX = True

When T4 runs across an assembly or include directive that it cannot find then it uses the registry to search for additional paths.  Each time we add a new template we need to add the path to the template to the list using the .pkgdef file.  Note that the subfolder we use in the project will correspond to the subfolder that the template is installed to so we need to include the full path.  Here’s the code we’ll add to the package definition file.


[$RootKey$\TextTemplating\IncludeFolders\.tt]

"IncludeMyAppSettings"="$PackageFolder$\TextTemplates\AppSettings"


This will add a new key to the registry with the given name and value.  The installer will replace $PackageFolder$ with the installation path for the package.  The project name follows that and the last name is the folder that was used when adding the template to the project.  It is critical that the key name start with “Include” otherwise the T4 host will ignore it.  It must also be unique.  Each new subfolder of nested templates will need a corresponding name-value pair in the definition file.


Some discussion of this can be found in MSDN.  But credit for pointing me in the correct direction when I was trying to figure this out has to go here.


VSIX Project


 


We’re on the home stretch.  We have set up the project for the shared assembly code, defined the item templates that will be available to the developers and got the support templates hooked up to T4.  The final step is to create a VSIX file to install everything.  By far this is the most frustrating part because VSIX is very picky about how things have to work and it can be a bear to work with.


Create a new VSIX Project (Visual C#\Extensibility\VSIX Project) called MyTemplateSetup.  The manifest editor will open up.  The manifest controls several important aspects of the setup including the information the user sees, the files to be installed and the version of the setup.


  1. Set Product Name to the name you want to the extension to appear as in the gallery.
  2. The Product ID must be unique and should already be set.
  3. Set Author to an appropriate value.
  4. The Version should default to 1.0.
  5. Provide a description of the extension.
  6. Since this is a template installer the Tags should probably be set to Templates.
  7. Optionally set the other attributes such as licensing, icon and release notes.

Switch to the Install Targets tab.  This tab indicates what version(s) and edition(s) of Visual Studio the extension supports.  The default is Visual Studio 2012 Pro or higher which should be fine.  Microsoft licensing does not allow third-party extensions to the Express products.  If a newer version of Visual Studio comes out then the version number can be updated.


Switch to the Assets tab.  This is where we identify the files to be installed.  This is also where things can get difficult if the projects were not created using the right project type.


Add a new asset for the shared assembly. 


  1. Type is Microsoft.VisualStudio.Assembly
  2. Source is a project in the solution
  3. Project will be the shared assembly project
  4. Click OK

Add a new asset for the item templates.


  1. Type is Microsoft.VisualStudio.ItemTemplate
  2. Source is a project in the solution
  3. Project will be the item template project
  4. Click OK

Notice that the path that is displayed includes an output group.  This property is set inside the item template project.  If the referenced project actually isn’t an item template then the build will fail.


Add a new asset for the nested templates.


  1. Type is Microsoft.VisualStudio.VsPackage
  2. Source is a project in the solution
  3. Project will be the text template project 
  4. Click OK

An interesting (and sometimes problematic) thing that happens is that project assets are added as references to the project.  For the nested templates this is problematic because by default the setup project expects an assembly to be generated.  For the nested templates there is no assembly so open the properties for the nested template reference and set Reference Output Assembly to False.  If this is not done then a compilation error will occur.


Switch to the Dependencies tab.  This is where any dependencies can be defined.  The .NET framework is already included but we depend upon T4 Toolbox so that needs to be added as well.  Assuming it is already installed on the machine you can do the following. to add the dependency.


  1. Source is Installed Extension
  2. Name is T4 Toolbox
  3. Version range will be set to the current version but you can adjust this as needed
  4. Click OK

One issue with dependencies is that if the user does not have the dependency installed they may get a generic error message before they get information about the missing dependency.  Hence it is useful to put dependencies in the description of the extension.


Almost done.  The last thing we need to do is add another package definition file (.pkgdef) matching the name of the shared assembly project.  As was done earlier, configure the item properties.


  • Build Action = Content
  • Copy to Output = Copy Always
  • Include in VSIX = True

The package definition file used earlier had an entry for each template.  This file will contain an entry for the shared assembly.


[$RootKey$\TextTemplating\IncludeFolders\.tt]

"IncludeMyTemplateAssemblies"="$PackageFolder$"


Compilation


Time to compile the setup but before you do I recommend that you change the VSIX properties in the setup project to not deploy to the experimental instance of VS.  The experimental instance of VS is designed to allow you to test your packages without impacting your main development environment.  Unfortunately in my experience it does not work well when you have additional packages or extensions installed.  You end up sitting through lots of error dialogs. 


If you do not use the experimental instance of VS then you won’t be able to easily debug your template.  However there is a simple approach that I find useful.  In most cases you will develop your template in a stand alone project so you can tweak the template.  Once it is added to the extension though you can still change it by finding the directory where the extension was installed.  By default it will be a randomly generated directory under <VSDir>\Common7\IDE\Extensions for shared extensions or <profiledir>\AppData\Local\Microsoft\VisualStudio\11.0\Extensions for user extensions.  Once you find the directory you can edit and/or replace the files until you’ve resolved any issues you’re having.  You can then update the project and redeploy.


To test the setup simply run the .vsix file.  Start VS, create or open a project and add the new item template to the project.  Confirm the generated code is correct.  In general if you are adding or removing extensions you should ensure that all instances of VS are closed first.  VS doesn’t full install/remove extensions until it is restarted and having multiple instances running can cause problems with that.


Versioning


Versioning of the extension is incredibly important.  When VS is looking for an updated extension it uses the version as defined in the manifest.  Ensure that whenever you build a new version of the setup that you increment the version number.  You should also consider keeping the major/minor version number of the shared assembly in sync with the manifest file.


Do not change the product ID.  If you do then this becomes a brand new extension that can be installed side-by-side with the old one.  In most cases this can cause  conflicts that you would want to avoid.


Troubleshooting


Troubleshooting T4 templates is an art more than a science but here’s a few thoughts based upon my experience.


If you get a compilation error while compiling the setup project saying it cannot find the assembly for the nested templates project then you forgot to change the reference’s properties.  Refer to the earlier section on fixing that.


If the root template cannot find the nested template then the registry was not updated properly.  This can occur if the extension was installed but VS was not restarted, multiple instances of VS were running or if something went wrong.  Uninstall the extension, restart VS to clean everything up, shut down VS and reinstall the extension.


If the shared assembly file cannot be found then the above comments about the root template also apply.  Additionally ensure that the assembly reference in the nested template includes the .dll or the assembly is in the GAC.


If the generated file contains an error token then use Error List to determine the actual error(s) that occurred.  In most cases it is a compilation issue with the nested template.  Load up the nested template in VS and edit it until it compiles.  Then rebuild the deployment package.


If the item templates do not show up then ensure that they were properly added to the setup project and that they appear in the extension directory.  The path to the extension directory is contained in the install log that is accessible at the end of the extension installation.


When adding a new template get it working in a standalone console application and then move it into the templates solution.  This will make it easier to make changes and you can still rely on the shared assembly and break up the template into the item and nested components.


Enhancements


There are quite a few enhancements that could be made with all this code.  One area that I personally recommend is using data annotations for validating configuration properties.  Apply annotations to the configuration properties where appropriate.  Alternatively implement IValidatableObject in the template class.  Then modify the Validate method in the base class to validate the annotations as part of the core validation.  This eliminates the need for per-template validation of things like required parameters.


Another area of improvement is breaking out all the non-template functionality.  I personally like extension methods so you could move all the non-template functionality into extension methods. 


Yet another area is defining some common template generation methods that derived types can override.  For example most T4-generated code should be marked as non user code so they won’t step into it.  Wrapping this up in a method allows derived types to call it where appropriate and, optionally, add their own attributes.


For deployment you can provide the developers with the VSIX package.  If you’ve read my earlier article on hosting your own private VS extension gallery then you could deploy the VSIX to that as well.