Importing Model Elements from an Arbitrary File Format

This walkthrough introduces you to a new recipe that imports information into a Service Contract Model.

Introduction

This walkthrough uses a rather simplistic XML document and describes the steps required to import this file into a Service Contract Model.
<service name="OrderService">
   <operation name="PlaceOrder"/>
   <operation name="TrackOrder"/>
   <operation name="ThirdOperation"/>
 </service>


When you are finished, you will be able to create your own import mechanism for a model and have gained insight in how Recipes can modify parts of a Model.
importedcontract.png
Figure 1: Importing information into the Services Model.

Prerequisites

In order to complete this walkthrough, you will need to:
  • Have the Service Factory source code installed on your computer.
  • Have a copy of the Service Factory source code registered and installed within Visual Studio’s Experimental Hive.
  • Be familiar with the basic usage scenarios for the Services Factory.

Procedures

This walkthrough requires you to:
1. Modify the Service Factory Guidance Package project.
  • Add a new Action to the Services Factory
  • Add a new Recipe to the Services Factory
2. Re-register the Service Factory Guidance Package
3. Test the import mechanism on an instance of the Services Model

To add an action to the Service Factory Guidance Package project
1. Open the Service Factory Guidance Package project, from the Services Factory source code solution.
2. To the Service Factory Guidance Package project, add a new folder called CustomRecipes and within this folder another called ImportXmlExample
The ImportXmlExample folder will contain your Recipe (ImportXml.xml) and Action (ImportXmlAction.cs). The Action will contain the logic to import the file into the model; the recipe will contain metadata that is used to add a context menu item inside Visual Studio and information about how the Action should be executed. The Recipe has a dependency on the Action, the walkthrough starts with adding the Action.
3. Add a new codefile called ‘ImportXmlAction.cs’ to the ImportXmlExample folder
In order to import a XML Document and uses this to modify the Model, you need 2 input arguments for this action:
  • XmlFileLocation. The location of the XML document on your file system.
  • ServiceContractStore. A ‘Store’ that allows us access to the Model and allows you to manipulate the Model.
To add the Action, you need to create a class that derives from ActionBase and declares these arguments as properties.
4. Add the following code to ImportXmlAction.cs file, created in step 3:
 using Microsoft.Practices.ServiceFactory.Actions;
 using Microsoft.VisualStudio.Modeling;
 using Microsoft.Practices.RecipeFramework;
 using Microsoft.Practices.ServiceFactory.ServiceContracts;
 using Microsoft.Practices.Modeling.Dsl.Integration.Helpers;
 using System;
 using System.Xml; 
 using System.Windows.Forms.Design;

 namespace Microsoft.Practices.ServiceFactory.CustomRecipes.ImportXml
 {
 public class ImportXmlAction: ActionBase
 {
 	private Store serviceContractStore;
 	private string xmlFileLocation;
 
 	[Input(Required = true)]
	public Store ServiceContractStore
 	{
 		get { return serviceContractStore; }
 		set { serviceContractStore = value; }
 	}
 
 	[Input(Required = true)]
 	public string XmlFileLocation
 	{
         get { return xmlFileLocation; }
         set { xmlFileLocation = value; }
 	}

 	public override void Execute()
 	{
         //do nothing	
 	}
 
 	public override void Undo()
       {
         //do nothing 	}
 }
 }


The ImportXmlAction class now contains a basic skeleton that is needed to collect the arguments we use and can be executed from within our recipe. The Execute method is left empty. At the end of this walkthrough the Execute method will do the actual work, once the Recipe is invoked. This walkthrough does not cover the Undo method.
Before implementing the Execute method, you should first add the Recipe to the project. The Recipe is contained in an XML file and describes how and when our action should be executed.
To add the Recipe to the Service Factory Guidance Package project
5. In the ImportXmlExample folder, you created the Action; you should add another file, called ImportXml.xml. In the Property window, make sure you set the Build Action to “Content” and Copy to Output Directory to “Copy if newer”.
6. Paste the following Xml in the ImportXml.xml file; this gives you a generic skeleton of a recipe.
Note: If you have chosen a different location for ImportXml.xml, make sure that <xi:include/>-element points to the CommonTypeAliases.xml file, contained in the Service Factory Guidance Package.

 <?xml version="1.0" encoding="utf-8" ?>
 <Recipe Name="ImportXml"
         xmlns="http://schemas.microsoft.com/pag/gax-core"
         xmlns:xi="http://www.w3.org/2001/XInclude"
         Bound="false">
   
   <!-- the following will import some common types used in the Services Factory -->
   <xi:include href="../../Recipes/Common/CommonTypeAliases.xml"
 				      xpointer="xmlns(gax=http://schemas.microsoft.com/pag/gax-core) xpointer(/gax:Types)" />
   <Caption>Import ServiceModel Elements from Xml</Caption>
   <Description>Import services from an arbitrary file format example.</Description>
   <HostData>
     <!-- The GUID/ID pair below attaches the recipe the ServiceContractModel's design surface -->
     <CommandBar Guid="0cd5c408-18b5-4905-8e14-a58404247220" ID="65536"/>
     
     <!-- Use the GUID/ID pair below to attach the recipe to the DataContractModel’s design surface-->
     <!-- CommandBar Guid="44157ffb-0ae0-44e4-858c-366754e3dcc9" ID="65536"/-->  
  </HostData>
   <Arguments>
     <!-- The ‘Arguments’ section contains the input parameters for this recipe when run-->
   </Arguments>
   <GatheringServiceData>
     <!-- The ‘GatheringServiceData’ section contains information about the UI and how to bind the values entered in the UI to the Arguments-->
   </GatheringServiceData>
   <Actions>
     <!-- The ‘Actions’ section contains a list of steps that are performed by this recipe --> 
   </Actions>
 </Recipe>


The Action you created in step 4 requires two Arguments. You should add these arguments inside the <Arguments/> section of the Recipe. The Recipe allows you to later reference these arguments and pass them to your Action when it is invoked.
For the ImportXml Action we need to pass the ServiceContractStore and XmlFileLocation. When the action is executed and added new elements to the model, you should call another Action, which will do the layout of the added models on the Diagram surface. The layout action also requires an argument, called Diagram.
To add the arguments to a recipe
7. Modify the <Arguments> Section of the Recipe to look like the following (or copy/paste the complete section into the Recipe)
   <Arguments>
     <!-- The ‘Arguments’ section contains the input parameters for this recipe when run-->
     <!-- The argument 'ServiceContractStore' will point at the give a reference to the ServiceContract Model we input the elements to -->
     <Argument Name="ServiceContractStore" Type="Microsoft.VisualStudio.Modeling.Store, Microsoft.VisualStudio.Modeling.Sdk, Version=8.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
       <ValueProvider Type="StoreProvider"/>
     </Argument>
     <!-- The argument 'XmlFileLocation' will get us the path to the xml file we want to import. -->
     <Argument Name="XmlFileLocation">
       <Converter Type="ValidLocationConverter" Extension=".xml"/>
     </Argument>
     <!-- The argument 'ServiceContractDiagram' will allow us to layout the shapes we added on the diagram after importing them. -->
     <Argument Name="ServiceContractDiagram" Type="Microsoft.VisualStudio.Modeling.Diagrams.Diagram, Microsoft.VisualStudio.Modeling.Sdk.Diagrams, Version=8.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
       <ValueProvider Type="DiagramProvider"/>
     </Argument>
   </Arguments>

Both the ServiceContractStore and ServiceContractDiagram arguments have a ValueProvider within their declaration. This ValueProvider will automatically populate the value of these argument, using a Store and Diagram based on contextual information.
The value of the XmlFileLocation cannot be determined based on contextual information. You should add some UI for a user to enter the path to the XML file they would like to import. The Converter element inside the XmlFileLocation’s Argument declaration will validate the input and ensure a file is entered with the extension .xml.

To add UI to a recipe
8. Modify the <GatheringServiceData> Section of the Recipe to look like the following (or copy/paste the complete section into the Recipe)
 <GatheringServiceData>
     <!-- The ‘GatheringServiceData’ section contains information about the UI and how to bind the values entered in the UI to the Arguments-->
     <!-- In our case the UI contains 1 page, with 1 input that allows to enter and browse for the Xml file we would like to import. -->
     <Wizard xmlns="http://schemas.microsoft.com/pag/gax-wizards" SchemaVersion="1.0">
       <Pages>
         <Page>
           <Title>Specify The file you would like to import</Title>
           <LinkTitle>Import from XML</LinkTitle>
           <Fields>
             <!-- The Field-element below will be shown on the wizard page and is bound to the XmlFileLocation argument in the 'Arguments' section. -->
             <!-- The field also contains an editor (that allows to browse local file system) and a tooltip. -->
             <Field ValueName="XmlFileLocation" Label="Xml File Location">
               <Tooltip>Enter the path to the Xml file you would like to import.</Tooltip>
               <Editor Type="Microsoft.Practices.RecipeFramework.Extensions.Editors.FileChooser, Microsoft.Practices.RecipeFramework.Extensions, Version=1.3.41007.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
 		Filter="XML files (*.xml) |*.xml"
 		Title="Select XML file to import."/>
             </Field>
           </Fields>
         </Page>
       </Pages>
     </Wizard>
   </GatheringServiceData>

The <GatheringServiceData> section now specifies that a Wizard should be shown. This wizard contains 1 page, which will show 1 field labeled “Xml File Location”. After a value is entered in this field, the input should be bound to the Argument you added in step 7, called* XmlFileLocation. Binding input to an Argument is done through the ValueName* attribute on the Field element.
To invoke an action from within the Recipe
9. Modify the <Actions> Section of the Recipe to look like the following (or copy/paste the complete section into the Recipe)
  <Actions>
     <!--The 'Actions' section contains a list of steps that are performed by this recipe.-->
     <!--the first action is one that we created, it will import the Model Elements from the Xml file.-->
     <Action Name="ImportXmlAction" Type="Microsoft.Practices.ServiceFactory.CustomRecipes.ImportXml.ImportXmlAction, Microsoft.Practices.ServiceFactory.GuidancePackage">
       <!-- The arguments, passed as input to this action refer to arguments described in the 'Arguments' section of this recipe.-->
       <Input Name="XmlFileLocation" RecipeArgument="XmlFileLocation"/>
       <Input Name="ServiceContractStore" RecipeArgument="ServiceContractStore"/>
     </Action>
     <!-- After importing the model elements, we want to layout the shapes -->
     <Action Name="ServiceContractsAutoLayout" Type="AutoLayoutShapeElementsAction">
       <Input Name="Diagram" RecipeArgument="ServiceContractDiagram"/>
     </Action>
   </Actions>


The Actions-section now contains 2 Actions. The first Action, called “ImportXmlAction” refers to the (empty) action we created in step 4. The second action, called “ServiceContractsAutoLayout”, refers to a predefined action called “AutoLayoutShapeElementsAction”.
Both Actions need Arguments to run, which are declared using an <Input/>-element. For both Arguments the Name attribute refers to a property-name on the Action’s class and the RecipeArgument refers to an Argument from within the Recipes <Arguments>-section.
This concludes the Recipe. Before the recipe can be used, it should be registered as part of the Guidance Package. We follow the structure that is used by the other Recipe’s in this Guidance package and need to make 2 modifications to existing files.
The ImportXml.xml should now look like the following: ImportXml.xml
To register a Recipe inside a Guidance Package
10. Open the Service Factory Guidance Package.xml file and inside the <Recipes> element, add a new child-element that refers to the Recipe we added. The line below can be used, but ensure that the path is correct, if you choose a different location for your Recipe.
<xi:include href="CustomRecipes/ImportXmlExample/ImportXml.xml"/>
11. Open the Bindings.xml file (in the Recipes folder) and within the <Actions> element, add a new child-element that exposes the Recipe we added to the Guidance package.
  <Action Name="ImportXmlRef" Type="RefCreator" AssetName="ImportXml"
 ReferenceType="Microsoft.Practices.Modeling.Dsl.Integration.References.SurfaceAreaReference, Microsoft.Practices.Modeling.Dsl.Integration"/> 

The recipe is now contained inside the Service Factory Guidance package.

After re-registering the Guidance package, you should be able to see a new context-menu item on the ServiceContract diagram surface called “Import Service Model Elements from Xml”.

Invoking the Recipe shows a 1 step wizard but performs, after which the 2 actions are invoked.
To Modify a Model within a Recipe’s Action.
For this specific XML Document format, the Execute method should do the following:
  • Read the contents of the Xml file
  • Open a Transaction on the Store object (The model can only be modified within the context of such a Transaction, the changes will only apply after doing a commit on the Transaction)
  • Add a Service to the model and give it the name found in the XML file.
  • Add a ServiceContract to the model and give it the name found in the XML File postfixed with “Contract” and connect this to the Service.
  • For each operation in the Xml File, we’d like to add an OperationContract to the model and reference this from the ServiceContract.
  • Commit the Transaction we opened prior. This will add the actual Elements we added to the Model and update the Diagram with shapes that represent the added Elements.
12. Open the ImportXmlAction.cs class, created in step 4 and modify its Execute method to look like the following:
public override void Execute()
{
    try
    {
        //Read the contents of the Xml document, so we can use it to import the elements.
        XmlDocument document = new XmlDocument();
        document.Load(XmlFileLocation);

        if (document.DocumentElement.Name != "service")
        {
            throw new InvalidOperationException("The selected XML file does not contain a root <services> element.");
        }

        //To interact with a store, we need to be in the context of a Transaction.
        using (Transaction transaction = ServiceContractStore.TransactionManager.BeginTransaction("ImportXmlDocument"))
        {
            //From the Store, retrieve the ServiceContractModel, this is the Element that contains all other elements in this model.
            ServiceContractModel model = DomainModelHelper.GetElement<ServiceContractModel>(this.ServiceContractStore);
            if (model != null)
            {
                //Retrieve the name attribute from the XmlDocument and check whether such a ServiceContract already exists in this model.
                string serviceName = document.DocumentElement.Attributes["name"].Value;
                if (model.Services.Exists(delegate(Service serviceInModel) { return serviceInModel.Name == serviceName; }))
                {
                    throw new InvalidOperationException(string.Format("The model already contains a service called {0}", serviceName));
                }

                //Create a new Service, through the Store's ElementFactory and assign its name.
                Service service = (Service)ServiceContractStore.ElementFactory.CreateElement(Service.DomainClassId);
                service.Name = serviceName;

                //Add the service to the Model.
                model.Services.Add(service);

                //Create a new ServiceContract, through the Store's ElementFactory and assign its name.
                ServiceContract serviceContract = (ServiceContract)ServiceContractStore.ElementFactory.CreateElement(ServiceContract.DomainClassId);
                serviceContract.Name = string.Concat(serviceName, "Contract");

                //Add the serviceContract to the Model.
                model.ServiceContracts.Add(serviceContract);

                //Assign the ServiceContract to the Service.
                service.ServiceContract = serviceContract;
                 //For each operation found in the Xml Document...
                foreach (XmlElement operationOnService in document.DocumentElement.SelectNodes("./operation"))
                {
                    //Query the operation name
                    string operationName = operationOnService.Attributes["name"].Value;

                    //Create a new Operation, through the Stores ElementFactory and assign its name.
                    Operation operation = (Operation)ServiceContractStore.ElementFactory.CreateElement(Operation.DomainClassId);
                    operation.Name = operationName;

                    //Add the operation to the model
                    model.Operations.Add(operation);

                    //Make the reference from the ServiceContract to the new Operation
                    serviceContract.operations.Add(operation);
                }
                 //Committing the transaction will update the Model and draw the appropriate shapes to the Diagram.
                transaction.Commit();
            }
        }
    }
    catch (InvalidOperationException invalidOperation)
    {
        //If something went wrong during execution, notify the user and write an exception to the output window.
        IUIService uiService = this.GetService<IUIService>();
        uiService.ShowError(invalidOperation, "Importing the Xml file did not complete successfully.");
    }
}

To register the Recipe
13. Close all other running instances of Visual Studio and:
  • Reset Visual Studio’s experimental hive
  • Re-build the Service Factory Guidance package-solution, from within the regular hive
  • Re-Register the Service Factory Guidance package-project in both the regular and experimental hive.
To test the Import Xml recipe
14. Open a new instance of Visual Studio under the experimental hive and:
  • Create a new Services Project
  • Add a new Model to the Services Project
  • Right click the designer surface on the empty model
  • In the wizard, enter the path to a copy of services.xml.
  • You should be able to see the information from services.xml reflected in the model.

Last edited Jun 7, 2007 at 11:57 PM by ndelgado, version 2

Comments

No comments yet.