GAS04: Adding Project References#

[This is episode 4 of the Guidance Automation Series]

Last time, we did a whole lot of work just to generate one very empty C# project. Just think of that as the healthy dish before the dessert: it's probably necessary but not really what you've been waiting for. This time, we'll really start using the power of Guidance Automation to generate a second project and add a project reference to it.

Adding Another Project

Now that we have seen how to generate the runtime project for the Enterprise Library Application Block, we'll just do the same thing to generate the design-time project. By convention, this has the same name as the runtime project, with ".Configuration.Design" added at the end. This is easily done in the solution's vstemplate file by adding a second project to the ProjectCollection node as such:

<ProjectTemplateLink ProjectName="$ApplicationBlockNamespace$.$ApplicationBlockName$.Configuration.Design">Projects\DesignTime\DesignTime.vstemplate</ProjectTemplateLink>

For this to work, we also need the DesignTime.vstemplate file in the proper location, but that's pretty easy to do: we can just copy the Runtime.vstemplate file and associated project files and make a few basic modifications to change the name. So with very little additional work, we have now generated two empty projects already.

Now of course, this design-time project will need information from the runtime project (e.g. the configuration types of the application block that are used by the runtime but need to be configured in the design-time project), so we need to add a project reference to it. Project references are actually stored in the referencing project's project file, but this isn't something we can generate out-of-the-box like before because it uses the GUID from the referenced project. This GUID is generated by Visual Studio at the time the solution is unfolded (remember the $guid1$ parameter) and we don't have access to that GUID outside the scope of that file. But Visual Studio knows how to add Project References, so we actually want to have the environment perform some action for us.

Actions

Actions are actually a big part of Guidance Automation, and they represent pieces of code that can run at the end of a recipe (and in other places too, but let's stick to the current problem). There are a lot of predefined actions in the Guidance Automation framework, but you can also define your own. To use an action, it can be registered in the Actions section in the recipe xml file, so that each one will be run in sequence at the end of the recipe. Actions have input and output arguments that can be configured, and it is important to note that these are strongly typed, which means that you're really passing objects from and actions and not just strings. The input arguments can either come from the recipe (e.g. a value the user has entered in the wizard), or they can be the output of another action.

Adding The Project Reference

To add a Project Reference, the predefined AddProjectReferenceAction seems to be just what we need here. It takes two input arguments: the referring project and the referenced project. Both are of the type EnvDTE.Project, meaning that they represent the actual project automation objects in Visual Studio. So we need a way to retrieve these projects, and that's where another action, aptly named GetProjectAction comes into play. This searches for a project based on its name and puts the project in an output argument so we can chain the two together as follows:

<Actions>
  <Action Name="GetRuntimeProject" Type="Microsoft.Practices.RecipeFramework.Library.Actions.GetProjectAction, Microsoft.Practices.RecipeFramework.Library">
    <Input Name="ProjectName" RecipeArgument="RuntimeProjectName" />
    <Output Name="Project" />
  </Action>
  <Action Name="GetDesignTimeProject" Type="Microsoft.Practices.RecipeFramework.Library.Actions.GetProjectAction, Microsoft.Practices.RecipeFramework.Library">
    <Input Name="ProjectName" RecipeArgument="DesignTimeProjectName" />
    <Output Name="Project" />
  </Action>
  <Action Name="AddProjectReference" Type="Microsoft.Practices.RecipeFramework.Library.Solution.Actions.AddProjectReferenceAction, Microsoft.Practices.RecipeFramework.Library">
    <Input Name="ReferringProject" ActionOutput="GetDesignTimeProject.Project" />
    <Input Name="ReferencedProject" ActionOutput="GetRuntimeProject.Project" />
  </Action>
</Actions>

Notice that the AddProjectReference action has inputs that come from the other two actions, and these in turn have inputs that come from the recipe. Now if you remember, the user didn't specifically provide the name of the projects to be generated, only the namespace and the name of the application block. We can use these arguments to derive the actual project names using the built-in ExpressionEvaluatorValueProvider.

Value Providers

A value provider is a class that provides values to arguments. Just like a Converter can convert and validate arguments, value providers can put values into arguments. Some of the built-in value providers can return the currently selected class in Visual Studio or a certain project by its name, but there's also a pretty powerful ExpressionEvaluatorValueProvider that can return values based on other arguments through expressions.

In this case, we simply want to build up the name of the runtime project by appending the two fields the user has entered:

<Argument Name="RuntimeProjectName">
  <ValueProvider Type="Evaluator" Expression="$(ApplicationBlockNamespace).$(ApplicationBlockName)">
    <MonitorArgument Name="ApplicationBlockNamespace" />
    <MonitorArgument Name="ApplicationBlockName" />
  </ValueProvider>
</Argument>

By also supplying the MonitorArgument values, the expression will automatically be re-evaluated if one of the monitored arguments has changed. We can now base the design-time project's name on this argument by appending a suffix to the project name as follows:

<Argument Name="DesignTimeProjectSuffix">
  <ValueProvider Type="Evaluator" Expression="Configuration.Design" />
</Argument>
<Argument Name="DesignTimeProjectName">
  <ValueProvider Type="Evaluator" Expression="$(RuntimeProjectName).$(DesignTimeProjectSuffix)">
    <MonitorArgument Name="RuntimeProjectName" />
    <MonitorArgument Name="DesignTimeProjectSuffix" />
  </ValueProvider>
</Argument>

Side note: it would also have been possible to use the expression "$(RuntimeProjectName).$(DesignTimeProjectSuffix).Configuration.Design" directly (without creating the DesignTimeProjectSuffix argument) but unfortunately there's a problem with the expression parser that makes the wizard very slow in this case.

Now that we have the names for both projects stored in arguments, we can also replace the project names in the vstemplate files that were still made up of the application block's name and namespace individually. E.g., we can replace ProjectName="$ApplicationBlockNamespace$.$ApplicationBlockName$" by ProjectName="$RuntimeProjectName$".

Where Are We

At this point, we've seen how to generate a second project with a project reference by using actions that are chained together. We've also seen how to use value providers that put values into arguments and how to use these values in actions. Next time, we'll see how to build our own value providers and have them interact with other arguments. Furthermore, we'll add binary references to some of the Enterprise Library assemblies and add post-build events to the projects. Stay tuned!

Download the source code for the current state of the Guidance Package.

Thursday, December 28, 2006 5:45:21 PM (Romance Standard Time, UTC+01:00)
Getting into learning GAT and appreciate your tutorials. Let me know when you get more or if you know of other resources.

Dave
Monday, January 15, 2007 3:32:30 PM (Romance Standard Time, UTC+01:00)
After doing the GAS03 with success, I tried to do the GAS04, but I have this exception :

An exception occurred during the binding of reference or execution of recipe CreateSolution.
Error was : Failed to load value providers..

I don't know what I can do to fix this problem.

Thank you for your help.
Friday, April 13, 2007 9:32:25 AM (Romance Standard Time, UTC+01:00)
Greats blog I like it.
Comments are closed.
All content © 2008, Jelle Druyts
On this page

Recent Photos
www.flickr.com
This is a Flickr badge showing public photos from Jelle Druyts. Make your own badge here.
Advertising
Top Picks
Statistics
Total Posts: 345
This Year: 8
This Month: 0
This Week: 0
Comments: 523
Archives
Sitemap
Disclaimer
This is my personal website, not my boss', not my mother's, and certainly not the pope's. My personal opinions may be irrelevant, inaccurate, boring or even plain wrong, I'm sorry if that makes you feel uncomfortable. But then again, you don't have to read them, I just hope you'll find something interesting here now and then. I'll certainly do my best. But if you don't like it, go read the pope's blog. I'm sure it's fascinating.

Powered by:
newtelligence dasBlog 2.0.7226.0

Sign In