Visug Session: Guidance Automation Toolkit#

If you've been looking at my Guidance Automation Series posts lately but didn't really "feel" it yet, I have good news for you: on Wednesday August 9th I'll be giving a session at the Belgium Visual Studio User Group (Visug) on the Guidance Automation Toolkit. This should give you a good overview of the technology and hopefully show you why I'm so excited about its possibilities when undertaking large developments projects.

You can register for this free session simply by adding a comment to the Visug announcement post. Here's the official announcement:

The Guidance Automation Toolkit provides a means for a solution architect or lead developer to deploy consumable guidance to developers.

If you've ever published a 40-page document full of guidelines, do's and don'ts on how to structure solutions and projects in your company and what the namespaces and class names should be, if the developers need to be able to start new projects quickly, if you have a need for code generation, or all of the above - this toolkit is for you. Microsoft is itself using this technology to publish their Software Factories, like the Web Service Software Factory, so that says something about the quality and future of this fine piece of software.

In this presentation, Jelle will show you how this works and how to build your own automated guidance so you too can benefit from its huge number of features.

Hope to see you there!

Tuesday, July 25, 2006 5:35:15 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Type Name Suffixes#

Back when I was preparing my "Best Practices In Framework Design" talk for the Developer & IT Pro Days in February, I got the following question during my dry run: “Why do you need to suffix exceptions (e.g. ArgumentException) and attributes (e.g. ObsoleteAttribute) but not enums (e.g. Color, not ColorEnum)?”.

I didn’t really have an answer other than "because Microsoft said so" at that time, but I just discovered the reason behind it while re-reading the excellent Framework Design Guidelines book. It’s the following guideline that makes the difference:

Consider ending the name of derived classes with the name of the base class.

So you should suffix exceptions and attributes, simply because they inherit from the System.Exception and System.Attribute base classes:

public class ArgumentException : Exception { ... }
public class ObsoleteAttribute : Attribute { ... }

Enums - and delegates as well for that matter - should not be suffixed because they don’t (explicitly) inherit from an enum or delegate base class:

public enum Color { ... }
public delegate void ClickEventHandler(...)

Behind the scenes, the compiler will make an enum a subclass of System.Enum and a delegate of System.MulticastDelegate, but you don’t explicitly state this in most languages and in fact you can’t build inheritance hierarchies with these types. So they don’t need to follow the guideline.

Maybe it doesn't really matter that much to you, but I thought it was a nice piece of background information on the "why" anyway :-)

Thursday, July 20, 2006 9:03:22 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

GAS06: Generating Classes#

[This is episode 6 of the Guidance Automation Series]

Last time, we added references to Enterprise Library and made sure the build output was copied to the configuration tool directory. Now to make the application block show up in this configuration tool, it needs to register some classes and commands so that the tool knows how to display it. Without implementing the full works here, let's see how we can already generate some classes that will serve as an excellent starting point to implement the rest of the application block.

Adding Default Classes

Actually, we've already seen how to add classes in the first episode of this series: the AssemblyInfo.cs file was added to the project by including it in a ProjectItem inside the project's vstemplate file and by including it in the C# project file (so that MSBuild will build it). The only extra feature we'll be using here is to change the target file name of each file to include the name of the application block.

To make it possible to add the application block in the configuration tool, we have to define a ConfigurationDesignManager class and register it in the AssemblyInfo.cs file. So we need to update that file and add a bunch more, including a CommandRegistrar, a NodeMapRegistrar and a root settings node. For the purpose of all these classes and how they should be implemented, I'll gladly refer to Mark Seemann's excellent article on "Speed Development With Custom Application Blocks For Enterprise Library" again. All we need to do here is provide the minimum amount of information to get it registered with the configuration tool and leave the rest up to the actual developer of the application block.

So we add all the necessary files to the project template, include them in the C# project file and register them in the vstemplate file as such:

<ProjectItem ReplaceParameters="true" TargetFileName="$ApplicationBlockName$CommandRegistrar.cs">CommandRegistrar.cs</ProjectItem>
<ProjectItem ReplaceParameters="true" TargetFileName="$ApplicationBlockName$ConfigurationDesignManager.cs">ConfigurationDesignManager.cs</ProjectItem>
<ProjectItem ReplaceParameters="true" TargetFileName="$ApplicationBlockName$NodeMapRegistrar.cs">NodeMapRegistrar.cs</ProjectItem>
<ProjectItem ReplaceParameters="true" TargetFileName="$ApplicationBlockName$SettingsNode.cs">SettingsNode.cs</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="$ApplicationBlockName$SettingsNode.bmp">SettingsNode.bmp</ProjectItem>

Notice the TargetFileName attribute here, to specify a new name for the file so that it includes the name of the application block as it was entered by the user. Also note that we're adding a bitmap file here (without replacing any parameters, to make sure we're not accidentally messing with the binary data), which is also added in the C# project file but then as an embedded resource:

<ItemGroup>
  <EmbeddedResource Include="$ApplicationBlockName$SettingsNode.bmp" />
</ItemGroup>

That's all there is to it. If we now register and run the Guidance Package, we'll get a project that includes all of these files. If we build it (without touching anything), it will automatically get copied into the configuration tool's directory (because of the post-build event), and we can already add the application block to an application directly!

Generating Classes

Now that we already have a root node in the project, suppose we want to add more nodes for the configuration tool. The number of nodes in the application block depends on what the developer has in mind and could even be unknown when the application block is being created. So in stead of adding more classes at the time the solution is unfolded, let's add a command to Visual Studio that allows us to generate a class on-demand. This can be done by adding a RecipeReference to a Visual Studio Template or by adding a separate template for the class in the Templates\Items directory.

  • With a RecipeReference, you have full control over the process: when the recipe is executed, you can choose to show a dialog box prompting for more information or you can go directly to work by executing actions. A drawback is that you also need to include the actual actions to generate the class content and add it to the project, although this isn't all too hard.
  • With a Template Reference, you will always get the "Add new item" dialog box that allows the developer to choose the new class' filename. This can be a drawback if you need to control the filename as well, but this mechanism does make it easier to generate the class and add it to the project.

Since adding the class as a Template Reference is very similar to adding a solution as a Template Reference (like we've already been doing all along), let's implement the new nodes as RecipeReferences. To allow the developer to add dynamically created items to a project, we need to attach a reference to a recipe in that project's vstemplate file. In this case, we'll add the following information to the vstemplate file of the DesignTime project:

<WizardData>
  <Template xmlns="http://schemas.microsoft.com/pag/gax-template" SchemaVersion="1.0">
    <References>
      <RecipeReference Name="AddConfigurationNode" Target="/" />
    </References>
  </Template>
</WizardData>

The References node has been added to register the AddConfigurationNode recipe to the root of the project (specified by the target). If there would be subdirectories, you could scope the target down by setting Target="/Nodes", for example.

The AddConfigurationNode recipe needs to be defined in the package's main xml file as such:

<Recipe Name="AddConfigurationNode">
  <Caption>Configuration Node...</Caption>
  <Description>Adds a Configuration Node class.</Description>
  <HostData>
    <Icon Guid="FAE04EC1-301F-11d3-BF4B-00C04F79EFBC" ID="4542" />
    <CommandBar Name="Project Add" />
  </HostData>
  <Arguments>
    <!-- User Input -->
    <Argument Name="NodeName" Required="true">
      <Converter Type="Microsoft.Practices.RecipeFramework.Library.Converters.CodeIdentifierStringConverter, Microsoft.Practices.RecipeFramework.Library"/>
    </Argument>
    <!-- Derived Arguments -->
    <Argument Name="CurrentProject" Type="EnvDTE.Project, EnvDTE, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
      <ValueProvider Type="Microsoft.Practices.RecipeFramework.Library.ValueProviders.FirstSelectedProject, Microsoft.Practices.RecipeFramework.Library" />
    </Argument>
    <Argument Name="TargetFile">
      <ValueProvider Type="Evaluator" Expression="$(NodeName).cs">
        <MonitorArgument Name="NodeName" />
      </ValueProvider>
    </Argument>
    <Argument Name="TargetNamespace">
      <Converter Type="Microsoft.Practices.RecipeFramework.Library.Converters.NamespaceStringConverter, Microsoft.Practices.RecipeFramework.Library"/>
      <ValueProvider Type="Evaluator" Expression="$(CurrentProject.Properties.Item('DefaultNamespace').Value)" />
    </Argument>
  </Arguments>
  <GatheringServiceData>
    <Wizard xmlns="http://schemas.microsoft.com/pag/gax-wizards" SchemaVersion="1.0">
      <Pages>
        <Page>
          <Title>Configuration Node</Title>
          <Fields>
            <Field ValueName="NodeName" Label="Node Name" InvalidValueMessage="Must be a valid .NET identifier (e.g. it shouldn't contain spaces or special characters)." />
          </Fields>
        </Page>
      </Pages>
    </Wizard>
  </GatheringServiceData>
  <Actions>
    <Action Name="GenerateClass" Type="Microsoft.Practices.RecipeFramework.VisualStudio.Library.Templates.TextTemplateAction, Microsoft.Practices.RecipeFramework.VisualStudio.Library"
				Template="Text\ConfigurationNode.cs.t4" >
      <Input Name="TargetNamespace" RecipeArgument="TargetNamespace" />
      <Input Name="NodeName" RecipeArgument="NodeName" />
      <Output Name="Content" />
    </Action>
    <Action Name="AddClass" Type="Microsoft.Practices.RecipeFramework.Library.Actions.AddItemFromStringAction, Microsoft.Practices.RecipeFramework.Library"
				Open="true">
      <Input Name="Content" ActionOutput="GenerateClass.Content" />
      <Input Name="TargetFileName" RecipeArgument="TargetFile" />
      <Input Name="Project" RecipeArgument="CurrentProject" />
    </Action>
  </Actions>
</Recipe>

The recipe definition starts off with the regular caption and description elements. The HostData section defines where the recipe should be shown in Visual Studio; in this case we add it only to the project's "Add" context menu (where the "Add new item" and "Add existing item" menus also live) and supply it with an appropriate icon from the C# Project dll again. Then, there's a regular argument that the developer can fill in through the wizard, but the more interesting part is the definition of the other arguments: we're using a new type of value provider that returns the currently selected project in Visual Studio (so we can add the new class to it). There are also some more uses for the ExpressionEvaluatorValueProvider: a simple one to define the filename based on the class name, and a pretty complex one that finds the default namespace from the current project by drilling down into its properties (the nested expression is evaluated at runtime and executed through reflection). Finally, the actions section defines two distinct stages: a first one to generate the class content by executing the TextTemplateAction, which transforms a T4 text template using a quite advanced text processing engine. Afterwards, the generated class content is added to the currently selected project by using the AddItemFromStringAction.

Finally, the template itself looks like this:

<#@ template language="C#" #>
<#@ assembly name="System.dll" #>
<#@ property processor="PropertyProcessor" name="TargetNamespace" #>
<#@ property processor="PropertyProcessor" name="NodeName" #>
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Text;

using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Configuration.Design;

namespace <#= this.TargetNamespace #>
{
    internal sealed class <#= this.NodeName #> : ConfigurationNode
    {
    }
}

This templating syntax looks quite a lot like ASP.NET, so creating and understanding these templates should be relatively straight-forward. We first declare some properties that are inputs from the recipe declaration above. These will actually become strongly-typed properties on the page template object, so we can refer to them later on by using this.NodeName, for example. At this point, the generated class is extremely simple, but you can actually perform very powerful code generation with this T4 templating engine by inlining arbitrary C# or VB.NET code within these ASP.NET-like tags that will drive the code generation process.

Notice that you also need to register the assemblies you require for the code generation process. In this case, we just register the System.dll assembly. If, for example, we needed to use the Visual Studio automation dll (EnvDTE.dll) in the template, we would need to add the following declarations:

<#@ assembly name="C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PublicAssemblies\EnvDTE.dll" #>
<#@ import namespace="EnvDTE" #>

Failing to do so will give you the following nasty error: "CS0246: Compiling transformation: The type or namespace name 'EnvDTE' could not be found (are you missing a using directive or an assembly reference?)"

With this in place, it's now possible to add a new configuration node through the project's context menu:

Where Are We

At this point, we know how to add default classes to a generated project that have a customized name. Furthermore, we've touched the surface on the very powerful T4 text templating engine that's part of the Guidance Automation framework. Next time, we'll define a custom action that generates a strong name key and configure the projects to automatically be signed with it. We'll also automatically change the solution's file name to something more meaningful.

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

Monday, July 03, 2006 8:39:13 PM (Romance Standard Time, UTC+01:00) #    Comments [4]  | 

 

All content © 2010, Jelle Druyts
On this page
Visug Session: Guidance Automation Toolkit
Type Name Suffixes
GAS06: Generating Classes

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: 350
This Year: 4
This Month: 2
This Week: 2
Comments: 526
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