Export (0) Print
Expand All

Guidance Automation Toolkit and Domain-Specific Language Tools for Visual Studio 2005: Integration Scenarios

 

Mauro Regio
Microsoft Corporation

Víctor García Aprea
Clarius Consulting SA

November 2006

Applies to:
   Microsoft Visual Studio 2005

Summary: The Microsoft Guidance Automation Package (GAT) and Domain-Specific Language Tools for Visual Studio 2005 (DSL) are two key technologies in Microsoft's software-factory strategy that allow architects and developers to author and package guidance and domain-specific languages.

GAT and DSL offer considerable value when used separately. However, when used together, they complement each other and, more importantly, can make up for certain limitations that each technology might have in its current state.

This paper presents a few GAT/DSL integration scenarios and discusses the benefits of adopting them, how to combine the two technologies, and whether you need to develop significant code development to do that. (17 printed pages)

Contents

Introduction
Providing DSL Users with Context/Guidance
Using GAT to Extend Consumption of DSL Artifacts
DSL Cross-Language Integration
Using DSL to Gather Values for GAT Recipes
Conclusion
Appendix A: Configuring Concurrent Development of GAX and DSL Packages
Additional Information: References

Introduction

A thorough presentation of the GAT and DSL toolkits is outside the scope of this paper. What follows is a short summary of GAT/DSL's most important concepts, together with a few key references to the toolkits' downloads, documentation, and communities available online.

GAT

The Guidance Automation Toolkit is an extension to Microsoft Visual Studio 2005 that allows architects to author rich, integrated user experiences for reusable assets, including frameworks, components, and patterns.

The resulting Guidance Packages comprise templates, wizards, and recipes to help developers build solutions in a way that is consistent with the architecture guidance.

The Guidance Automation Extensions for Visual Studio 2005 are run-time components that you must install to use the Guidance Automation Toolkit itself, as well as to use any guidance packages built using the Guidance Automation Toolkit.

The toolkit is designed to simplify integrating reusable code into applications, allowing architects to automate development activities that developers would usually have to perform manually—often, by following a series of instructions. By using the toolkit, architects can also ensure that repetitive and often error-prone activities are performed in a consistent manner—streamlining and accelerating the development process.

The toolkit can be used with assets developed in-house or by third parties, such as the assets created by the Microsoft Patterns & Practices team. These assets can be exposed to developers within Visual Studio and, in some cases, configured by using configuration files, templates, and wizards.

Additional information on GAT is available on the GAT forum at MSDN, as well as on the GAT Community Web site.

Domain-Specific Language Tools for Visual Studio 2005

Domain-Specific Languages (DSLs) typically are small, highly focused languages used to model and solve some clearly identifiable problems with which an analyst, architect, developer, tester, or system administrator must wrestle. Developers are already familiar with examples of DSLs: SQL for data manipulation, XSD for XML document-structure definition, and so on.

Of course, using a DSL is much easier than specifying a new one, and developing the language editors (possibly visual ones), and compilers.

Microsoft recently announced the Domain-Specific Language Tools for Visual Studio 2005, which enable architects and developers to build DSLs by adopting the same technology used to build the modeling tools that shipped with Visual Studio Team Edition for Software Architects. This technology, which can be thought of as "tools to build tools," simplifies the task of defining DSLs, and reduces the effort and skills required to build graphical editors and compilers for them.

Using DSL Tools, designers can specify the domain model (semantics) for a new language; create a custom graphical editor that uses a domain-specific diagram notation (syntax); and construct custom text templates that use models created using the graphical editor, to generate source code and other files. Additionally, the DSL toolkit provides a validation framework that makes it easy to apply constraints to the language, and supports the creation of a standard installer package that allows the use of the new language within Visual Studio 2005.

Additional information on the DSL toolkit is available on the DSL forum at MSDN.

Providing DSL Users with Context/Guidance

Every domain-specific language is designed and implemented based on the requirements of the domain it is intended to model. However, when that language is used as part of the development process of a given solution, the solution's context might affect the way that language is used and how its artifacts are consumed.

For starters, DSL users will need to know whether there are only specific places in the solution structure where DSL instances should be created/stored. Also, it's the task of the guidance provided with the solution template to support and enforce the well-formed-ness rules of these "solutions." Again, it must be noted that this knowledge might not be available to the DSL author.

Imagine that a business analyst is using a Business Process DSL while collecting requirements for a solution to be developed within Visual Studio. That analyst might not have the necessary information and skills to determine where to put the Business Process model files inside the solution, or how to process them. In fact, that knowledge much more likely belongs to the solution architects and developers.

Additionally, for each artifact (model file and derivates) that can be created using a certain DSL, developers will need to know what the artifact life cycle is, in the context of a solution-development process in which that DSL is used. In fact, the DSL toolkit supports a very basic life cycle—that is, create, save (possibly delete), and apply report templates—for DSL model files. Also, the designer of a specific solution might want to change and extend that life cycle significantly.

For example, certain preconditions—verifiable at the solution/project/item level in the Visual Studio solution—might have to be validated before a DSL instance of a certain type can be created. Or, as we will see in more detail later, the use of one language might be affected by and/or dependent on other DSLs used in the same solution. Similar problems must be solved for all the artifacts in a solution. For example, solutions developers must address the same problem for source-code files or even binaries. However, usage scenarios for programming languages are much more predictable (because they're better known) than a generic DSL.

Likewise, DSLs virtually span through many levels of abstraction, and their scopes can be very different from one another. That makes providing the necessary guidance to enable the DSL artifact life cycle very important. As part of that guidance, DSL users will also need to be educated on which procedures (or commands) can be applied to language instances, and which artifacts will be created—if any—when these commands are applied. For example, a solution designer might have created various transformations that—depending on the solution's state—can be applied to instances of a certain DSL. So, commands to launch these transformations might appear within a context sensitive menu when the developer selects certain DSL model files, model elements, or even a DSL design surface.

As we will show in the following sections, GAT can be used very effectively to address the problems just mentioned, and provide DSL users with the necessary guidance about how to use a certain language and how to embed into a solution the necessary support for the specific life cycle on DLS model files, model elements, and so on.

Integrating DSL Within a Specific GAT Solution

Firstly, GAT can be used to provide the right solution structure, and to guide—or even force—the DSL user to create instances of a certain DSL only where it makes sense.

To do that, one can use a Visual Studio template file (.vstemplate), specifying the structure for the entire solution. Usually, that includes enumerating solution folders that are used to partition the solution (very likely based on the different viewpoints), with several projects in each folder. An example of a solution template might resemble Figure 1.

Aa905334.gatdslintscn01(en-us,MSDN.10).gif

Figure 1. Example of a solution template

Let's say that one of the projects in your solution has the sole purpose of containing certain DSL model files. This is where GAX's extension to Visual Studio's processing of templates comes in handy, as you can easily link a given Visual Studio template to a folder of your project. In the template of the project that we want to contain the model files, we can link folders in it to corresponding item templates for DSL model files, as shown in the boldface elements in the code snippet shown here.

<VSTemplate Version="2.0.0" Type="Project" 
xmlns="http://schemas.microsoft.com/developer/vstemplate/2005">
    <TemplateData>
   <Name>CollaborationSpecification</Name>
    </TemplateData>
    <TemplateContent>
   <Project File="CollaborationSpecification.csproj" ReplaceParameters="true">
             <Folder Name="Messages" TargetFolderName="Messages"></Folder>
             <Folder Name="Entities" TargetFolderName="Entities"></Folder>
             <Folder Name="UseCases" TargetFolderName="UseCases"></Folder>
       </Project>
    </TemplateContent>
    <WizardExtension>
   <Assembly>Microsoft.Practices.RecipeFramework.VisualStudio, 
Version=1.0.60429.0, Culture=neutral, 
PublicKeyToken=b03f5f7f11d50a3a</Assembly>
   <FullClassName>Microsoft.Practices.RecipeFramework.VisualStudio.
Templates.UnfoldTemplate</FullClassName>
    </WizardExtension>
    <WizardData>
   <Template xmlns="http://schemas.microsoft.com/pag/gax-template"
      SchemaVersion="1.0">
       <References>
      <TemplateReference Name="Items\BCSFMessages\BCSFMessages_CS.vstemplate" 
Target="\Messages" />
      <TemplateReference Name="Items\BCSFEntities\BCSFEntities_CS.vstemplate" 
Target="\Entities" />
      <TemplateReference Name="Items\BCSFUseCases\BCSFUseCases_CS.vstemplate" 
Target="\UseCases" />
       </References>
   </Template>
    </WizardData>
</VSTemplate>

Note how, in the above template, we linked three different project-item templates against three different folders in the project. Now, when users right-click on any of these folders, they will see the corresponding project-item template under the Add menu, as shown in Figure 2.

Aa905334.gatdslintscn02(en-us,MSDN.10).gif

Figure 2. Example of a GAT-enabled context menu

Integrating DSL Artifact Life Cycle Within GAT Recipes

After adding GAT to the solution template, the procedures and commands that support various phases of a given DSL life cycle can be added to GAT packages.

For instance, you might want to add a new Validate Model command shown in the context menu for the design surface that executes a recipe containing your custom logic to validate multiple models.

The Guidance Automation Runtime (also known as the "GAX") abstracts you from the low-level details of adding commands to Visual Studio (which usually includes such tasks as creating CTC files, resource files, and so forth) by providing a very simple declarative support that takes the following form.

<CommandBar Guid="ebfdec15-5ae4-444e-bb53-4eb001977fdf" ID="256"/>

Here, you specify a Guid for a command set and ID of the menu to which you want to add your command. This information can be found in the code generated for the DSL package designer. For the DSL Tools distributed with the Visual Studio SDK September 2006 release, you will find the Guid command set dynamically generated at the bottom of the Constants.cs file.

//
// Constants not generated from values in DesignerDefinition.dsl 
are defined below
//
namespace Company.Language1
{
   internal static partial class Constants
   {
      // Menu identifier
      public const string Language1CommandSetId = 
"ebfdec15-5ae4-444e-bb53-4eb001977fdf";
   }
}

The ID for the menu is fixed to the value <65536> for the DSL designer surface.

Aa905334.gatdslintscn03(en-us,MSDN.10).gif

Figure 3. Launching a GAT recipe from a DSL language surface and to the value <65537> for the DSL language explorer window

Aa905334.gatdslintscn04(en-us,MSDN.10).gif

Figure 4. Launching a GAT recipe from a DSL language explorer

With this information in hand, a developer can properly declare the CommandBar element when authoring the HostData section of a recipe, as shown in the following code fragment.

<Recipe Name="TestRecipe" Bound="false" 
xmlns="http://schemas.microsoft.com/pag/gax-core" 
xmlns:xi="http://www.w3.org/2001/XInclude">
  <xi:include href="../Common/CommonTypeAliases.xml" />
  <Caption>Validate Collaboration</Caption>
  <HostData>
    <Icon ID="9956" />
    <CommandBar Guid=" ebfdec15-5ae4-444e-bb53-4eb001977fdf" ID="65536" />
  </HostData>   
  .
  .
  .
</Recipe>

This allows the GAX recipe to be run from the context menu of the DSL designer-surface.

Providing Guidance in Context

GAT can be used also to provide additional documentation to DSL users—on top of what was originally provided by the DSL's authors. That documentation should explain the way in which a certain DSL shall be used inside the context of that solution. Typically, the two most important patterns are:

  1. Showing (just after solution unfolds) an introductory page providing the user with the general package overview.
  2. Showing (before and/or after each recipe execution) a recipe-specific page that includes what should be/was done, and the next steps.

Within GAT, displaying documentation assets (HTML pages) is as easy as adding the OpenDocumentationPageAction action to any of the package's recipes.

For example, to provide some general overview after a user has just started to use a guidance package, such as supporting the aforementioned pattern 1 (that is, at solution-unfolding time), you would add the mentioned action to the recipe that creates the initial solution structure.

<Recipe Name="CreateSolution">
      ...
      <Caption>Creates a new EdgeSystem solution</Caption>
      ...
   <Actions>
      ...
<Action Name="ShowSolutionOverview" 
   Type="Microsoft.Practices.BCSF.Actions. 
OpenDocumentationPageAction,          Microsoft.Practices.BCSF"
   TemplateFile="Templates\Documentation\SolutionOverview.htm"/>
      ...
   </Actions>
   ...
</Recipe>

Or, if you want to provide some details on what changes a given recipe just performed, along with suggested next steps, you could insert a piece of code, like the one shown here.

<Recipe Name="GenerateImplementation">
...
   <Caption>Performs code generation</Caption>
   ...
   <Actions>
   ...
      <Action Name="CodeGenerationOverview" 
Type="Microsoft.Practices.BCSF.Actions.OpenDocumentationPageAction, 
Microsoft.Practices.BCSF"
                  TemplateFile="Templates\Documentation\CodeGenerationOverview.htm"/>
...
   </Actions>
   ...
</Recipe>

Using GAT to Extend Consumption of DSL Artifacts

By using the DSL Toolkit, a DSL designer can define transformations on DSL model files to create various artifacts.

An example of a transformation would be the compilation of instances of a custom application architecture language into programming modules containing classes implementing the applications or patterns defined by that architecture.

To define a transformation, a DSL author uses the debugging environment that is natively provided by the DSL toolkit when a new DSL language solution is created, which contains startup transformation templates.

To make transformations available to language users, these templates must be copied inside the solution or project where the model files of that language are going to be created.

Solution developers can select a transformation template and apply it to the language instance, just by asking the development environment to apply the template.

When launched, a transformation might load one or more models of one or various DSLs to get its input data. However, the artifact generated must be directed to a single file. This last fact, depending on the particular development scenarios, might be perceived as a significant limitation on use and adoption of DSLs. In these cases, GAT can be used to overcome this feature constraint.

In fact, GAT recipes make it easier for the package developer to invoke transformation templates from within code. Also, the recipe writer can determine more freely where to direct the output of the transformation, if one or more artifacts are generated as results of the application of that transformation. For example, a recipe can be written that repeatedly applies a text template to the same model, perhaps using different model elements each time and generating different artifacts—one per model element.

Interestingly enough, the transformations developed by the DSL author within the DSL toolkit debugging environment can be brought—almost with no changes—into the GAT package containing the solution guidance.

In this way, a solution developer can also apply different transformations to the same model element, or have a single transformation that applies to various model elements even of different languages.

Typically, to have Text-to-Text Transformation (T4) templates accept both GAX recipe arguments and T4 model files at the same time, you will need custom code. Alternatively, you can acquire third-party integration libraries. (Click here for an example of such a library.) However, no matter whether you choose the build or the buy approach, that code gives developers a powerful combination, in terms of the code that can be written in the template.

What follows is an example of how one can include an action in a recipe to transform a whole model file against a given T4 template.

  <Action
      Name="GenerateBusinessEntity"
      Type="Clarius.GuidanceAutomation.Library.TextTemplating.
VsTextTemplateAction, Clarius.GuidanceAutomation.Library"
      Template="Text\BusinessEntity.t4">
         <Input Name="ModelFile"
            RecipeArgument="ModelFile"/>
  <Output Name="Content"/>
  </Action>

Note how the template BusinessEntity.t4 file (originally developed in the context of a DSL project) is now accessed from the Text folder that is part of every guidance package default solution structure.

DSL Cross-Language Integration

As mentioned in the introduction, DSLs generally are relatively small, highly focused languages used to model and solve certain clearly identifiable problems that various developer roles might have to tackle as part of the software-development life cycle.

The limited scope of a DSL should not be perceived as a negative factor, however. Actually, the smaller the scope, the greater the language's potential effectiveness for solving problems belonging to that scope, and for supporting the specific needs of roles that are the primary stakeholders for that scope.

Nevertheless, in many cases, architects and developers must be able to navigate through various viewpoints—that is, through different areas of concern and across various abstraction levels. Other times, they must be able to transform artifacts created while designing and developing a certain viewpoint, into other artifacts belonging to a different viewpoint.

Navigating across viewpoints and transforming related artifacts imposes new requirements on DSL design, especially in terms of integration among them. Perhaps the most important requirement is the ability to refer to model elements of different languages—more specifically, to refer from one DSL's model elements or properties to another's.

For example, let's say we are modeling business-message types and business entities while developing a service-oriented architecture (SOA)–based application that uses two different DSLs, namely a Business-Message Type DSL and a Business Entities DSL. To define the payload for any given message type while using the Business-Message Types DSL, the message designer might need to refer to instances of business entities that were originally specified using the Business Entities DSL.

The current release of the DSL toolkit does not support any integration across different DSLs. More specifically, it's not natively possible to refer from the model elements or properties in one language to those modeled and instantiated in another. While GAT does not provide any native mechanism to support DSL cross-language integration, once the integration service is available, GAT recipe patterns can be used to package the necessary integration capabilities in a way that makes it easier for the solution developer to use various DSLs.

However, that level of integration can be programmatically obtained. In fact, a DSL integration service can be developed to allow the DSL author/users to browse through model elements defined in different languages and to establish the necessary cross-language references.

Additionally, one needs to develop custom property editors that will extend the DSL user interface to activate the integration service, and allow DSL users actually to instantiate cross-language references.

To support referencing of model elements across different DSLs, you can add the Visual Studio DSL Integration Service (DIS) to Visual Studio using the Microsoft.VisualStudio.Modeling.Integration package, as outlined in the next section.

Making Your DSL Available for Browsing from Other DSLs

By default, the DIS cannot browse DSLs registered on the machine, unless they have been marked as consumable by this service. Therefore, if you want to refer model elements of a DSL, you must expose that DSL to the DIS. To do so, add the RegisterAsExportable custom metadata attribute to the Package class in your DslPackage project. As you can see from one of its constructors, this attribute takes four types (there is another constructor that you can use to specify the type name as a string).

        public RegisterAsExportableAttribute(Type domainModelType, Type 
domainModelMonikerResolverType, Type modelElementRootType, Type 
domainModelSerializerBehaviorType);

To add that code to the DSL solution, browse to the Package.tt template in the DSLPackage Solution (under the GeneratedCode folder of the DslPackage project), and modify the template code in the Package class, as shown here, where boldface text indicates statements that you must add.

<#
   string directiveName = "BCSFEntities";
   string domainModelRoot = "EntityModel";
#>
[..]

[Microsoft.VisualStudio.Modeling.Integration.RegisterAsExportable(
        typeof(<#= directiveName #>DomainModel),
        typeof(<#= directiveName #>SerializationBehaviorMonikerResolver),
        typeof(<#=domainModelRoot#>),
        typeof(<#= directiveName #>SerializationBehavior))]
    internal sealed partial class <#= dslName #>Package : 
<#= dslName #>PackageBase

If you transform Package.tt, you can look at the code for the Package.cs class and check your code against the Entities DSL provided with this service as an example of how this attribute was applied.

   [Microsoft.VisualStudio.Modeling.Integration.RegisterAsExportable(
        typeof(BCSFEntitiesDomainModel),
        typeof(BCSFEntitiesSerializationBehaviorMonikerResolver),
        typeof(EntityModel),
        typeof(BCSFEntitiesSerializationBehavior))]
    internal sealed partial class BCSFEntitiesPackage : 
BCSFEntitiesPackageBase

Subsequently, make sure you add a reference to the DIS in your DslPackage project. Otherwise, you will get a missing-reference error at build time.

As a last step, you must make sure that every Domain class that you will want to make available for referencing has one Domain property with its IsElementName property set to true, as this will be considered by the service to be the identifier of the target class.

Browsing Other DSLs from Your DSL

After having enabled a DSL for browsing, most probably you would want to be able to refer easily to its model elements from another DSL. You can achieve this without any coding, just by using three custom metadata attributes—namely:

  1. System.ComponentModel.Editor
  2. System.ComponentModel.TypeConverter
  3. Microsoft.VisualStudio.Modeling.Integration.TargetClass

For example, the provided Message DSL has a MessagePart class that contains a value property of type String, named Type, that has been designed to specify the type of a message part.

Now, to allow users to specify model elements designed in another DSL—the Business Entities DSL—as the value for this Type property, you will need to properly decorate the property with the three custom metadata attributes. Let's take a look at each one.

Step 1: The Editor Attribute

This specifies that you want to use the service's ExportedClassEditor editor for editing this property.

[System.ComponentModel.Editor("Microsoft.VisualStudio.Modeling.
Integration.Editors.ExportedClassEditor, 
Microsoft.VisualStudio.Modeling.Integration, Version=1.0.0.0, 
Culture=neutral, PublicKeyToken=b07ca182ff1d78fe",typeof(System.Drawing.Design.
UITypeEditor))]

Setting this will display the following friendly UI when editing your property.

Aa905334.gatdslintscn05(en-us,MSDN.10).gif

Figure 5. DSL Model Element Selector browser window

Step 2: The TypeConverter Attribute

Setting this will mark your property as a user of the ExportedInstanceConverter.

[System.ComponentModel.TypeConverter(typeof(Microsoft.VisualStudio.
Modeling.Integration.ExportedInstanceConverter))]

In this way, your property will accept string values in a format that points to model elements in a given model file, while validating that they exist. Figure 6 shows what a value for this property looks while being edited in the Properties window.

Aa905334.gatdslintscn06(en-us,MSDN.10).gif

Figure 6. Reference to a Model Element in a different file and/or language

Step 3: The TargetClass Attribute

Setting this will determine what kind of model elements you want the user to browse. This is specified in the attribute's constructor, which takes a string representing the namespace for the target class.

public TargetClassAttribute(string targetClassNamespace)

The provided namespace should follow the format "YourDSLNamespace\ClassName".

Now, see what this attribute looks like when applied to the aforementioned Type property for the MessagePart class of the Business Message DSL from which you only want to browse BusinessEntity classes and no others.

[Microsoft.VisualStudio.Modeling.Integration.
TargetClass(@"Microsoft.BCSF.Entities\BusinessEntity")]

Finally, make sure you add a reference to the DIS in your DSL project. Otherwise, you will get a missing-reference error at build time.

Using the Service

After playing with your DSLs and creating references between them, most probably you will want to be able to walk these references and get to the actual model elements to which they point, to do any useful stuff like generating code.

Getting a Hold of the Service

The code needed to perform this is very simple: You must ask Visual Studio for the Visual Studio DIS, and then ask this service for a domain model browser object.

/ get VS DSL integration service
IDslIntegrationService service = 
(IDslIntegrationService)GetService(typeof(IDslIntegrationService));

// get a domain model browser for a given dsl namespace
IDomainModelBrowser domainModelBrowser = 
service.GetDomainModelBrowser(targetDslNamespace);

Once you have a reference to the domain model browser, you can execute the different operations supported by it that are contained on the contract specified by the IDomainModelBrowser interface.

    public interface IDomainModelBrowser
    {
        List<ExportedClass> FindAllClasses();
        List<ExportedInstance> FindAllInstances(ExportedClass exportedClass);
        ExportedClass GetExportedClassByNamespace(string classNamespace);
        ExportedInstance 
GetExportedInstanceByNamespace(string instanceNamespace);
    }

Keep in mind that all of the above operations will apply only against the DSL specified at the time of retrieving the domain model browser.

Getting a List of Available Classes

The FindAllClasses method takes no arguments and returns a list of ExportedClass objects. An ExportedClass is a very simple object that represents a class defined in a DSL. This is what you use when asking the model browser for instances of a given class.

Getting a List of Available Instances

The FindAllInstances method returns a list of instances of the class specified in its only argument. The returned objects are of type ExportedInstance and represent an existing instance of a given class living into a model file. All model files in the solution are looked up by this method.

Working with Instances

The more interesting bit about ExportedInstance is that it wraps a ModelElement instance internally that you can access by using a string indexer for querying properties on this latter type, so you could use code like the following to access any property—such as the Description property for Business Entities—defined on the model element.

// modelElement is an instance of ExportedInstance 
string melDescription = modelElement["Description"];

Getting a Specific Class or Instance

If you know the path for a class or instance, you can access it by calling either the GetExportedClassByNamespace or GetExportedInstanceBynames method, respectively. The signatures for these methods are pretty simple.

ExportedClass GetExportedClassByNamespace (string classNamespace)
ExportedInstance GetExportedInstanceByNamespace (string instanceNamespace)

As you can see, you provide a path to a specific class or instance, and you get back a corresponding ExportedClass or ExportedInstance that you can use.

Extending the Design-Time Experience

Based on your specific requirements, the built-in editors and type converters provided by the service might not be enough for you. If this is the case, note that you can write your own editors and consume the service APIs (that we just saw in the previous section) from them. For example, you could build an editor supporting the browsing of different model element types simultaneously, or offering other more complex browsing UIs.

Using DSL to Gather Values for GAT Recipes

GAT recipes automate repetitive and often error-prone activities that developers would usually perform manually, often by following a series of instructions.

Typically, the invocation of a recipe involves some level of interaction with the user—for example, to collect data necessary to drive the recipe execution, or to provide values that will be used for the recipe's arguments. GAT designers can use wizards as an effective value-gathering strategy, to gather values from recipe arguments. In fact, any GAT recipe can have a wizard associated with it. Also, a wizard can be used to walk the developer through one or more steps, which are displayed as pages of the wizard, collecting information at each step, and subsequently providing values for the recipe execution.

Design of GAT wizards is basically done in a declarative way, writing snippets of XML code—conformant with the GAX Wizard XSD schema, which, while not impossible, is quite complex to do, manage, and document. (Assuming that Visual Studio 2005 has been installed at the default location on the local hard drive, that XSD schema can be found at C:\Program Files\Microsoft Visual Studio 8\Xml\Schemas\WizardFrameworkConfig.xml.)

However, developing a DSL to model GAT wizard design and implementation activities is relatively easy. Also, that DSL can be provided to GAT designers as an additional tool to create and maintain GAT wizards. We designed such a simple DSL based on the GAX Wizard schema to allow easier authoring of GAX wizards while abstracting the Recipe author from having to know the schema and writing XML by hand.

This DSL includes WizardPage and WizardPageField classes that are used to model a wizard page and its fields, respectively.

Aa905334.gatdslintscn07(en-us,MSDN.10).gif

Figure 7. Domain model for the GAX Wizard schema DSL

Actually, WizardPage and WizardPageField classes are embedded into the language root class WizardModel (not shown in the picture). The language root is accessible through the designer surface, and contains a single property named Recipe that is used to specify the recipe in the GAX manifest to which the wizard should be added. This property has been annotated with the RecipeNameConverter type converter, which offers a list of all available recipes found in the GAX manifest of the currently opened solution. (These converters and other assets—such as UI Editors, Actions, and so on—for development of software factories can be obtained from the Clarius Software Factories download page.)

We also wrote a Guidance Package that adds a new item to Visual Studio's Add Item menu for quick access to the creation of a GAX Wizard model file. Technically, this is done by creating an unbound reference added by an action defined in the binding recipe.

      <Actions>
        <Action Type="RefCreator" Name="GPWizardTemplateRef" 
AssetName="Items\gaxWizard_CS.vstemplate" 
ReferenceType="GaxDsl.References.AnyElementReference, GaxDslPackage" />
      </Actions>

The resulting UI experience for the Recipe author resembles Figure 8.

Aa905334.gatdslintscn08(en-us,MSDN.10).gif

Figure 8. Invoking the GAX Wizard

The same Guidance Package also contains a recipe, GeneratesWizardXmlSnippet, that is used to generate the Recipe's wizard XML code snippet from the DSL instance.

That recipe takes a Store as its input and generates a corresponding snippet of XML that represents it, and finally inserts it into the right place in the package's manifest file. This is performed by two actions: The first one takes the Store (containing the Wizard DSL model) and applies to it a T4 template (Text\gaxWizardXmlSnippet.t4) to output a string containing the corresponding XML. The action declaration looks like the following.

<Action Name="GenerateXmlSnippet" 
Type="Microsoft.Practices.RecipeFramework.VisualStudio.Library.
Templates.TextTemplateAction, 
Microsoft.Practices.RecipeFramework.VisualStudio.Library"
    Template="Text\gaxWizardXmlSnippet.t4">
  <Input Name="Store" RecipeArgument="Store"/>
  <Output Name="Content" />
</Action>

The second action, AddSnippet, uses the XML snippet just generated by the previous action, and adds it to the corresponding recipe in the package manifest file.

<Action Name="AddSnippet" Type="GaxDsl.Actions.AddWizardXmlSnippet, 
GaxDslPackage">
  <Input Name="Store" RecipeArgument="Store"/>
  <Input Name="WizardXmlSnippet" ActionOutput="GenerateXmlSnippet.Content"/>
</Action>

The aforementioned recipe is programmatically executed from the Wizard DSL (and not a GAX package, as is usually the case) by extending the validation logic and adding proper code to do so in a new Validation.cs file.

    [ValidationState(ValidationState.Enabled)]
    public partial class WizardModel
    {
        [ValidationMethod(ValidationCategories.Save)]
        private void PersistWizard(ValidationContext context)
        {
            // Here you should perform any validation step.
            try
            {
                RecipeExecutor recipeExecutor = new 
RecipeExecutor(this.Store as IServiceProvider);
                Hashtable arguments = new Hashtable();
                arguments.Add("Store", this.Store);

                recipeExecutor.Execute("GaxDsl", 
"GeneratesWizardXmlSnippet", arguments);
            }
            catch (Exception exception)
            {
                context.LogError(exception.Message, exception.Source, this);
                throw;
            }

The custom metadata attributes added above will cause the validation method to run every time that the model file is saved. This way, the GAX manifest keeps updating automatically with every save operation of the model file.

Note how this is another generic pattern of integration between DSL and GAT that complements the other examples provided in the third section.

Conclusion

The Guidance Automation Toolkit helps abstract and automate the user experience, facilitating reuse, and eliminating errors. By including guidance directly in context through templates, wizards, and recipes—versus in remote documentation—not only is the user's job simplified, but the user can maintain a high degree of process control and consistency.

The DSL Tools enable architects and developers to build modeling tools within Visual Studio Team Edition for Software Architects, dramatically reducing the efforts and skills required to build graphical editors and compilers for them. GAT and DSL are very effective, and when combined they complement and extend each other in significant ways. We have shown a few examples of integration of these two technologies.

While the two toolkits are currently shipped outside Visual Studio, they are simple enough to install and use, well-supported by both Microsoft and other vendors, and worth the time and investment for teams that use Visual Studio, develop DSLs, and build software factories.

Appendix A: Configuring Concurrent Development of GAX and DSL Packages

Unfortunately, DSL Tools and GAX do not yet offer a seamless out-of-the-box experience when it comes to developing both types of assets in Visual Studio concurrently.

While packages for newly created DSLs get registered by default in the experimental Visual Studio registry hive, their GAX counterparts do so at the regular Visual Studio registry hive, thus making the debugging of a solution containing both assets impossible.

The reason that GAX uses this default is that it does not support running Guidance Packages under the experimental or any other hive. Also, the fact that GAX does not really create a stand-alone Visual Studio package for each new Guidance Package (like the DSL Tools does for each new DSL) makes the obvious option of switching a given Guidance Package to use the experimental hive a nontrivial task.

If you can afford to debug under the regular Visual Studio registry hive (which basically means if you can afford to uninstall/reinstall Visual Studio or reregister DSL designer, in case something goes very wrong), modifying a DSL package to run under this hive is as simple as modifying two settings in the generated DslPackage.csproj project file.

The first setting is the TargetRegistryRoot property, which controls the registry hive to be used by the RegPkg MS Build task in charge of registering Visual Studio packages. This setting must be changed by direct editing of the project file (there is no UI in Visual Studio to access it)—from:

<TargetRegistryRoot>Software\Microsoft\VisualStudio
\8.0Exp</TargetRegistryRoot>

To:

<TargetRegistryRoot>Software\Microsoft\VisualStudio
\8.0</TargetRegistryRoot>

The second setting is StartArguments, which is used to specify the registry hive when launching the instance of Visual Studio used for debugging. This setting can be edited by right-clicking on the Project node and selecting Properties, then clicking Debug, Start, Options, and selecting Command line arguments. Change the current value of:

/RootSuffix Exp /DesignTimeRun "..\..\..\Debugging\Debugging.sln"

To:

/DesignTimeRun "..\..\..\Debugging\Debugging.sln"

As noted previously, doing this the other way around—that is, making GAX packages register in the experimental hive—is a non-supported feature, and is something that requires special knowledge of GAX and Visual Studio registry entries for anyone trying to accomplish this manually.

Additional Information: References

Guidance Automation Package

Domain-Specific Language Toolkit

Software Factories

Clarius Consulting

 

About the authors

Mauro Regio is Program Manager for Software Factories in the Microsoft Visual Studio Team Architect at Microsoft Corporation.

Víctor García Aprea is a Developer Lead for Clarius Consulting SA, working on software factories.

Show:
© 2014 Microsoft