Was this page helpful?
Your feedback about this content is important. Let us know what you think.
Additional feedback?
1500 characters remaining
Export (0) Print
Expand All

Building a Custom Project Wizard in Visual Studio .NET

Visual Studio .NET 2003
 

Atul Gupta
.NET Center of Excellence
Infosys Technologies Limited

May 2003

Summary: Learn how to extend Visual Studio to create a custom project type for new types of applications, or to enforce corporate standards. (13 printed pages)

Overview

The more one uses Microsoft Visual Studio® .NET to build solutions, the more the power of the tool is realized. However, there are times when it would be helpful to have more wizards and templates to be available. Having suitable wizards and templates to kick start a project goes a long way in reducing the overall time required to build a project.

When working on an application, it's not uncommon for some of the coding to be repetitive. Macros can help with this issue some of the time, but macros don't address the for a variety of project templates. It would be helpful to be able to generate a base template that takes care of the following:

  1. Sets up the right environment by adding the necessary references to .NET assemblies.
  2. Creates a base class and possibly derives it from any additional class and interfaces.
  3. Provides default implementation for some of the methods.
  4. Adds GUIDs by default if the component needs to be registered with COM and adds code to register for COM Interop.

Wizards

Wizards allow us to create base implementations and provide them as New Project or New Item options in Visual Studio .NET. In this article, we will see how we can create a wizard that helps us build new C# or Visual Basic® .NET project.

The sections below walk through a step-by-step process for creating a wizard in both C# and Visual Basic .NET. The references added to the project or the .cs or the .vb files won't have any relevant code because the idea here is only to demo the method. You can create specific files by following the steps listed here and build custom wizards for any purpose that you need. Some examples for where you might want to create a wizard are:

  • Creating a HttpHandler or HttpModule component.
  • Creating an enterprise service component.
  • Creating a BizTalk application integration component.

The sample demonstrates the following:

  1. Building a wizard to create a new project for a .NET library component (.DLL).
  2. Adding additional references to .NET assemblies, as required by the project.
  3. Adding GUID and necessary project settings for the COM Interop.
  4. Adding code to make this component work with COM+.

Where necessary, I will also point out the changes required if you are working with Visual Studio .NET 2003.

Before we create the wizard, there is one final point that needs to be mentioned. Visual Studio .NET does provide a Custom Wizard and Visual Studio .NET Add-in project templates. This can be used to create new wizards and add-ins. However, these project templates only allow you to create the wizards only in Visual C++® projects. In the case of the add-in, we need to implement the IDTWizard interface. The approach taken in this document is to show what happens when a new wizard is created and also to demonstrate the point that you can easily build additional custom wizards and simplify your day to day work with

Visual Studio .NET and increase your productivity. Creating Custom Wizards in C#

  1. Locate the VC# directory in the Visual Studio .NET installation (default installation location is C:\Program Files\Microsoft Visual Studio .NET\VC#).
  2. Visual Studio .NET uses files with the extension .VSZ to launch wizards. We will start by creating a VSZ file using your text editor of choice and naming this file CSharpCustom.vsz. The CSharp prefix in the file name is the convention used for existing C# wizards in Visual Studio .NET. This file will be stored in the CSharpProject subdirectory in the VC# directory (default installation location is C:\Program Files\Microsoft Visual Studio .NET\VC#\CSharpProjects). Add the code snippet below to the file that was just created. The Wiz postfix in line 3 of the snippet again by Visual Studio .NET convention.
    VSWIZARD 7.0
    Wizard=VsWizard.VsWizardEngine
    Param="WIZARD_NAME = CSharpCustomWiz"
    Param="WIZARD_UI = FALSE"
    Param="PROJECT_TYPE = CSPROJ"
    
    
    Note   For Visual Studio .NET 2003, the Engine version is 7.1. Hence, the Wizard value would be VsWizard.VsWizardEngine.7.1.
  3. Visual Studio .NET uses the VSDIR file extensionto get information for the New Project and Add Item dialog boxes. We will create a new VSDIR file in the same location as the VSZ file and name it CSharpCustom.vsdir. Once the file is created, add the code snippet below to it. The GUID refers to an assembly from which the display strings and the icon are loaded (which is used in the New Projects Wizard), and the number 4547 is the ID of the icon being used. In our case, we will use the values from the existing file CSharp.vsdir.
    CSharpCustom.vsz|{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}|Custom 
    Project|1|Custom Project Template|{FAE04EC1-301F-11d3-BF4B-
    00C04F79EFBC}|4547| |Custom
    
    
  4. Create a new folder named CSharpCustomWiz in the VC#Wizards subdirectory inof the VC# directory. (The default installation location is C:\Program Files\Microsoft Visual Studio .NET\VC#\VC#Wizards.)
  5. We will need to create a new default CSPROJ file to be used when a new project is created using the new wizard. We can't use the default CSPROJ file that comes with Visual Studio .NET because we want this assembly to be registered for use with COM and we will need to enable the configuration settings for this in the project build properties. Create DefaultCustom.csproj in the VC#Wizards directory (default installation location is C:\Program Files\Microsoft Visual Studio .NET\VC#\VC#Wizards) and add the following code snippet to it.
    Note   This file is same as the DefaultDll.csproj file except for the RegisterForComInterop setting. We need this setting because we are deriving from the ServicedComponent and the component will be hosted in COM+, so we need to have COM interop.
    <VisualStudioProject>
        <CSHARP>
            <Build>
                <Settings
                    OutputType = "Library"
                    NoStandardLibraries = "false"
                >
                    <Config
                        Name = "Debug"
                        DebugSymbols = "true"
                        Optimize = "false"
                      OutputPath = ".\bin\Debug"
                      EnableUnmanagedDebugging = "false"
                      DefineConstants = "DEBUG;TRACE"
                        WarningLevel = "4"
                        RegisterForComInterop = "true"
                    />
                    <Config
                        Name = "Release"
                        DebugSymbols = "false"
                        Optimize = "true"
                      OutputPath = ".\bin\Release"
                      EnableUnmanagedDebugging = "false"
                         DefineConstants = "TRACE"
                        WarningLevel = "4"
                   IncrementalBuild = "false"
                        RegisterForComInterop = "true"
                    />
                </Settings>
            </Build>
            <Files>
                <Include/>
                <Exclude/>
            </Files>
        </CSHARP>
    </VisualStudioProject>
    
    
  6. Each of the directories in the VC#Wizards directory contains two subdirectories—Scripts and Templates. To simplify our work, we will copy the contents from the CSharpDLLWiz into the CSharpCustomWiz folder.
  7. At this point we need to make some changes to the default.js file. The new/modified lines are shown in bold below.
    // (c) 2001 Microsoft Corporation
    
    function OnFinish(selProj, selObj)
    {
        var oldSuppressUIValue = true;
        try
        {
            oldSuppressUIValue = dte.SuppressUI;
            var strProjectPath = wizard.FindSymbol("PROJECT_PATH");
            var strProjectName = wizard.FindSymbol("PROJECT_NAME");
            var strSafeProjectName = CreateSafeName(strProjectName);
            wizard.AddSymbol("SAFE_PROJECT_NAME", strSafeProjectName);
    
            var bEmptyProject = 0; //wizard.FindSymbol("EMPTY_PROJECT");
            
            var strAppGuid = wizard.CreateGuid();
            wizard.AddSymbol("GUID_CLASS", 
               wizard.FormatGuid(strAppGuid, 0));
     
            var strCOMplusGuid = wizard.CreateGuid();
            wizard.AddSymbol("GUID_COMPLUS", 
                wizard.FormatGuid(strCOMplusGuid, 0));
            
    
            var proj = CreateCSharpProject(strProjectName, 
              strProjectPath, "DefaultBTSAIC.csproj");
    
            var InfFile = CreateInfFile();
            if (!bEmptyProject && proj)
            {
                AddReferencesForCustomProject(proj);
                AddFilesToCSharpProject(proj, strProjectName, 
                  strProjectPath, InfFile, false);
            }
            proj.Properties("ApplicationIcon").Value = "App.ico";
            proj.Save();
        }
        catch(e)
        {
            if( e.description.length > 0 )
                SetErrorInfo(e);
            return e.number;
        }
        finally
        {
            dte.SuppressUI = oldSuppressUIValue;
            if( InfFile )
                InfFile.Delete();
        }
    }
    
    function GetCSharpTargetName(strName, strProjectName)
    {
        var strTarget = strName;
    
        switch (strName)
        {
            case "readme.txt":
                strTarget = "ReadMe.txt";
                break;
            case "File1.cs":
                strTarget = "Class1.cs";
                break;
            case "assemblyinfo.cs":
                strTarget = "AssemblyInfo.cs";
                break;
    
        }
        return strTarget; 
    }
    
    function DoOpenFile(strName)
    {
        var bOpen = false;
        
        switch (strName)
        {
            case "Class1.cs":
                bOpen = true;
                break;
        }
        return bOpen; 
    }
    
    function SetFileProperties(oFileItem, strFileName)
    {
        if(strFileName == "File1.cs" || strFileName == "assemblyinfo.cs")
        {
            oFileItem.Properties("SubType").Value = "Code";
        }
    }
    
    function AddReferencesForCustomProject(oProj)
    {
        var refmanager = GetCSharpReferenceManager(oProj);
        var bExpanded = IsReferencesNodeExpanded(oProj)
        refmanager.Add("System");
        refmanager.Add("System.Data");
        refmanager.Add("System.EnterpriseServices");
        if(!bExpanded)
            CollapseReferencesNode(oProj);
    }
    
    
  8. The AddReferencesForCustomProject function (as shown in the code above) is used to add the necessary project references. We can also add this function in the Common.js (default installation location is C:\Program Files\Microsoft Visual Studio .NET\VC#\VC#Wizards\1033) where other common utility functions are implemented and Visual Studio .NET automatically invokes when creating a new project.
    Note   If the .NET assemblies added as references are not found when the wizard is run, the wizard will quit and fail to create a new project. The path that Visual Studio .NET checks to locate the assemblies is taken from the following registry keys:
    • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.0\AssemblyFolders\PublicAssemblies
    • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\AssemblyFolders

      For Visual Studio .NET 2003, the registry key used to search the referenced assemblies is:

    • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.1\AssemblyFolders
  9. The Templates\1033 folder in the CSharpCustomWiz folder has the files that will be added to the project by default. File1.cs should be modified as shown below:
    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.EnterpriseServices;
    
    namespace [!output SAFE_NAMESPACE_NAME]
    {
        /// <summary>
        /// Summary description for [!output SAFE_CLASS_NAME].
        /// </summary>
        
        // The following GUID is for COM interop
        [Guid("[!output GUID_CLASS]")]
        public class [!output SAFE_CLASS_NAME] : ServicedComponent
        {
            /// <summary>
            /// Add any class level variables here
            /// </summary>
    
            public [!output SAFE_CLASS_NAME]()
            {
                //
                // TODO: Add constructor logic here
                //
            }
        }
    }
    
    

    The SAFE_NAMESPACE_NAME is the name given to the project. Visual Studio .NET uses this as the name of the namespace. The SAFE_CLASS_NAME is picked up by Visual Studio .NET on its own. The default value for a class library would be Class1, and Webform1 for a Web Form. The Wizard engine replaces these values with the actual ones while creating the new project because we have used the !output directive.

  10. We also need to modify the AssemblyInfo.cs file as shown below:
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.EnterpriseServices;
    
    .
    .
    .
    //COM+ related settings 
    //
    [assembly: ApplicationName("Custom Component")]      
    [assembly: ApplicationID("[!output GUID_COMPLUS]")]
    [assembly: Description("Custom component using C#.NET")]
    
    

    Now start Visual Studio .NET and invoke the New Project option. We will see the new project option we just created as shown in Figure 1. The items highlighted by the red rectangles are based on the values set in the CSharpCustom.vsdir file.

    Figure 1. Highlight of values set in the VSDIR file when opening a new C# project

    You can go ahead and change the Project Name and Location to what works for you and click OK. This createz the base files and project settings necessary to build the .NET assembly.

Steps to Create Custom Wizards in Visual Basic .NET

  1. Locate the VB7 directory in the Visual Studio .NET installation (default installation location is C:\Program Files\Microsoft Visual Studio .NET\VB7).
  2. Visual Studio .NET uses the .VSZ file extension VSZ o launch wizards. We will start by creating a VSZ file using your text editor of choice and naming the file Custom.vsz. This file will be stored in the VBProjects subdirectory in the VB7 directory (default installation location is C:\Program Files\Microsoft Visual Studio .NET\VB7\VBProjects). Type the following code snippet in the file:
    VSWIZARD 6.0
    Wizard=VsWizard.VsWizardEngine
    Param="WIZARD_NAME = Custom"
    Param="WIZARD_UI = FALSE"
    Param="PROJECT_TYPE = VBPROJ"
    
    
    Note   For Visual Studio .NET 2003, the Engine version is 7.1. Hence the Wizard value would be VsWizard.VsWizardEngine.7.1.
  3. Visual Studio .NET uses the VSDIR file extension to get information for the New Project and Add Item dialog boxes. We will create a new VSDIR file in the same location as the VSZ file created in Step 2 and name it Custom.vsdir. Type the code snippet below into the new file. The GUID refers to an assembly from which the display strings and the icon are loaded (which are used in the New Projects Wizard), and the number 4500 is the ID of the icon being used. In our case, we will use the values from the existing file Projects.vsdir.
    Custom.vsz|{164B10B9-B200-11D0-8C61-00A0C91E29D5}|Custom 
    Project|1|Custom Project Template|{164B10B9-B200-11D0-8C61-
    00A0C91E29D5}|4500| |Custom
    
    
  4. Create a new folder named Custom in the VBWizards subdirectory of the VB7 directory (the default installation location is C:\Program Files\Microsoft Visual Studio .NET\VB7\VBWizards). To simplify our work, we will copy the contents from the ClassLibrary directory into the new Custom directory.
  5. We will need to create a new VBPROJ file to be used when a new project is created using this wizard. We can't use the default that comes with Visual Studio .NET because we want this assembly to be registered for use with COM and we will need to enable the configuration settings for this in the project build properties, as well as adding required Import statements. We will rename the existing VBPROJ file to Custom.vbproj and modify it as shown in the code snippet below.
    Note   This file is same as the DefaultDll.csproj file except for the RegisterForComInterop setting. We need this setting because we are deriving from the ServicedComponent and the component will be hosted in COM+, so we need to have COM interop.
    <VisualStudioProject>
        <VisualBasic>
            <Build>
                <Settings
                    OutputType = "Library"
                    StartupObject = ""
               >
                    <Config
                        Name = "Debug"
                        DebugSymbols = "true"
                        DefineDebug = "true"
                        DefineTrace = "true"
                        IncrementalBuild = "true"
                        OutputPath = "bin"
                        RegisterForComInterop = "true"
                    />
                    <Config
                        Name = "Release"
                        DebugSymbols = "false"
                        DefineDebug = "false"
                        DefineTrace = "true"
                        IncrementalBuild = "false"
                        Optimize = "true"
                        OutputPath = "bin"
                        RegisterForComInterop = "true"
                    />
                </Settings>
                <References>
                    <Reference Name = "System" />
                    <Reference Name = "System.Data" />
                    <Reference Name = "System.EnterpriseServices" />
                </References>
                <Imports>
                    <Import Namespace = "Microsoft.VisualBasic" />
                    <Import Namespace = "System" />
                    <Import Namespace = "System.Data" />
                    <Import Namespace = "System.EnterpriseServices" />
                </Imports>
            </Build>
            <Files>
                <Include>
                </Include>
            </Files>
        </VisualBasic>
    </VisualStudioProject>
    
    
    Note   If the .NET assemblies added as references are not found when the wizard is run, the wizard will quit and fail to create a new project. The path that Visual Studio .NET checks to locate the assemblies is taken from the following registry keys:
    • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.0\AssemblyFolders\PublicAssemblies
    • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\AssemblyFolders

      For Visual Studio .NET 2003, the registry key used to search the referenced assemblies is

    • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.1\AssemblyFolders
  6. Next we needto make some changes to the OnFinish method in the default.js file located in the scripts\1033 subdirectory (default installation location is C:\Program Files\Microsoft Visual Studio .NET\Vb7\VBWizards\Custom\Scripts\1033). . The changes are shown below in bold (replace ClassLibrary.vbproj with Custom.vbproj and add additional line to generate another GUID).
    function OnFinish(selProj, selObj)
    {
    .
    .
        var strTemplateFile = strTemplatePath + "\\Custom.vbproj";
    .
    .
        if( project )
        {
            strProjectName = project.Name;  //In case it got changed
    
            var item;
            var editor;
            var strRawGuid = wizard.CreateGuid();
            wizard.AddSymbol("GUID_ASSEMBLY", 
                wizard.FormatGuid(strRawGuid, 0));
                
            var strCOMplusGuid = wizard.CreateGuid();
            wizard.AddSymbol("GUID_COMPLUS", 
               wizard.FormatGuid(strCOMplusGuid, 0));
    
    
  7. The Templates\1033 folder in the Custom folder contains the files that are added to the project by default. We need to modify the Class.vb as shown in the code snippet below:
    Public Class [!output SAFE_ITEM_NAME]
        Inherits ServicedComponent
    
        'Add your code here
    End Class
    
    

    The SAFE_ITEM_NAME is picked up by Visual Studio .NET on its own. The default value for a class library would be Class1 and Webform1 for a Web Form. The Wizard engine replaces these values with the actual ones while creating the new project because we used the !output directive.

  8. We also need to modify the AssemblyInfo.vb file as shown below:
    Imports System.Reflection
    Imports System.Runtime.InteropServices
    .
    .
    
    'COM+ related settings 
    '
    <Assembly: ApplicationName("Custom Component ")>
    <Assembly: ApplicationID("[!output GUID_COMPLUS]")>
    <Assembly: Description("Custom component using VB.NET")>
    
    

    Now start Visual Studio .NET and invoke New Project option. We see the new project option that we just created. The items highlighted in red in Figure 2 are the based on the values set in the VSDIR file.

    Figure 2. Highlight of values set in the VSDIR file when opening a new Visual Basic project

    You can go ahead and change the Project Name and Location to what suits your needs and click OK. This creates the base files and project settings necessary to build the .NET assembly.

Conclusion

A Custom Project Wizard can help you create a custom project type for new types of applications, or to enforce corporate standards using both C# and Visual Basic .NET. This article shows you how to add this functionality to your own applications, as well as being a stepping-stone upon which you can build more feature-rich Project Wizards or other types of wizards.

References

Show:
© 2015 Microsoft