Windows DevCenter    
 Published on Windows DevCenter (http://www.windowsdevcenter.com/)
 See this if you're having trouble printing code examples


Creating Visual Studio Project Templates

by Ron Petrusha
10/17/2006

Often, developers perform the same repetitive operations when creating a new Visual Studio project. They frequently find themselves adding the same references and sometimes the same project items to their projects. Sometimes, developers even add more or less identical "framework" code (that is, code that performs essential operations, such as instantiating top-level objects found in an application object model). When these repetitive operations involve adding references or otherwise empty project items, they are just mildly annoying and inefficient. When they require adding repetitive code, they often become irritating and, depending on how the repetitive code is generated, error-prone as well. However, Visual Studio 2005 offers a solution to the inefficiencies of creating largely identical projects: custom project and item templates, supplemented by wizards, can automate the process of project creation and eliminate the need for developers to add the same references, the same project items, or even largely identical code to new projects.

Note: This article applies to Visual Studio project templates and wizards for Visual Studio 2005 only. In particular, it introduces a new object model for wizard development that is completely incompatible with previous versions of Visual Studio .NET.

Inside Visual Studio Project Templates

The starting point for creating a Visual Studio project is to select the type of project to be created from the New Project dialog, as shown in Figure 1. Each project that the user can create is represented by a Visual Studio template, which in turn corresponds to a Zip file stored in a predetermined location. Visual Studio recognizes two different kinds of templates:

Figure 1
Figure 1: The Visual Studio New Project dialog.

If we open the Zip file that defines a particular Visual Studio project template, we see that it includes a number of files that will eventually be added to a newly created project. For example, in Figure 2, WinZip is displaying the files contained within the Visual Basic Console Application project template, ConsoleApplication.zip. Of the 10 files contained in the Zip file, each corresponds to a file included in a new console mode application and accessible from the Visual Studio Solution Explorer, with just two exceptions:

Thumbnail, click for full-size image.
Figure 2: The files contained in the ConsoleApplication.zip project template file. (Click for full-size image.)

Since it is the template file that distinguishes this collection of files as a Visual Studio project template, it is instructive to examine its contents. As Figure 3 shows, consoleapplication.vstemplate turns out to an XML file that consists of two sections, TemplateData (which provides information about how the template is to be handled) and TemplateContent (which defines the individual items to be included in the project).

Thumbnail, click for full-size image.
Figure 3: The contents of a .vstemplate file. (Click for full-size image.)

In the TemplateData section, the ProjectType tag defines the project language. The DefaultName and ProvideDefaultName tags determine whether Visual Studio provides a root project name (such as ConsoleApplication followed by a number), which it increments depending on the number of projects found in a particular solution directory to form a unique project name.

In the TemplateContent section, each ProjectItem tag defines a file to be included in a new project. The TargetFileName attribute determines the location of the project item if it is not placed in the project directory. Most interesting is the ReplaceParameters attribute, which suggests that the project item files stored in the template have replaceable parameters. In the Console Application template, only some items, including Module1.vb, AssemblyInfo.vb, and the three Designer files, have replaceable parameters. We can get some sense of what these replaceable parameters are by looking at the files whose ProjectItem tag has a ReplaceParameters attribute set to true. For example, Figure 4 shows Module1.vb, the file used to store developer code in a console application. Its replaceable parameter, $safeitemname$, is easy to pick out, since it is delimited by dollar signs. Table 1 contains a complete list of replaceable parameters used in the Visual Basic console application project template, including those found in the consoleapplication.vbproj file.

Figure 4
Figure 4: A project item with a replaceable parameter.

File Replaceable Parameter
Module1.vb $safeitemname$
AssemblyInfo.vb $projectname$
AssemblyInfo.vb $registeredorganization$
AssemblyInfo.vb $year$
AssemblyInfo.vb $guid1$
MyApplication.Designer.vb $clrversion$
Resources.Designer.vb $clrversion$
Resources.Designer.vb $safeprojectname$
Settings.Designer.vb $clrversion$
Settings.Designer.vb $safeprojectname$
consoleapplication.vbproj $safeprojectname$

Table 1: A list of replaceable parameters in the Visual Basic console mode application template.

To get a better sense of how the contents of the Visual Studio project template relate to a new project, it's also worthwhile to take a look at the project file stored in a template. Example 1 shows it in full. In addition to defining build and debug settings and determining the path of the output assembly, the project file is also responsible for defining the following:

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProductVersion>
    </ProductVersion>
    <SchemaVersion>
    </SchemaVersion>
    <ProjectGuid>{00000000-0000-0000-0000-000000000000}</ProjectGuid>
    <OutputType>Exe</OutputType>
    <StartupObject>$safeprojectname$.Module1</StartupObject>
    <RootNamespace>$safeprojectname$</RootNamespace>
    <AssemblyName>$safeprojectname$</AssemblyName>
    <MyType>Console</MyType>
  </PropertyGroup>
    
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <DefineDebug>true</DefineDebug>
    <DefineTrace>true</DefineTrace>
    <OutputPath>bin\Debug\</OutputPath>
    <DocumentationFile>$safeprojectname$.xml</DocumentationFile>
    <NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn>
  </PropertyGroup>
    
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <DefineDebug>false</DefineDebug>
    <DefineTrace>true</DefineTrace>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DocumentationFile>$safeprojectname$.xml</DocumentationFile>
    <NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn>
  </PropertyGroup>
    
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Data" />
    <Reference Include="System.Deployment" />
    <Reference Include="System.Xml" />
  </ItemGroup>

    
  <ItemGroup>
    <Import Include="Microsoft.VisualBasic" />
    <Import Include="System" />
    <Import Include="System.Collections" />
    <Import Include="System.Collections.Generic" />
    <Import Include="System.Data" />
    <Import Include="System.Diagnostics" />
  </ItemGroup>
    
  <ItemGroup>
    <Compile Include="Module1.vb"/>
    <Compile Include="My Project\AssemblyInfo.vb"/>
    <Compile Include="My Project\Application.Designer.vb">
      <AutoGen>True</AutoGen>
      <DependentUpon>Application.myapp</DependentUpon>
    </Compile>
    <Compile Include="My Project\Resources.Designer.vb">
      <AutoGen>True</AutoGen>
      <DesignTime>True</DesignTime>
      <DependentUpon>Resources.resx</DependentUpon>
    </Compile>
    <Compile Include="My Project\Settings.Designer.vb">
      <AutoGen>True</AutoGen>
      <DependentUpon>Settings.settings</DependentUpon>
      <DesignTimeSharedInput>True</DesignTimeSharedInput>
    </Compile>
  </ItemGroup>
    
  <ItemGroup>
    <EmbeddedResource Include="My Project\Resources.resx">
      <Generator>VbMyResourcesResXFileCodeGenerator</Generator>
      <LastGenOutput>Resources.Designer.vb</LastGenOutput>
      <CustomToolNamespace>My.Resources</CustomToolNamespace>
      <SubType>Designer</SubType>
    </EmbeddedResource>
  </ItemGroup>
    
  <ItemGroup>
    <None Include="My Project\Application.myapp">
      <Generator>MyApplicationCodeGenerator</Generator>
      <LastGenOutput>Application.Designer.vb</LastGenOutput>
    </None>
    <None Include="My Project\Settings.settings">
      <Generator>SettingsSingleFileGenerator</Generator>
      <CustomToolNamespace>My</CustomToolNamespace>
      <LastGenOutput>Settings.Designer.vb</LastGenOutput>
    </None>
  </ItemGroup>
    
  <Import Project="$(MSBuildBinPath)\Microsoft.VisualBasic.targets" />
    
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
  </Target>
  <Target Name="AfterBuild">
  </Target>
  -->

 </Project>

Example 1: The project file in the Visual Basic console application template

A Visual Studio project template along with its project file allows you to define the source code files (which of course can contain custom code) to be included in a project, replace parameter strings with their values, define the assemblies references by the project, and indicate which namespaces the compiler imports on a project-wide basis.

Modifying or Creating a Visual Studio Project Template

One of two approaches can be used to modify a Visual Studio project template:

Although the second is the easier option, it suffers from a major limitation: If you create the project that you plan to export from a template, all replaceable string parameters will be replaced by their values, and the value of your template will be substantially reduced. This makes the first alternative the most attractive. However, you can combine these approaches without losing a project template's replaceable string parameters. To do this, follow these steps:

  1. Create a directory in which to store your project. If you store multiple projects in a single solutions directory, also create a solutions directory as the parent of the project directory.
  2. Locate the Zip file containing the Visual Studio template that most closely resembles the template you plan to create.
  3. Extract the contents of the Zip file to the project directory you've just created. If the Zip file does not do it for you, you should create the directory structure that would be created if you created the project from a template in Visual Studio. For example, in Visual C#, the AssemblyInfo.cs file is placed in the Properties subdirectory of the project directory; in Visual Basic, AssemblyInfo.vb is placed in the My Project subdirectory of the project directory.
  4. Delete the template (*.vstemplate) file.
  5. Review the files listed in the project (*.vbproj or *.csproj) file. All of the files listed must be present for Visual Studio to successfully generate a template. Modify the contents of the project file accordingly. This is also a good opportunity to rename files. The project file itself is a particularly good candidate for renaming.
  6. In Visual Studio, select the Open Project option from the File menu, and navigate to the project file that you'd like to open.
  7. Make whatever changes you desire to create the new template.
  8. Use the Export Template option from the File menu to generate the new template.
  9. Since this approach does not create a new project from an existing template, it preserves replaceable parameter strings in the project's files. This does, however, have one disadvantage: because the Visual Basic and C# compilers will not recognize the parameter strings as valid language elements, the project cannot be compiled. Instead, you'll have to use your template to create a project, which you can then compile and debug.

Visual Studio and the Templates Cache

Once a Visual Studio project session starts, it stores its templates in a cache. This means that any changes you make to a template will not be reflected until Visual Studio is closed and restarted. This is an important detail to keep in mind: you can waste hours making changes to a template, only to find that they do not appear to be reflected in new project or project items generated from that template.

Some Template Customizations

You can use this technique to generate templates that you've customized by adding new references, importing new namespaces, and using replaceable string parameters. In this section, we'll examine how to perform these three kinds of customizations before generating a new Visual Studio project template.

Adding References

To generate a template that includes additional references to .NET assemblies, right click on the Visual Basic or C# project in the Solution Explorer and select the Add Reference option from the context menu. (In Visual Basic, you can also open the project's Properties dialog and navigate to its References pane.) You can then select the references that you'd like to add to the project.

When you save your project and then export it as a template, the references you've added are present in any projects are created from that template. Both Visual Basic and C# projects behave identically in this regard.

Importing Namespaces in Visual Basic

Another of the repetitive operations that developers have to perform in each project is importing the namespaces whose types will be referenced by the project's code. Most commonly, developers find themselves importing the same namespaces into their projects, usually by adding an Imports statement (in Visual Basic) or the using statement (in C#) to the beginning of each source code file. (The statement applies only to the source code file in which it is placed, rather than to a project as a whole.)

Visual Basic provides direct support for importing namespaces into a project, rather than importing them on a file by file basis. To import a namespace for the project as a whole, open the project's Properties dialog and select the References tab. The Imported Namespaces list box, which is shown in Figure 5, allows you to see which namespaces are globally imported for your project, as well as to import additional namespaces found in the assemblies references by your project.

Figure 5
Figure 5: The Imported Namespaces list box

Project-wide imports are reflected in Import tags in the projects .vbproj file. For example, the namespaces imported in Figure 5 result in the following entries in the project's .vbproj file:

<ItemGroup>
   <Import Include="Microsoft.VisualBasic" />
   <Import Include="System" />
   <Import Include="System.Collections" />
   <Import Include="System.Collections.Generic" />
   <Import Include="System.Data" />
   <Import Include="System.Diagnostics" />
</ItemGroup>

Unlike Visual Basic, C# does not recognize the Import tag, and therefore does not allow you to import namespaces on a project-wide basis. However, you can modify the individual source code (.cs) files in your template to reflect the namespaces that you most commonly import.

Using Replaceable String Parameters

In addition to adding references to .NET assemblies and to importing namespaces, you can also add replaceable string parameters within project templates. Visual Studio automatically recognizes the following replaceable parameters:

Parameter Name Description
$guid1$ The GUID that identifies the project if it is exposed to COM.
$guid2$ A second project-defined GUID.
$guid3$ A third project-defined GUID.
$guid4$ A fourth project -defined GUID.
$guid5$ A fifth project -defined GUID.
$guid6$ A sixth project -defined GUID.
$guid7$ A seventh project -defined GUID.
$guid8$ An eighth project -defined GUID.
$guid9$ A ninth project -defined GUID.
$guid10$ A tenth project -defined GUID.
$time$ The current time.
$year$ The current year.
$username$ The name of the authorized user who is creating the project.
$userdomain$ The domain of the authorized user who is creating the project.
$machinename$ The name of the computer on which the project is crated.
$clrversion$ The version of the .NET runtime under which the project will run.
$registeredorganization$ The organization for which the project is being created.
$runsilent$ A flag indicating whether the template should display any user interface elements.
$projectname$ The name of the project. This is the name that the user has entered into the Name textbox when creating the project.
$safeprojectname$ The name of the project with any unsafe characters removed.
$installpath$ The path to the directory containing the project template used to generate the project.
$exclusiveproject$ Determines whether the current solution is closed and a new one opened before the new project is created. If True, the solution is closed; if False, the new project is added to an existing solution.
$destinationdirectory$ The path to the project directory.

Table 2.

For example, you may find that, when creating console mode projects, you always change the name of the class or module in your source code to match that of your project. By making the following simple modification to the Console Application template, you can have Visual Studio make this change for you when it generates the project's files:

' Modification to Module1.vb in Visual Basic projects
Module $safeprojectname$

// Modification to Program.cs in C# projects
   class $safeprojectname$

In addition to making use of existing replaceable strings in your custom templates, you can also add your own custom replaceable strings. These are defined by the <CustomParameters> node in the Visual Studio template (.vstemplate) file and take the following form:

<CustomParameters>
   <CustomParameter Name="$name$" Value="value" />
</CustomParameters>

To add custom replaceable string parameters to a project, you first have to generate the Visual Studio project or item template, extract and edit its .vstemplate file. The <CustomParameters> node should be inserted just before the closing </TemplateContent> tag. Then replace the old version of .vstemplate file in the template Zip file with the new one.

Custom string parameters are most useful when a Visual Studio project or item template is linked with a wizard. Even without custom templates, however, they can be useful. For example, a single project template might be used to target different versions of a particular object model. To take a very simple example, version 1.0 of a small object model is contained in a class named CompanySDK1 in an assembly of the same name. Version 2.0 of the object model is contained in a class named CompanySDK2, also in an assembly of the same name. CompanySDK1 includes a DisplaySDKVersion method, which is inherited by CompanySDK2, that displays the version of the SDK used by the application. A single template can be developed to handle both versions. For example, the code contained in a template that instantiates the versioned application object and calls its DisplaySDKVersion method might appear as follows:

Imports CompanySDK

Module Module1]
   Sub Main()
      Dim versionedSDK As New $versionedClass$
      versionedSDK.DisplaySDKVersion ()
   End Sub
End Module

Two templates that differ only in their single custom parameter can then be generated from this project. The template supporting version 1.0 of the SDK has the following <CustomParameters> node:

<CustomParameters>
   <CustomParameter Name="$versionedClass$" Value="CompanySDK1" />
</CustomParameters>

The template supporting version 2.0 of the SDK has the following <CustomParameters> node:

<CustomParameters>
   <CustomParameter Name="$versionedClass$" Value="CompanySDK2" />
</CustomParameters>

Ron Petrusha is the author and coauthor of many books, including "VBScript in a Nutshell."


Return to the Windows DevCenter.

Copyright © 2009 O'Reilly Media, Inc.