Friday, February 27, 2009

Creating Project Item Templates in Visual Studio

I'm building a multi-tier application in JavaScript.  This is implementing the Model View ViewModel pattern to keep as much of the code generic as possible, while allowing for customizations in both the UI and data layers for specific browsers and data access methods such as a local SQLLite database or calls to the server.  I'm using code-generation to create my models, but need a good way to create the Controllers, Views, and ViewModels.  I had been using a simple template, but cutting, pasting and replacing text seemed a little inefficient, so I decided to create some Visual Studio Templates to accomplish this goal.
It's fairly simple to create a trivial template, but I needed to add just bit more complexity, I need to build up a custom namespace for my classes, so just a simple ZIP file containing my template didn't cut it.  The answer was to implement IWizard in the Microsoft.VisualStudio.TemplateWizard namespace
We need to create two components for our new templates, a ZipFile containing, *.vstemplate and a .NET Assembly containing the IWizard implementation.
Create your IWizard Implementation
1) Create a Windows Class Library
2) Add the following References

  • EnvDTE
  • Microsoft.VisualStudio.TemplateWizardInterface
3) Create an empty class, and implement the interface Microsoft.VisualStudio.TemplateWizard.IWizard
4) The method you care most about is:
      public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, Microsoft.VisualStudio.TemplateWizard.WizardRunKind runKind, object[] customParams)
5) In this method you can add additional tokens to be used in your template by adding name value pairs to the replacementDictionary
replacementsDictionary.Add("$modulename$", "MyName");
6) In addition you can add a standard windows form to your wizard to collect information
      _inputForm = new ModuleInputForm();
      _inputForm.ShowDialog();
      replacementsDictionary.Add("$modulename$", _inputForm.ModuleName);
7) Within the Project Properties and the Signing Tab, you need sign your assembly.  You will be installing within the GAC, so it needs a strong name.
8) Install your Assembly in the GAC.  The easiest way to do this is to just copy the file from your build output directory to %WINDIR%\assemblies between two explorer windows.
9) After you install your Assembly note the Assembly Name and Public Key Token, you will need these to create your vstemplate.

10) If you make updates to your wizard, you have to make sure you close all instances of Visual Studio .NET so your wizard is reloaded.
Create your vstemplate:
We will need to create a ZIP file that contains at least three components (more if you want to generate multiple files):
1) SomeTemplate.ext - This will contain the template used to create the Visual Studio Project Item
2) Specification.vstemplate - An XML file that contains the configuration for your template
3) Image.ico - An image that will be displayed in the new project item dialog for your custom project template.
What I did was just create a temp directory where I created these files.  Once complete I'll zip them up and show you where to put them so Visual Studio will recognize them.
SomeTemplate.ext, or in my case Controller.js
This is your custom template.  It can contain anything, but what makes this work is the ability to replace tokens within the template.  I wanted to use a custom module name and class name within my JavaScript files, this is only a fragment from the file, but you get the idea.
MyCompany.Controllers.$modulename$.$safeitemname$.registerClass('MyCompany.Controllers.$modulename$.$safeitemname$', null, Sys.IDisposable, MyCompany.Controllers.IController);
Specification.vstemplate or in my case Controller.vstemplate
Next we need to create our .vstemplate file.  This is a small XML file that tells Visual Studio how to build your populated instance of the template, it's format is as follows:
<VSTemplate Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Item">
  <TemplateData>
    <DefaultName>Controller.js</DefaultName>
    <Name>M-V-VM Controller</Name>
    <Description>Model View ViewModel Controller</Description>
    <ProjectType>CSharp</ProjectType>
    <SortOrder>10</SortOrder>
    <Icon>__TemplateIcon.ico</Icon>
  </TemplateData>
  <TemplateContent>
    <References />
    <ProjectItem SubType="" TargetFileName="$fileinputname$.js" ReplaceParameters="true">Controller.js</ProjectItem>
  </TemplateContent>
  <WizardExtension>
    <Assembly>MyCompany.Templates.WizardTemplateInstance, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=a2c453bf57a7f5d7</Assembly>
    <FullClassName>MyCompany.Templates.WizardTemplateInstance</FullClassName>
  </WizardExtension>
</VSTemplate>
Pretty straight forward. but here's a little more information about the file format, one important note, make sure in your ProjectItem node, you have ReplaceParameters="true" to update your tokens.  In addition to the custom tokens we added in the wizard we created above, here is a list of built in tokens.
You need to find yourself an icon for your template.  Make sure it's in the same directory and is specified by the <Icon> node.
Once you have this completed, zip all three files and place them in the directory on your machine similar to:
[USERNAME]\Documents\Visual Studio 2008\Templates\ItemTemplates
Generate your Template
Start Visual Studio, within your solution tree, click on Add New Item and in the bottom section on MyTemplates you should see the following:

Change the name and if the Software God's are shining on you you should see:

where you can enter the name of your module.
Once you hit Save, your new file should get generated any tokens you specified be replaced.  In my case here is a portion of the generated file with the module name of "Sync" and file name of "History" is as follows:
MyCompany.Controllers.$modulename$.$safeitemname$.registerClass('MyCompany.Controllers.$modulename$.$safeitemname$', null, Sys.IDisposable, MyCompany.Controllers.IController);
MyCompany.Controllers.Sync.History.registerClass('MyCompany.Controllers.Sync.History', MyCompany.Controllers.ControllerBase, Sys.IDisposable, MyCompany.Controllers.IController);

Yes that is Javascript, if you are doing client side programming, why aren't you taking advantage of the Microsoft Ajax Client Library?

-ec

No comments:

Post a Comment