Download the code for this article here. (Zip file updated to include source code for VSIX and Wizard projects.)
Earlier this year I authored an open-source toolkit, called Simple MVVM Toolkit, to help developers build Silverlight, WPF and Windows Phone applications based on the Model-View-ViewModel design pattern. To enhance developer productivity, the toolkit combines a set of helper classes with code and xml snippets, as well as Visual Studio item and project templates. After installing the toolkit, all a developer needs to do to get started is open Visual Studio and create a new project by selecting one the project templates that appear under the Mvvm category.
Visual Studio makes it extremely easy to create a single-project template. Simply select Export Template from the File menu and follow the prompts. This will produce a .zip file containing the contents of the project and a .vstemplate text file with xml describing the template content and properties.
There are times, however, when it you would like to create a template which generates multiple projects belonging to a single Visual Studio solution. A good example is the SimpleMvmRiaServices project template shown in the first screenshot, which generates a Visual Studio solution with three projects: an ASP.NET Web project, a Silverlight client project, and a Test project for unit tests. To create this template I first had to create individual templates for each of the three projects, then extract the contents of each zip file in order to combine them into a single multi-project Visual Studio template, with a .vstemplate file describing the contents. Here is an example of a VSTemplate file I created for a Multi-Project WCF REST Template I recently did for a talk I gave at a .NET developer group (some of the content has been elided for clarity).
<VSTemplate Type="ProjectGroup"> <TemplateData> <Name>WCF REST Service</Name> <Description>REST Service Template by Tony Sneed</Description> <ProjectType>CSharp</ProjectType> <TemplateGroupID>WCF</TemplateGroupID> <RequiredFrameworkVersion>4.0</RequiredFrameworkVersion> </TemplateData> <TemplateContent> <ProjectCollection> <ProjectTemplateLink ProjectName="$safeprojectname$.Client"> Client\Client.vstemplate </ProjectTemplateLink> <ProjectTemplateLink ProjectName="$safeprojectname$.Entities"> Entities\Entities.vstemplate </ProjectTemplateLink> <ProjectTemplateLink ProjectName="$safeprojectname$.Service"> Service\Service.vstemplate </ProjectTemplateLink> <ProjectTemplateLink ProjectName="$safeprojectname$.Web"> Web\Web.vstemplate </ProjectTemplateLink> </ProjectCollection> </TemplateContent> <WizardExtension> <Assembly>RestTemplateWizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c9a76f51a8a9555f</Assembly> <FullClassName>RestTemplateWizard.RootWizard</FullClassName> </WizardExtension> </VSTemplate>
The template encompasses four project templates: Client, Entities, Service and Web. Notice the variable, $safeprojectname$, which captures what the user entered for the project name in the New Project dialog. There is also a WizardExtension element, which references an IWizard implementation. IWizard allows you to control the template creation process, for example, to allow projects to reference one another or to set project properties, such as the RIA Service project link required for my SimpleMvvmRiaServices template.
For my WCF REST template I wanted the Client and Service projects to both reference the Entities project, and for the Web project to reference the Service project. The problem here is that within each child template, $safeprojectname$ represents the current project name. There needs to be a way to pass in the root $safeprojectname$ from the parent template. This is where the IWizard project comes in.
Start by creating a class library project and referencing the assembly, Microsoft.VisualStudio.TemplateWizardInterface.dll. Create a RootWizard class that implements IWizard. In the RunStarted method you can capture $safeprojectname$ from replacementParameters and store it in a public static Dictionary<string,string> so that it can be captured by a ChildWizard class and stored as $saferootprojectname$ in replacementParameters.
public class RootWizard : IWizard { // Use to communicate $saferootprojectname$ to ChildWizard public static Dictionary<string, string> GlobalDictionary = new Dictionary<string,string>(); // Add global replacement parameters public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams) { // Place "$saferootprojectname$ in the global dictionary. // Copy from $safeprojectname$ passed in my root vstemplate GlobalDictionary["$saferootprojectname$"] = replacementsDictionary["$safeprojectname$"]; } // Other members elided for clarity }
public class ChildWizard : IWizard { // Retrieve global replacement parameters public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams) { // Add custom parameters. replacementsDictionary.Add("$saferootprojectname$", RootWizard.GlobalDictionary["$saferootprojectname$"]); } // Other members elided for clarity }
You can now edit project references in child project csproj files, as well as using directives in source code files, to include $saferootprojectname$ where you want the user-entered project name to appear.
<Project ToolsVersion="4.0" DefaultTargets="Build"> <ItemGroup> <ProjectReference Include="..\$saferootprojectname$.Entities \$saferootprojectname$.Entities.csproj"> <Project>{3EC4AE2B-892C-42FC-9814-D46EB06A22E4}</Project> <Name>Entities</Name> </ProjectReference> </ItemGroup> </Project>
using System; using $saferootprojectname$.Entities; namespace $safeprojectname$ { class Program { static void Main(string[] args) { // Client code goes here ... } } }
Next, make sure to sign your wizard assembly, using a test certificate if you like, and then obtain the public key token by opening a Visual Studio command prompt at the location of your wizard dll and entering the command (replace WizardAssembly with your dll file name):
sn –T WizardAssembly.dll
Now that you’ve created a multi-project Visual Studio template, you need a way to deploy it. The best way is to create a VSIX project, which allows you easily publish your template to the Visual Studio Extensions Gallery, so that developers from around the world can install it by selecting Extension Manager from the Tools menu right from within Visual Studio! First you’ll need to install the Visual Studio 2010 SP1 SDK (assuming you’ve first installed SP1 for VS 2010). Then create a new VSIX project (in the Extensibility category that now appears in the New Project dialog). What you get is a nice designer for editing the .vsixmanifest file, where you can set properties and add content.
There are two items you will want to add as content. One is the template wizard assembly you just created, and the other is a zip file containing the multi-project template contents, including both the parent .vstemplate file and icons, as well as a folder for each child project template containing the project contents and .vstemplate file.
The important thing to note here is that the assembly for the IWizard implementation need not be installed into the Global Assembly Cache when it is packaged into the same VSIX project as the templates using it. Another thing you might want to do when adding the template zip file is specify the category under which you want your template to appear in the Visual Studio New Project dialog. In the the case of my WCF REST Template, I have indicated it should appear under the WCF category.
When you build the extensibility project, you’ll get a .vsix file that you can upload to the Visual Studio Extensions Gallery, so that it will appear in the Online Gallery when developers select Extension Manager from the Tools menu. Enjoy.
You can download the code for this article here.
Useful, very detailed post. Thanks!
After installing my VSIX & resarting VS, clicking File > New Project & selecting my multi-project template, I get this error:
Error: this template attempted to load component assembly ‘XXX, Version=1.0.0.0, Culture=neutral, PublicKeyToken=XXX’
I’ve double-checked the PK token & it’s definitely correct in all the .vstemplate files. Do you have any idea what could be causing this?
Never mind. I was using delayed signing!
Shouldn’t the guid match with the referenced project? I’ve added a method in the “RootWizard” which removes and re-adds all project references to resolve this issue..
Thank you!
can you post this?
I cant find the RootWizard class or the RestTemplateWizard assembly anywhere in your zip file. Am I missing something here?
I downloaded the .vsix from the Visual Studio Extensions Gallery. It contained the RestTemplateWizard assembly.
Pingback: Creating Multi-Project Templates « Bitmask
I created the Multi-project template and installed the VSIX file. I created a new solution using this extension but I am missing dll references. How can I get the dll references fixed? do I always do it manually, please let me know if there is a way to fix this issue. Thanks
Hey John,
I worked with Ed Gore from Microsoft and we failed together to get this all to work as expected. As a result, I built a quick and dirty utility that works well so far.
https://github.com/keithminder/Visual-Studio-Multi-Project-Solution-Template-Tool
give it a spin and let me know if it works out. I will continue to polish it and then make it into a visual studio extension.
Sorry I’ve been so delinquent in responding. I noticed the download file was incomplete, so I updated it to include source code for the VSIX and Wizard projects. The key to getting the project references right is to make sure the relative path is correct and that the root and child wizards are correctly setting the $saferootprojectname$ variable. The project guids also need to match.
There are a number of manual steps to perform and it’s easy to miss something. So I found it useful to install the wizard assembly into the GAC and test the template by copying the zip file to the appropriate location under Documents\Visual Studio 2010\Templates. Keith’s multi-project solution template tool sounds intriguing, and I’ll be sure to check it out.
I’m REALLY not into wheel reinvention, so wanted to take the time to explain why I decided to build something. The premise follows: 1. VS solution files are text files. 2. Working with text files is easy. 3. Creating multi-project solution templates should be easy.
Hi Keith, Appreciate your contrib! Building multi-project solution-based templates can get hairy because of all the different text files you need to hand-edit. Great idea to automate this. Have you published it to the VS Extensions Gallery yet?
P.S. Just posted a template for WCF SOAP services: http://visualstudiogallery.msdn.microsoft.com/2b106597-fda0-43eb-aa21-4f065584ef10.
Not yet. It will be my first time too. Can I reach out if I get stuck on that part?
Sure, I’ll see what I can do. Sounds like you may want to look at building a VS add-in.
Pingback: WCF SOAP and REST Multi-Project Visual Studio Templates - .NET Code Geeks
After doing this stuff, when I am trying to use the template Iam getting error “The given Key was not present”
Now this issue got resolved.But the references are still with the old names, not updated to the new child reference
When i tried to create this template for Visual Studio 2005, it doesn’t replace the variable $safeprojectname$ and all the projects are create like $safeprojectname$.Web, $safeprojectname$.Persistence and so on. Any suggestion for this?
I’m not sure if it works for versions of Visual Studio prior to 2008.
I am trying to create a multi-project template in VS2012 and have created the required ProjectTemplates & IWizard implementation assembly.
As mentioned by you, we need to add the Wizard assembly as CONTENT in VSIX. But in VS2012 VSIX we don’t have CONTENT tab.
How do I add the IWizard assembly?
Can you help me with this?
If you’re using the VS 2012 SDK, there is an Assets tab. For the Type specify Microsoft.VisualStudio.Assembly. For the Source, you can either select the DLL as a file on the system or, better yet, add the wizard project to the solution and set source to Project in the Current Solution. You can then embed it in the TemplateWizard folder.
Ok. Let me give it a try. Thanks for the quick reply.
Thanks Tony. Yup its worked great. 🙂
Has anyone tried preinstalling NuGet for a multi-project template.
I am trying to add a NuGet to only 1 project of the 3 project I have in the multi-project template.
Any pointers will be helpful.
~Bhavya
Yes, I’ve done it with my Trackable Entities extension. Here is a blog post on how to do it: http://docs.nuget.org/docs/reference/packages-in-visual-studio-templates.
This article is based, however, on the VS 2010 SDK. You should download and inspect the sample project, and then make adjustments for a VS 2012 VSIX project.
Thanks Tony.
I have a multi project template and i want to add a condition in the solution vstemplate file in order to control which project to add, depends on the wizard user input.
the $if$($paramname$==value) does not work in the solution vstemplate file.
any ideas ?
I don’t know if anyone is still coming across this blog but i have created an assembly that can be used directly to expose top level template parameters too all the child templates. It also extends the number of Guids available from 10 to 100. You can find the source or just the binaries if that’s all you need here [GlobalParams|https://globalparams.codeplex.com/]
I don’t know if anyone is still coming across this blog but i have created an assembly that can be used directly to expose top level template parameters too all the child templates. It also extends the number of Guids available from 10 to 100. You can find the source or just the binaries if that’s all you need here https://globalparams.codeplex.com/
In my case $saferootprojectname$ never gets resolved, even though it seems to pick up the Wizard. I get $saferootprojectname$ all over my project. 😦
You probably have a typo in your wizard code. Check that more closely and you’ll find the problem.
Excelent Tutorial!
Pingback: Visual Studio Templates - The BHW Group
I am currently finding that the ProjectName from the ProjectGroup template is replacing the $safeprojectname$ in my using statements. Is this why you use $saferootprojectname$ or is that more specific to your wizard?
$safeprojecteootname$ is specific to my wizard and represents the solution name.
So it’s kind of performing the same function as I described. I have a problem where the names of each project in the main vstemplate is replacing the names in the using statements.
using $safeprojectname$.service;
namespace $safeprojectname$
becomes:
using test.mvc.service;
namespace test.mvc
This should obviously be test.service!
Am I correct in saying that your wizard method resolves this issue?
Yes, this will reaolve your issue.
Excellent, thanks very much!
I went round in circles a bit, but under VS 2013 you can achieve the $safeprojectrootname$ functionality without a wizard using $ext_safeprojectname$ and the following in the ProjectGroup vsTemplate file.
Now to just resolve my nuget bundling in my VSIX and I can call it a day!
^ code was emitted..
ProjectTemplateLink ProjectName=”$safeprojectname$.Example” CopyParameters=”true”
Hi, I created the templates and template wizard in vs2013. I created the vsix file (with the templates and the wizard as assets) and then installed it, All went well. I could also see the template installed under “extension and updates” and under File\new projects. However, when I select the new project and click Ok, Nothing is created. All I see is a status bar at the bottom telling me that the project is created. But the solution explorer is empty and there are no files in windows explorer. I’m not sure what I’m missing. Any guidance would be of great help
It looks like something is wrong with your project template. You should first test the template by placing the zip file you created for it in your local Documents folder – under Visual Studio 2013\Templates\Visual C#. Once you’ve copied the zip file there, you should be able to execute it via the New Project dialog in Visual Studio. After you’ve tested the project template, then you can make it part of your VSIX project.
Thanks a lot. I finally got it. I forgot to add “projectgroup” to my multi project template. It is now working. I have one more question, if you do not mind. I need to add some solution level items. Would you have ay suggestions? I’m already using wizard extension to do NuGet dependencies for my project.
Yes, it’s not well documented how to add solution items from a project template. But I found a way to add a ReadMe file to the solution in a Wizard project by interacting with DTE2. You can find the wizard code here on line 65:
_dte2.ItemOperations.AddExistingItem(destReadMePath);
Let me know if this works for you.
You probably long since forgot about this article but I found it and followed it and got a lot out of it :).
I would suggest an improvement. Its not obvious that in each template specification you need to specifiy the child template in the wizard xml elements, its obvious when you look at your zip file this is what should be done but it would be an improvement in your article (which is good) if you specify this explicitly.
Great job in documenting this.
Thanks. Your comment helped me to get over my issues
Pingback: How To Add Template In Visual Studio 2012 | Tiriya2
I know this is a fairly old post now but I’m looking to create a multi project solution and this looks to cover exactly what I need. All is fine except that my inter project references for my created solution are broken as the $saferootprojectname$ is not being replace and so I have references such as
$saferootprojectname$.Models in my projects.
I can see that my wizard code is running because when I force an error in the RootWizard RunStart method, I get an error when I try to create a new project using this template. I guess I’ve overlooked something but whatever it is it’s had me scratching my head for most of the day
Do you know how to debug and set breakpoints with the experimental instance?
No I dont. I have put some logging code in RunStarted in both RootTemplate and ChildTemplate as follows
string logText = string.Format(“{0},{1}”, DateTime.Now.ToString(“dd/MM/yyyy HH:ss”), “RootWizard_RunStarted”);
if (!File.Exists(_logPath))
using (StreamWriter sw = File.CreateText(_logPath))
sw.WriteLine(logText);
else
using (StreamWriter sw = File.AppendText(_logPath))
sw.WriteLine(logText);
I get output from Root but not Child. Im just looking into Krystan’s post just above mine to see if I have missed something
This article describes how you can configure Visual Studio to start an experimental instance, so you can set breakpoints and debug a VSIX extension: http://weshackett.com/2009/11/configure-vsix-project-to-enable-debugging.
Thanks. I managed to get debugging working and, when running from dev environment, all works as expected. My issues must therefore be related to my deployment of vsix.
i had the same problem you did. take a look at my previous comment if you want a wizard you can drop in and use. that project i created was based on this blog
thanks. I’ll take a look
I have made some progress with my multi-project solution template. I have a couple of outstanding issues that I would like to overcome.
1) The structure of the solution created using the template has an additional sub-directory. Ie if I click New Project.. and I select my multi-project template, I get my solution created with all of my projects correctly referencing each other but instead of have a directory structure such as
MyApp
MyApp/MyApp.sln
MyApp/MyApp.Models/MyApp.Models.csproj
MyApp/MyApp.Repositories/MyApp.Repositories.csproj
MyApp/MyApp.Services/MyApp.Services.csproj
etc.
I get
MyApp
MyApp/MyApp.sln
MyApp/MyApp/MyApp.Models/MyApp.Models.csproj
MyApp/MyApp/MyApp.Repositories/MyApp.Repositories.csproj
MyApp/MyApp/MyApp.Services/MyApp.Services.csproj
etc.
ie my solution file is not at the same level as my project specific sub-directories
2) I’m also getting issues with any packages added via nuget. The generated solution does not have a packages directory. If I try to restore I notice a number of packages being pulled down but when I try to subsequently build I get the following error
This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is ..\packages\Microsoft.Net.Compilers.1.0.0\build\Microsoft.Net.Compilers.props.
If I copy the packages folder from my template solution to the directory as all of the project sub-directories and build, I get a solution that builds, though I get another packages directory created one level up as well
Any suggestions on either of the two points would be appreciated
@Terry:
Without more information, it’s difficult for me to tell why you might be getting an extra directory level. Perhaps you could create a GitHub repo with your code? You might also want to check out my Trackable-Entities extensions: https://github.com/TrackableEntities/trackable-entities/tree/develop/Extensions.
You need to use the NuGet template wizard to install packages from your VSIX. See this article in the NuGet docs for more info. Also, have a look at my implementation.
I tried to set up a git repository but failed miserably. I have upload project to https://drive.google.com/open?id=0BzU4R4nv9EslX2ZqNTVibjVaNkE
Its the CanaccordSolutionTemplate.zip that Im concerned with. It appears as “Canaccord solution template c#” in New Project dialog in VS2015
No problem .. I’ll have a look.
Have you had a chance to see whats wrong with my code Tony?
I downloaded CanaccordSolutionTemplate.zip and had a look, but I think your problem is probably with SolutionTemplateWizard. What I would suggest is that you debug your VSIX using the experimental instance. Then you should be able to inspect file paths and figure out why you have an extra folder level. That’s the best I can do for you at this point. See this SO answer for more info on debugging VSIX projects.
First of all this really is a good blog to make a solid VSIX Extension. Mine works fine :). But i wanted to add another function to my Wizard and have been looking for a solution and did not find something. I was wondering if there is a way to Install the latest NuGet Package Version on creation process of an Item/ProjectTemplate from your Wizard programmatically. Tony do you have any experience there?
My current solution is to go into the used template folder with customParams[0].ToString, read out all packages.config files in these folders and get the NuGetPackages Id and version.
Then i try to install the NuGetPackages via IVsPackageInstaller with the newest Version number filtered out of NuGetRepository (https://packages.nuget.org/api/v2).
My problem at the moment for this solution is that i need the Project object, to Install the Packages via IVsPackageInstaller, which seems to be acessible via the RunStarted(object automationObject, …..), saying that i need the “Project” object from the created project. First i thought to access it via ProjectFinishedGenerating(Project project){} but the parameter project always has the value null. I have no experience with EnvDTE objects and in some blogs i could only read that there are possibilities to convert the automationObject into the Project object via EnvDTE. Can you help?
This is my first post and i have not that much experience in programming. If anything is missing or you need further information just say and i will provide it. Any help or suggestions would be appreciated.
@Bernd:
See this article.
See this comment with a link to an article on debugging a VSIX.
See this comment for a link to one of my GitHub repos where you can see an example of this.
Nice article,
I have one question, I want to create one project template in VS 2017 in which I have one solution which contain three project and one solution level folder “submodule”. Please help me on this.
WCF REST Service
REST Service Template by Tony Sneed
CSharp
WCF
4.0
Client\Client.vstemplate
Entities\Entities.vstemplate
Service\Service.vstemplate
Web\Web.vstemplate
RestTemplateWizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c9a76f51a8a9555f
RestTemplateWizard.RootWizard
Thanks for your post! I have a problem by adding file in . Could you tell me please how I can do it?
I have a problem by adding file (file isn’t referring to any project, it’s should be just in solution folder) in SolutionFolder.
Cool article. Unfortunately, the link to your zip file no longer works. Can you please update it? Thank you.