Click here to Skip to main content
Click here to Skip to main content
Go to top

Working with Project Template and Wizard Extension

, 29 May 2012
Rate this:
Please Sign up or sign in to vote.
Create a customized project template, and let your team use it for repaid development.

Introduction

Project Templates and Item Templates are hidden gems of development in Visual Studio. They not only improve development time but also force any architectural concept specially in large projects.

This is not a first attempt to write an article on Project Template but I am trying to address whatever I developed in my recent work. Going forward, I may extend this article for future aspects.

Background

Project Templates and Item Templates provide common project structure to enterprise solution. Project Templates are very useful when you have a series of similar projects in one/many solution(s). It provides common project references, class structure, default implementation and a lot more.

Get more information about Project and Item templates here.

Using the Code

In this article, we will create a customized Project Template with Wizard Extension. You will get more on project template at MSDN site here. We will use Wizard Extension to get inputs from user at the time of creating project.

In our solution, we will assume that all projects are implementing MyProjectTemplate.BaseProject class and IDisposable interface:

using MyProjectTemplate.BaseProject;
namespace MyExternalService
{
    public class ExternalService : BaseService, IDisposable
    {
    }
}

Create a Base Project Template

To create a Project Template, we require a base project template, where we are referring to external references, a customized Class, customized external files (will input files in wizard). One can extend this to various number of combinations like Config files, namespaces, etc.

We can modify this project and their classes by using project template attributes.

Create a Project

Create a project and add all required references including MyProjectTemplate.DLL.

You also need to add Request.XML and Response.XML into resources folder.

Add Default Class

Now add one class MyExternalService to your project and implement it for BaseService and IDisposable.

using MyProjectTemplate.BaseProject;
 
namespace MyExternalService
{
    public class MyExternalService : BaseService, IDisposable
    {
 
        public override string Action
        {
            get { return "POST"; }
        }
 
        public override bool ValidateRequest(object context, object request)
        {
            if (!base.ValidateRequest(context, request)) return false;
 
            //Do customized request validation

            return true;
        }
 
        public override int SLA
        {
            get { return 30; }
        }
 
        public override int Timeout
        {
            get { return 20; }
        }
 
        protected override string RequestXML
        {
            get { return "MyExternalService.Resources.Request.XML"; }
        }
 
        protected override string ResponseXML
        {
            get { return "MyExternalService.Resources.Response.XML"; }
        }
 
        protected override string TargetURL
        {
            get { return "http://localhost/MyExternalService"; }
            // do the default request header mapping using requestHeader
        }
 
        public void Dispose()
        {
            //Release local XML file
        }
    }
}  

Make sure that you are able to compile project so that when you will create template, your created project should not introduce any errors.

Substitute Parameters in Project

Now our project is ready to substitute parameters.

In the above class, replace namespace and class name MyExternalService by $projectname$. You can also replace $projectname$ in RequestXML and ResponseXML property so new property should refer to XML files in newly created project.

After substituting parameters in the above class, our project will look like below:

using MyProjectTemplate.BaseProject;
namespace $projectname$
{
    public class $projectname$ : BaseService, IDisposable
    { 
        public override string Action
        {
            get { return "POST"; }
        }
 
        public override bool ValidateRequest(object context, object request)
        {
            if (!base.ValidateRequest(context, request)) return false;
 
            //Do customized request validation

            return true;
        }
 
        public override int SLA
        {
            get { return 30; }
        }
 
        public override int Timeout
        {
            get { return 20; }
        }
 
        protected override string RequestXML
        {
            get { return "$projectname$.Resources.Request.XML"; }
        }
 
        protected override string ResponseXML
        {
            get { return "$projectname$.Resources.Response.XML"; }
        }
 
        protected override string TargetURL
        {
            get { return "http://localhost/MyExternalService"; }
            // do the default request header mapping using requestHeader
        }
 
        public void Dispose()
        {
            //Release local XML file
        }
    }
} 

Note: Don't worry now you will not be able to compile your project but that should be fine for now.

Export Project Template

Now select this project and click on Export Template from File menu in your Visual Studio, an Export Template Wizard should appear.

Select project from drop-down in Export Template Wizard and click next.

Provide Template name, description, icon and default preview image and click on Finish button.

After exporting a project as Project Template, now your exported project should appear in Project Template lists in Add/Create project list in Visual Studio.

This project template is still basic and not taking any inputs from user like XML Files, URL, timeouts etc.

To implement more customization in Project Template, you need to use Wizard Extension, where you will create a form to enter inputs from user and eventually it will be used in Creating a New project using project template.

You can find detailed implementation @ Visual Studio 2005 Project and Item Templates.

Create a Wizard Extension

As described in the earlier section, to add User Inputs at time of creating new project, we will add Wizard Extension, which uses an input form.

To create a Wizard Extension, we have to add one Class Library project. In this exercise, we have to perform below mandatory functionality.

  • Add a class inheriting from IWizard interface, which is part of Microsoft.VisualStudio.TemplateWizard namespace.
  • Add a Windows Form and provide input fields like Open File Dialog, text box and Submit button.
  • Create a strong type Assembly with a strong key and register into GAC.

Add a Property Class to store input from user.

Add a ProjectProperty class with get/set properties only (DTO), this class will help to store and transfer user inputs from Form. You can customize this class for your own needs.

public class ProjectProperty
{
    public string Method = "POST";
    public string RequestSchemaFilePath = "";
    public string RequestSchemaFile = "";
    public string ResponseSchemaFilePath = "";
    public string ResponseSchemaFile = "";
    public int SLA = 20;
    public int TimeOut = 15;
    public string ExactTargetURL = @"https://MyWebservice.com/AddCustomer.asmx";
}   

Add a Windows Form with all customized inputs.

Add a Windows Form with all customized controls and exposed one property of type ProjectProperty. So when user Submits the form Wizard can take control values from exposed property.

public ProjectProperty ESInterfaceProject
{
    get { return this.projectProperty; }
}

private void btnSubmit_Click(object sender, EventArgs e)
{
    int.TryParse(txtSLA.Text, out this.projectProperty.SLA);
    int.TryParse(txtTimeout.Text, out this.projectProperty.TimeOut);
    this.projectProperty.ExactTargetURL = txtTargetURL.Text;
    this.projectProperty.Method = txtMethod.Text;
    this.Close();
} 

Implement IWizard interface.

Add a class with implementing IWizard interface.

namespace MyProjectTemplate
{
    public class MyCustomWizard : IWizard
    {
    }
}  

IWizard implementing RunStarted method, shows your Windows Form from this method and maps all customized values (ProjectProperty) to the replacementsDictionary collection.

public void RunStarted(object automationObject, Dictionary<string, 
string> replacementsDictionary, WizardRunKind runKind, object[] customParams)
{
    try
    {
        // Display a form to the user. The form collects 
        // input for the custom message.
        inputForm = new SchemaForm();
        inputForm.ShowDialog();
        
        //customMessage = inputForm.get_CustomMessage();
        
        // Add custom parameters.
        replacementsDictionary.Add("$path1$",
            inputForm.ESInterfaceProject.RequestSchemaFilePath);
        replacementsDictionary.Add("$path2$",
            inputForm.ESInterfaceProject.ResponseSchemaFilePath);
        replacementsDictionary.Add("$file1$",
            inputForm.ESInterfaceProject.RequestSchemaFile);
        replacementsDictionary.Add("$file2$",
            inputForm.ESInterfaceProject.ResponseSchemaFile);
        replacementsDictionary.Add("$method$",
            inputForm.ESInterfaceProject.Method);
        replacementsDictionary.Add("$sla$",
            inputForm.ESInterfaceProject.SLA.ToString());
        replacementsDictionary.Add("$timeout$",
            inputForm.ESInterfaceProject.TimeOut.ToString());
        replacementsDictionary.Add("$targeturl$",
            inputForm.ESInterfaceProject.ExactTargetURL);
            
        EnvDTE.DTE dte = automationObject as EnvDTE.DTE;
        
        string solutionPath = System.IO.Path.GetDirectoryName(dte.DTE.Solution.FullName);
        string projectPath = System.IO.Path.GetDirectoryName(dte.DTE.FullName);
        
        replacementsDictionary.TryGetValue("$projectname$", out projectPath);
        
        projectPath = solutionPath + "\\" + projectPath;
        
        string xsdPath = projectPath + @"\Resources";
        
        replacementsDictionary.Add("$xsdpath$",
            xsdPath);
            
        this._xsdPath = xsdPath;
        
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }
} 

Adding Files to Project

You cannot add files to project structure in RunStarted method, because at time of RunStarted method executing, project structure has been created in Temp folder and DTE is trying to read files from Temp folder which are not existing.

Another problem is that you can't create any file and folder in project directory because at the time of creating file/folder in project folder, IO will create project folder and after RunStarted method DTE will give error because it will already have project folder, and it is assuming that project has been already created.

So the final solution is, you need to copy files after project has been created, which you can achieve by implementing RunFinished method, which executes when DTE completing project creating task.

public void RunFinished()
{
    // To copy a folder's contents to a new location:
    // Create a new target folder, if necessary.
    if (!System.IO.Directory.Exists(_xsdPath))
    {
        System.IO.Directory.CreateDirectory(_xsdPath);
    }
    // copy files
    System.IO.File.Copy(inputForm.ESInterfaceProject.RequestSchemaFilePath, _xsdPath + 
    "\\" + inputForm.ESInterfaceProject.RequestSchemaFile, true);
    System.IO.File.Copy(inputForm.ESInterfaceProject.ResponseSchemaFilePath, _xsdPath + 
    "\\" + inputForm.ESInterfaceProject.ResponseSchemaFile, true);
}

Register in GAC

After compiling the above DLL with Strong Key, register it in GAC. This is a mandatory step as project template has access of GAC.

Updating Project Template Again

Now we are ready with our customized Wizard Extension using IWizard interface. In this section, we will add substitute parameters in our project.

After updating parameters in project template class, it will look like below:

using MyProjectTemplate.BaseProject;
namespace $projectname$
{
    public class $projectname$  : BaseService, IDisposable
    {
 
        public override string Action
        {
            get { return "$method$"; }
        }
 
        public override bool ValidateRequest(object context, object request)
        {
            if (!base.ValidateRequest(context, request)) return false;
 
            //Do customized request validation

            return true;
        }
 
        public override int SLA
        {
            get { return $sla$; }
        }
 
        public override int Timeout
        {
            get { return $timeout$; }
        }
 
        protected override string RequestXML
        {
            get { return "$projectname$.Resources.$file1$"; }
        }
 
        protected override string ResponseXML
        {
            get { return "$projectname$.Resources.$file2$"; }
        }
 
        protected override string TargetURL
        {
            get { return "$targeturl$"; }
            // do the default request header mapping using requestHeader
        }
 
        public void Dispose()
        {
            //Release local XML file
        }
    }
} 

Export Project

Now again, export the above project in the same way we exported in earlier section.

Updating .vstemplate File

Now update .vstemplate file and update with Wizard Extension.

Open .vstemplate file in Notepad and add the below code and save it.

<VSTemplate Version="3.0.0" 
xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" 
Type="Project">
  <TemplateData>
    ...
  </TemplateData>
  <TemplateContent>
    ...
  </TemplateContent>
  <WizardExtension>
    <Assembly>MyProjectTemplate, Version=1.0.0.1, 
Culture=Neutral, PublicKeyToken=27092cf962afb8d7</Assembly>
    <FullClassName>MyProjectTemplate.MyCustomWizard</FullClassName>
  </WizardExtension>

</VSTemplate>    

Zip it and save it in My Exported Templates location.

Now zip the entire project template and .csprj files from "My Exported Templates" location.

Finally

We are almost ready with our customized Project Template with Wizard Extension. If everything is in place, then you will be able to see Project Template in Add/Create project menu in Visual Studio.

When you will create a new project, you will prompt for files and other inputs from user, which information being used to generate class and project. You might need to include Resources folder into project files manually (I am working on this part to create it automatically and will update you once I am done).

This way, you can customize N number of combinations and you can also use in Config files, XML files, etc.

Code

Coming soon.... I will update code in couple of days.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Joshi, Rushikesh
Technical Lead Synechron
United States United States
Having 12 Years of Software Development experience.

Comments and Discussions

 
QuestionCode PinmemberFireBall172529-Jan-13 2:29 
GeneralMy vote of 4 PinmvpKanasz Robert5-Nov-12 2:47 
QuestionWill this work for ItemTemplate? Pinmemberguru.sarkar21-Sep-12 6:03 
GeneralMy vote of 5 PinmemberDishR30-May-12 19:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140905.1 | Last Updated 30 May 2012
Article Copyright 2012 by Joshi, Rushikesh
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid