Introduction
Project Templates and Item Templates are hidden gems of Development in Visual Studio. It not only improves development time but also forces any architectural concept specially in large projects.
This is not a first attempt to write any article on Project Template but I am trying to address what ever I developed in my recent work. Going forward I may extend this article for future aspects.
Background
Project Templates and Item Templates are providing common project structure to enterprise solution. Project Templates are very useful when you have series of similar project in one/many solution(s). It provides common project references, class structure, default implementation and lot more.
Get more information about Project and Item templates @ http://msdn.microsoft.com/en-us/library/6db0hwky.
Using the code
In this article, we will create a customize Project Template with Wizard Extension. You will get more on project template at MSDN site http://msdn.microsoft.com/en-us/library/s365byhx.aspx. We will use Wizard Extension to get inputs from user at 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 customize Class, customize 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 class 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;
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"; }
}
public void Dispose()
{
}
}
} Make sure that you are able to compile project so that when we will create template, our created project should not introduce any errors.
Substitute Parameters in Project
Now our project is ready to substitute parameters.
In 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 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;
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"; }
}
public void Dispose()
{
}
}
}
Note: Don't worry now you will not 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 a 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 use 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 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 property 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 Submit 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, show your windows form from this method and map all customize values (ProjectProperty) to the replacementsDictionary collection.
public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)
{
try
{
inputForm = new SchemaForm();
inputForm.ShowDialog();
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 can not add files to project structure in RunStarted method, because at time of RunStarted method executing, project structure is been creating in Temp folder and DTE is trying to read files from Temp folder which are not existing.
Other problem is you can't create any file and folder in project directory because at 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 assuming that project has been already created.
So the final solution is, you need to copy files after project has been creating, which you can achieve by implementing RunFinished method, which executes when DTE completing project creating task.
public void RunFinished()
{
if (!System.IO.Directory.Exists(_xsdPath))
{
System.IO.Directory.CreateDirectory(_xsdPath);
}
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 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;
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$"; }
}
public void Dispose()
{
}
}
} Export Project
Now again export above project in 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 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 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 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.