Ioan Lazarciuc's Weblog I program, therefore I exist.

25Apr/102

Custom Parameters in Multi-Project Templates

Visual Studio project templates offer two ways of specifying template parameter values:

  • In the .vstemplate file, using a CustomParameters section, as described here.
  • Using a template wizard in order to specify run-time values in the IWizard.RunStarted method, as described here.

The problem is that these mechanisms don’t work as one would expect in the case of multi-project templates. The parameters specified in the root template are not picked up by the child project templates. I tried to find a workaround online but could not find anything. Finally, I came up with a workaround of my own.

My workaround requires that the root project template and the child project templates have template wizards.

For demonstration purposes, let’s assume that the root template has a RootTemplateWizard class assigned as the template wizard. Let’s also assume that the child project that needs parameters values from the root template has a ChildTemplateWizard class assigned as the template wizard. If your child project template does not already have a template wizard, then you need to add one in order to pick up parameter values from the root template.

The main thing that will allow the two wizard classes to communicate is the fact that for multi-project templates the solution/project creation is done within the same application domain. This means that static members from each of the two classes are viewable in both classes.

Let’s say that ChildTemplateWizard needs to specify a $MyInheritedParam$ value. This is done in the IWizard.RunStarted method by adding the value to the replacementsDictionary method parameter with the key “$MyInheritedParam$”. Because we’re anticipating the value being present in a static field, we use that field as the value in the replacementsDictionary, as indicated in the code below:

public class ChildTemplateWizard : IWizard

{

    public static string MyInheritedParam;

    public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)

       {

             replacementsDictionary["$MyInheritedParam$"] = MyInheritedParam;

       }

       …

}

The value for the MyInheritedParam static field can be set from the RootTemplateWizard class, from its RunStarted method. The other IWizard methods are not suitable because they get called only after project generation is complete for all child projects. The code below illustrates setting the value for the static field.

public class RootTemplateWizard : IWizard

{

    public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)

       {

             ChildTemplateWizard.MyInheritedParam = "test value";

       }

       …

}

It’s worth mentioning that RootTemplateWizard and ChildTemplateWizard don’t have to be placed in separate assemblies. If placed in the same assembly, you only have to install a single assembly in the GAC when installing the multi-project template.

P.S. While writing this post and looking up some links for documentation I stumbled upon a blog post where the same principle is used to transmit custom parameters to child project templates. This post did not appear when I was searching for an already existing solution and for me it was not immediately clear that it solved this issue.

Comments (2) Trackbacks (0)
  1. Dear Ioan,

    At first I thought this was a pretty silly behaviour – the parent scope not affecting the children scopes – but your suggested pattern (more than a workaround in my opinion) makes a lot of sense, since it models the hierarchy in the multi-project template and gives full control for every single project generation in it.

    Nice work!

  2. Thanks for the post this definitely helped me. I wanted a multiple project with one wizard and so instead of the way you suggest I decided to:

    // IWizard Class

    private static Dictionary Replacements = null;

    public void RunStarted(object automationObject,
    Dictionary replacementsDictionary,
    WizardRunKind runKind,
    object[] customParams)
    {
    if ( _replacementDictionary != null )
    {
    foreach (var keyValuePair in _replacementDictionary)
    {
    replacementsDictionary[keyValuePair.Key] = keyValuePair.Value;
    }
    return;
    }

    // show dialog and get results
    }

    I’d say your way is a more flexible framework, but my two project templates depended on way too many same variables that I just decided to do it this way.

    Unfortunately you do have to watch out though because some of the project specific variables like $guid#$ get copied over too, but I didnt have a problem with since it was only 2 projects in the solution

    Thanks again for awesome post


Leave a comment

No trackbacks yet.