Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Creating project item code templates for Visual Studio .NET

0.00/5 (No votes)
27 Jul 2005 1  
This article describes how new project item code templates work in Visual Studio .NET 2003 (and 2005) and presents a component that makes it easy to write new templates.

Contents

Template Manager

Introduction

This article describes how the code templates that are used when adding new project items in Visual Studio .NET work. There are several other articles on CodeProject that touches on this subject (a few are listed in the References section at the bottom) and the goal of this article is to describe in more detail how it works.

A component, called Krevera Template Manager, is also introduced that makes it (fairly) easy to create custom templates and its functionality is described. Included with the component are templates for strongly typed collections and strongly typed dictionaries for C#, VB and C++. If needed, this component can be extended to create even more powerful templates. All code examples are written in C#.

The component is available in two versions, one for Visual Studio .NET 2003 and one for Visual Studio .NET 2005. The latter has been tested on Beta 2 but will probably work with later versions as well.

Background

There are several types of templates and wizards in Visual Studio, most notably project templates and project item templates. Project templates contain the basic files needed for the different types of projects, and project item templates are single file templates (or multiple if the item has code-behind files) that can be included in projects. To be completely accurate, there is actually no need of actual template files for all project items since they can also be created purely on-the-fly, but it's normally easiest to have a template file for each project item and then adjust it slightly when it's included in the project.

This article is about project item templates, for project templates the reader is advised to look elsewhere.

When adding a new project item to a project, the Add New Item dialog is displayed:

Add New Item dialog

This dialog is where we will add our custom templates.

How does it work?

The items in the dialog above are generated by Visual Studio by reading configuration files from the current project type's project item directory. For example, if the current project is a Visual Basic project, then the items in this directory:

C:\Program Files\Microsoft Visual Studio .NET 2003\Vb7\VBProjectItems

are analyzed (that is on my system of course, the exact location depends on where the installation was made).

There are two important types of files in the project item directory:

  • .vsdir files - Lists the items to display and gives all the properties that are displayed in the dialog, such as name, description, icon, default item filename, etc. For each item, its corresponding .vsz file is also named.
  • .vsz files - Each project item has a .vsz file that tells Visual Studio how items of the type are created. More accurately, it names a COM component that Visual Studio can use to create the item, and what parameters that component should receive (for example, a template file name).

These two types of files and a COM component that creates the instance are all that are needed to create project items. Here's an overall picture of how everything is connected:

How .vsdir and .vsz files are connected

The format of these files will now be more closely described before we introduce our solution to create flexible project item templates (as you might have guessed, it's an implementation of a COM component as described above).

Note that we can create categories of project items by storing our .vsdir and .vsz files in sub-directories. In the image above, we have sub-directories, for example, for "Local Project Items", "UI", "Code", etc.

Project item directories

Before we go into the details of file formats, it might be useful to know where to find the files. As usual, there are, of course, several ways. Easiest is probably to use the Explorer to navigate to the install directory of Visual Studio and look for sub-directories for the different .NET languages. For example, VB7 obviously contains stuff for Visual Basic. Within that directory, look for a directory name with "projectitem" in it. Soon enough, we find the VBProjectItems directory which contains the files. It's equally easy to find the project item directories for other project types.

However, if we want to do this programmatically, we use a more reliable way namely reading from the registry, under this key:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.1\
                                Projects\{[PROJECT_TYPE_GUID]}

The value we're interested in, is called ItemTemplatesDir and it contains the absolute path to the correct directory. The only complication is that we have to know the GUID for the project type. For Visual Basic and C#, that GUID is available in the Visual Studio Automation interface in the form of these constants (requires a reference to VSLangProj.dll):

VSLangProj.PrjKind.prjKindCSharpProject
VSLangProj.PrjKind.prjKindVBProject

Unfortunately, VSLangProj does not define constants for all project types, so for example, for C++, we have to hardcode it. Fortunately, it's fairly easy to find the correct GUID using regedit. Navigate to the key given above and look in each project sub-key for the DefaultProjectExtension value (for C++ that is vcproj). When that is found, we can be sure we're in the correct project type. Using that method, we find that the GUID for C++ projects is {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}. The bad news is now of course that, we cannot be absolutely sure that Microsoft won't change this in the next Visual Studio version, but we'll worry about that when it happens...

Here's some code that retrieves the project item directory for C#:

const string sVisualStudioVersion = "7.1";
RegistryKey rk;

// C#

rk = Registry.LocalMachine.OpenSubKey("SOFTWARE")
  .OpenSubKey("Microsoft")
  .OpenSubKey("VisualStudio")
  .OpenSubKey(sVisualStudioVersion)
  .OpenSubKey("Projects")
  .OpenSubKey(VSLangProj.PrjKind.prjKindCSharpProject);
_sCSProjItemPath = (string)rk.GetValue(@"ItemTemplatesDir");

Note: For Visual Studio .NET 2005 we set sVisualStudioVersion to "8.0".

.vsdir file format

A .vsdir file lists a set of items and their properties. There can be several .vsdir files in the same directory.

The file format is described in MSDN (use the index search to find ".VSDir files") but here's a brief summary:

  • The file contains one row for each item.
  • Each row is a |-separated list of properties in the following order:
    1. RelPathName - relative path to the item's .vsz file.
    2. {clsidPackage} - optional GUID for a component containing localized resources (e.g. strings).
    3. LocalizedName - the name of the project item to display in the Add New Item dialog.
    4. SortPriority - a number that decides the relative order of items in the Add New Item dialog. Items with low numbers are listed before items with a higher number.
    5. Description - a description of the project item, is displayed in the status field of the Add New Item dialog.
    6. DLLPath or {clsidPackage} - a path or GUID of a DLL that contains an icon resource to use for the item.
    7. IconResourceId - the resource ID of the icon contained in the component given by column 6.
    8. Flags - combined values of the flags to use.
    9. SuggestedBaseName - the base file name to use for the item. Note that the Add New Item dialog will insert a number just before the file name extension of this name. For example, a base file name of "Class.cs" will be suggested to the user as "Class1.cs", or if the project already contains such an item, "Class2.cs".

    The fields that in my opinion are most interesting are marked with bold type in the list. I normally set all the others to 0 except SortPriority for which 1 is a suitable number. If an icon is desired it's easier to just copy an icon file (.ico extension) to the same directory as the .vsz file, and give it the same name as the .vsz file (except the extension of course) than to create resource components.

Here's an example of what a .vsdir file might look like (the lines are wrapped for readability):

Strongly Typed Collection.vsz|0|Typed Collection|1|
 Sub-class of CollectionBase with elements of a given type|
 0|0|0|TypedCollection.cs
Strongly Typed Dictionary.vsz|0|Typed Dictionary|1|
 Sub-class of DictionaryBase with keys and values of the selected types|
 0|0|0|TypedDictionary.cs 

If we store that file in a sub-directory "Krevera Template Manager", the following items will be displayed in the Add New Item dialog (a few column numbers are given in the image to show how the column strings of the .vsdir file are used):

Add Item dialog

When the user selects an item and clicks the Open button, then Visual Studio opens the item's .vsz file, so it's time to describe its format now.

.vsz file format

The sole purpose of .vsz files is to tell Visual Studio what component to call to create the project item and the parameters it needs. The file format is very simple and here's an example (namely the standard "Class" project item for C#):

VSWIZARD 7.0
Wizard=VsWizard.VsWizardEngine.7.1
Param="WIZARD_NAME = CSharpAddClassWiz"
Param="WIZARD_UI = FALSE"
Param="PROJECT_TYPE = CSPROJ"

Description:

  • The first line should always read "VSWIZARD 7.0" so that Visual Studio knows the expected version.
  • The second line tells Visual Studio, the COM component that should be used for creating the project item. The component's ProgID or CLSID must be given here.
  • From the third line and down, we can give the component some extra parameters if we want to. In the example above, the creators of the item template want to send three parameters. Note that everything inside the quotes is sent untouched to the component. Visual Studio has no idea what "WIZARD_NAME = CSharpAddClassWiz" means, that's up to the component to decide.

There is not really a whole lot more to say about the .vsz file format, so we continue with describing the logic of actually creating project items.

COM component

As already mentioned, the actual project item is created by a component pointed out by a .vsz file. The component must fulfill a few requirements:

  1. It must be a COM component. To use a .NET component, we must register it for COM interop.
  2. The COM class must implement the interface EnvDTE.IDTWizard (defined in the EnvDTE.dll library, which by the way, also contains most of Visual Studio's automation interface). That interface has just one method, Execute, with the following prototype:
    void Execute(
       object Application,
       int hwndOwner,
       object[] ContextParams,
       object[] CustomParams,
       ref wizardResult retval
    );

The parameters to the Execute function are as follows:

  1. Application - A DTE object (which is the top-level object of the Visual Studio Automation model) that references the running Visual Studio instance.
  2. hwndOwner - Parent window to use for dialogs we want to display.
  3. ContextParams - A list of properties about the current project. The exact list depends on how the wizard was called, but for new project items, the list is as follows:
    • WizardType - The type of wizard, for project items which we're concerned with, this is always equal to Constants.vsWizardAddItem.
    • ProjectName - The name of the current project.
    • ProjectItems - Collection with ProjectItem-objects for the current project. When we're done, we will have created a new item in this collection.
    • LocalDirectory - File system directory where current project is stored.
    • ItemName - The requested name for the new item (this is taken from the Add New Item dialog).
    • InstallationDirectory - Installation directory of Visual Studio.
    • Silent - bool value that if true indicates that the component should not display a user interface.
  4. CustomParams - A list of the extra parameters that is given in the .vsz file.
  5. retval - An output parameter that the Execute method uses to tell Visual Studio if it succeeded in creating the new project item.

And that's it. When the project item is to be created, Visual Studio creates an instance of the class and calls its Execute method, in which the class creates the new project item and inserts it into the ProjectItems collection. Creating the project item normally involves copying an existing template file and modifying it according to the name given in the Add New Item dialog, the current project's default namespace, and other things.

Microsoft has created a fairly large number of project item templates that work in this way. The actual template files are not stored together with the .vsdir and .vsz files, but separately. It's entirely possible to adjust them, using the method described in the Code Project article "Customizing Visual Studio's Code Generation Templates" but the possible modifications are limited to layout changes, standard file headers etc. No dynamic content (current date, username, etc.) can be added, since the component that adjusts the template file for the project is limited to do what the Visual Studio developers deemed feasible. Also there is, as far as I know, no documentation of the variable replacements that are performed by the standard component. Another problem is that, we cannot build new logic, for example asking the user for information.

To allow creating more complex templates, we thus have to create a new COM IDTWizard component, and that's exactly what will be presented now.

Krevera Template Manager

Krevera Template Manager is basically a COM component for handling requests for creating new project items. It's written in C# and the full source code is available for download at the top of this article. There's also a binary download that contains an installation program for the component and a few sample templates for creating strongly typed collections and dictionaries in C#, VB and C++.

Templates are normal text files that contain variables that Krevera Template Manager replaces when creating the project items. The variables are written in XML and include the following (the type attribute is here for clarity, it can be ignored in this case since its default value is "standard"):

<variable type="standard" name= "[variablename]"/>

There are 13 different variables that can be used:

Variable name Description Sample value
fullfilename Absolute path to the new project item. C:\Documents and Settings\Emil\My Documents\Visual Studio Projects\Template Manager Test\CS_Test\FoobarCollection.cs
curdate The current date. 2005-03-14
directory Absolute path to the directory, in which the new project item is created. C:\Documents and Settings\Emil\My Documents\Visual Studio Projects\Template Manager Test\CS_Test
domainname The name of the domain the computer is connected to. FRODO
domainusername The domain-qualified username. FRODO\Emil
filename Filename for the new project item. FoobarCollection.cs
filenamebase Filename excluding the extension. This is often useful as the name of the new item. For example, a class in a new file called MyClass.cs can be assumed to be called MyClass. FoobarCollection
fullusername The full name of the current user. For this to work, the computer must be connected to a domain. Emil �str�m
installdir Installation directory of Krevera Template Manager. Useful in .vsz files when referencing template files relative to the installation directory. C:\Program Files\Krevera Software\Krevera Template Manager\
machinename The name of the computer. FRODO
namespace The default namespace of the current project. CS_Test
projectname The name of the current project. CS_Test
username The username of the current user, excluding domain name. Emil

We can also access environment variables by setting the type attribute to "environment" and giving the environment variable's name in the name attribute:

<variable type="environment" name="TEMP"/>

Finally, we can also use custom variables whose values we retrieve by asking the user in a wizard-like interface. Here's an example:

<wizard>
  <page>
    <title>Select data type</title>
    <description>Select data type for which to create 
            a strongly typed collection</description>
    <variable name="datatype"/>
  </page>    
</wizard>

When creating the project item whose template contains the above wizard fragment, the following dialog is displayed:

Krevera Template manager Wizard example

When the user has typed the wanted value for the custom variable (named "datatype" in the example), we can use it later in the template by using variables with the type attribute set to "custom":

<variable type="custom" name="datatype"/>

To make sure that the variable declaration for the template does not collide with the code syntax in the template text, all variables must be surrounded by [% and %].

To summarize, we now show the top part of a real template, namely the template for creating strongly typed collections that is distributed with the code:

[%
<wizard>
  <page>
    <title>Select data type</title>
    <description>Select data type for which to create a 
       strongly typed collection</description>
    <variable name="datatype"/>
  </page>    
</wizard>
%]using System;
using System.Collections;

namespace [%<variable name="namespace"/>%] {
    public class [%<variable type="standard"
       name="filenamebase"/>%] : CollectionBase
    {
         public [%<variable type="custom"
               name="datatype"/>%] this[ int index ] {
            get
            {
                return( ([%<variable type="custom"
                    name="datatype"/>%]) List[index] );
            }
            set
            {
                List[index] = value;
            }
        }
        ...

If we insert a project item of this type into a project with a default namespace of CS_Test, name the item FoobarCollection.cs, and answer the wizard question with Foobar, we will get a file containing the following:

using System;
using System.Collections;

namespace CS_Test
{
    public class FoobarCollection : CollectionBase
    {
        public Foobar this[ int index ]
        {
            get
            {
                return( (Foobar) List[index] );
            }
            set
            {
                List[index] = value;
            }
        }
        ...

Hopefully everyone agrees that, this is a very easy way of creating strongly typed collections, compared to doing it yourself.

Other frequent uses for templates are to enforce organization-wide rules about file headers. With this component, it's easy to include filename, creation date, username, etc. automatically in the header of the files. It's also easy to create new variables if needed, just modify the Wizard.EvaluateVariable function in the code to implement evaluation of the new variables.

Implementation overview

The code will not be described in detail here; it's probably easier to read the code and its comments directly. It may however be useful to have an overview of how the code works, so here we go:

  • The component is a .NET component written in C# with COM interop enabled (the project property "Register for COM Interop" is set to "true"). For this to work, we must associate the component with a strong name (a .snk file) which can be generated from a command line:
    sn -k TemplateManager.snk

    The key file must also be referenced from AssemblyInfo.cs (see that file for the details).

    Update: This does not apply to the Visual Studio .NET 2005 version. We still have to set Register for COM Interop but we don't have to worry about strong names etc.

  • Our .vsz file must give Krevera.TemplateManager.Wizard as the component to use and the mandatory parameter TEMPLATE_FILE that gives the name of the template file to use. Here's an example (word-wrapped for readability):
    VSWIZARD 7.0
    Wizard=Krevera.TemplateManager.Wizard
    Param="TEMPLATE_FILE = [%<variable name="installdir"/>%]
           Templates\CSharp\Project Items\StronglyTypedCollection.cs"

    Note that we can use variables in the parameter if needed.

  • The template file is copied into the project (and ProjectItems collection) using this code:
    ProjectItem newfile = _projitems.AddFromTemplate(
      _sTemplateFile, _sNewItemName);
  • The included file is opened and the contents of all [% ... %] blocks are validated with an XML schema. Then if the block is a variable, it is replaced by the variable's value. If the block is a wizard-fragment, then all the pages in the block are displayed and the values the user gives are stored in a dictionary with custom variable values.
  • There is also an installation program project included in the source code. The project is fairly basic, except for that it uses so called custom actions to copy the actual .vsdir and .vsz files into the correct project item locations in Visual Studio's sub-directories. The function doing the copying is placed in the component project and is called TemplateManagerInstaller.OnAfterInstall. That is the place to modify if the project items should not be placed in "Krevera Template Manager" sub-directories, which is the default.

Installation

Either run the installation program (and restart Visual Studio if it's open during the installation) or do it manually:

  1. Build the component project. That should also register the component for COM interop.
  2. Make sure that there is a registry key HKEY_LOCAL_MACHINE\SOFTWARE\Krevera Software\Template Manager (for the Visual Studio .NET 2005 version, append " VS2005") with a named value InstallDir that gives the location of the installation directory of Krevera Template Manager (needed to find the template files). Example of a value is C:\Program Files\Krevera Software\Krevera Template Manager\.
  3. Copy .vsdir and .vsz files into the project items directories of Visual Studio.

Those steps should be enough to use the templates.

License

You're allowed to use this software in any way you want, except for making money. You're free to use it privately or in your organization (in original or modified state) but you're not allowed to include it in commercial products etc. At least not without explicit approval by the author (i.e. me!).

References

Book:

  • Inside Microsoft Visual Studio .NET by Brian Johnson, Craig Skibo and Marc Young.

    This is a book with a very good scope but unfortunately the contents don't look very organized and there are a number of errors. Still, it doesn't have very much of a competition, so it can still be recommended for readers interested in creating macros and add-ins for Visual Studio.

Other Code Project articles:

History

  • 2005-07-24
    • Bug fix: Files created in sub-folders of projects were not opened correctly.
    • Some slight changes to the article text, most notably a correction of the description of LocalDirectory that earlier was incorrectly stating that the value was the path to the new item, when it actually is the project folder path (which is not the same if the item is created in a sub-folder).
    • Added a version of the template manager that works with Visual Studio .NET 2005 Beta 2 (and possibly later versions). Credits to Marcin Celej for pointing out the simplicity of the required modifications.

      Notes:

      • Compiles with 3 warnings but works as it's supposed to.
      • The sample templates are the same as before (strongly typed collection and dictionary) even though those are a lot less useful when we have access to Generics. They still illustrate the template mechanisms however, so I leave them as they are.
  • 2005-04-24

    Fixed some errors (layout and grammatics).

  • 2005-03-15

    First published version.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here