Creating/Modifiying Schemas in BizTalk 2004. Quick Tip.

When BizTalk 2004 is installed on a developer machine, one of the many things that
is installed is the BizTalk Editor. The BizTalk Editor is used to
create and modify .xsd schemas in VS2003 that will eventually be used in a BizTalk process.

The BizTalk Editor is broken up into three parts:
1) The left hand side displays a tree structure to add new nodes to the specification.
2) The centre displays the xml markup representation of the schema.
3) The right hand side displays the properties pane, to edit the properties of the currently
highlighed node on the left hand side tree structure.

This is illustrated below.


One of the limiting factors of the BizTalk Editor is that the centre xml markup representation
is not editable.


To be able to edit the xml markup representation of the .xsd schema do this:
1) Right mouse button on the schema and choose Open With... on the pop up menu.
2) A dialog such as the one below will appear -> In this dialog, choose XML Schema editor.


3) The .xsd schema will now display in the XML Schema Editor. This editor is more object based,
displaying each element in a box. New attributes can be added to the Invoice, Invoices and InvoiceItem
elements. The properties window, can also be used to edit individual attributes.

4) While in the editor, at the bottom choose the XML tab, as below.

5) Now the below schema can be edited directly.

Constructing BizTalk 2004 XML Messages (In an Orchestration) - Choices

Before using a BizTalk XML message inside an Orchestration , it must first be
constructed. In fact ,this is just populating the message with XML. If there is an
attempt to use the message before it is constructed, the following error will appear when 
building the BizTalk project -> use of unconstructed message 'MyMessageNameHere'.
Some messages come pre-constructed, such as messages that originate from a port, but there
will be times when a message needs to be constructed inside of an orchestration.

For example if there is a message inside an Orchestration called msgShippingInfo (that is unconstructed),
and is of type ShippingInfo.xsd and the xsd schema looks like the below:

<?xml version="1.0" encoding="utf-16"?>
<xs:schema xmlns="http://ConstructingXMLMessages.ShippingInfo" xmlns:b="http://schemas.microsoft.com/BizTalk/2003" targetNamespace="http://ConsltructingXMLMessages.ShippingInfo" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="ShippingInfo">
    <xs:complexType>
      <xs:attribute name="ShipToAddress" type="xs:string" />
      <xs:attribute name="ShipToCity" type="xs:string" />
      <xs:attribute name="ShipToCountry" type="xs:string" />
    </xs:complexType>
  </xs:element>
</xs:schema>

Then at some point, the message must be populated with some xml as below, before that message can
be used:

<ns0:ShippingInfo ShipToAddress="Some Address" ShipToCity="Some City" ShipToCountry="Some Country"
xmlns:ns0="http://ConstructingXMLMessages.ShippingInfo" />


Below are some options for constructing XML messages, inside of an Orchestration.

1) Create new message(s) from a existing message(s) using a BizTalk Map.
This is one of the more common methods. For example,
I need to map my incoming Purchase Order message to an outgoing
Invoice message. Also keep in mind that you can have multiple input messages and output messages
when mapping. For example within an Orchestration, there is an existing constructed Purchase Order and Shipping messages.
I can then use these two messages to create an Order message.

A simple example is as below: 

 

If you have not done it before :
To create a map with multiple inputs and outputs, create the needed messages
in the Orchestration View. For example , in this case an Order message,
PurchaseOrder Message and the Shipping Message.
Drop a transform shape on your orchestration and then choose the source(input) message(s).
In this case there are two : PurchaseOrder message and Shipping message.
Choose the destination(output) message(s) : Order message.
In the Transform Configuration dialog box, choose -> new map. In this case
a new map is created that looks like the above (minus any of the links and functoids between source and
destination messages). These were put in later.

2) Assign one message to another.
At some point you may need a copy of another message. If msgOrderCopy is of the same type
as msgOrderOriginal, then the below assignment can be done.


Construct msgOrderCopy
{

  msgOrderCopy = msgOrderOriginal;
  //Once the new message is constructed, it can now be modified.
  // For example if the schema for the order has distinguished fields,
  // these can now be set. XPATH can also be used to set the data in (Some xpath examples are further down).
  msgOrderCopy.OrderedBy = "Bob";

}


3) Create a message with the help of a System.XML.XMLDocument variable.

In the below example, the XML to populate the message is hardcoded inside the
expression shape. The XMLDocument variable is initially assigned to the hardcoded XML. The BizTalk message is
then assigned to the XMLDocument variable. The BizTalk message is now good to go.
This is a handy technique especially when the schemas/xml instance messages are small. If the schema
has distinguished fields, then these can then be used to set the attributes in the message with real data, otherwise XPATH
can be used to populate the ID and Name attributes of the below simple XML message. It is also
possible to alter the hardcoded XML with the real data, and then do the message assignments.

construct msgShipping
{
  // Use the Variable varXMLDom to load in some XML
 varXMLDom.LoadXml(@"<ns0:ShippingInfo ShipToAddress=""Some Address"" ShipToCity=""Some City"" ShipToCountry=""Some Country"" xmlns:ns0=""http://ConstructingXMLMessages.ShippingInfo"" />");
  // Assign the BizTalk message to the XMLDom variable. Note this can be accomplished becuase a BizTalk message derives from an XMLDocument.
  msgShipping = varXMLDom;
  // If there are Distinguished Fields available, then
  msgShipping.ShipToAddress = "1 Main Street";
  msgShipping.ShipToCity = "Toronto";
  msgShipping.ShipToCountry = "Canada";
  // If there are no distinguished fields, then XPATH can be used to set the attributes in the message.
  xpath(msgShipping , "//@ShipToAddress") = "1 Main Street";
  xpath(msgShipping, "//@ShipToCity") = "Toronto";
  xpath(msgShipping, "//@ShipToCountry") = "Canada";
 


4) Creating a more complex message using : System.XML.Document, System.XML.XMLNode,System.XML. XMLAttribute, Interim BizTalk Message
The below example, uses the same method as in 3) , but the message is more complicated.
A Looping shape is used to add the order items to an order message. The part of the orchestration that does this
is as below:

 


In the the First Expression Shape, Construct the initial Order Message (With just the Order Header), with the help of a XMLDocument variable
construct msgOrderFromAppending
{
   // Initially create the Order Header part of the Order Message
   varOrderXMLDom.LoadXml(@"<ns0:Order OrderedBy=""SomeBody"" TotalOrderAmount=""0"" xmlns:ns0=""http://ConstructingXMLMessages.Orders""></ns0:Order>");
   msgOrderFromAppending = varOrderXMLDom;

}


In the next expression shape add the order items. Note that in the Orchestration this expression
shape is in a loop, so there are multiple order items being added.


// Construct the Temp Order Message to Append the Node.
construct msgOrderFromAppendingTemp
{
   // First set the XMLDOM variable to the msgOrderFromAppending in the first shape.
   varOrderXMLDom = msgOrderFromAppending;
   // use CloneNode to make a fresh copy of msgOrderFromAppending
   varOrderXMLDom = (System.Xml.XmlDocument) varOrderXMLDom.CloneNode(true);
   // Create a new Order Item Node
   varXMLNodeOrderItemNode = varOrderXMLDom.CreateElement("OrderItems");
   // Create Amount Attribute
   varXMLAttribute = varOrderXMLDom.CreateAttribute("Amount");
   varXMLAttribute.InnerText = "0";
   varXMLNodeOrderItemNode.Attributes.Append(varXMLAttribute);
   // Create Description Attribute
   varXMLAttribute = varOrderXMLDom.CreateAttribute("Description");
   varXMLAttribute.InnerText = "A Description";
   varXMLNodeOrderItemNode.Attributes.Append(varXMLAttribute);    
   // Create Qty Attribute
   varXMLAttribute = varOrderXMLDom.CreateAttribute("Qty");
   varXMLAttribute.InnerText = "0";
   varXMLNodeOrderItemNode.Attributes.Append(varXMLAttribute);    
   // Now append the actual Order Item Node to the XML Instance
   varOrderXMLDom.FirstChild.AppendChild(varXMLNodeOrderItemNode);
   // Now actually construct the Temp message.
   msgOrderFromAppendingTemp = varOrderXMLDom;
   System.Diagnostics.Debug.WriteLine("The output is " + varOrderXMLDom.OuterXml);
}

// Now Set the Real Order Message From the Temp Message.
// This is so we can keep adding messages to the real message, without losing them.

construct msgOrderFromAppending
{
   msgOrderFromAppending = msgOrderFromAppendingTemp;
   // Set the Data in the newly added Order Item Node.
  
   // Now use XPAth to set the Data in the attributes,
   // NOTE: I could of also done this by setting the real data
   // on the XMLAttribute object -> varXMLAttribute.InnerText = "Description for Order Item : " +  System.Convert.ToString(varOrderItemCount) ;

   strvarOrderItemCount = System.Convert.ToString(varOrderItemCount); 
   xpath(msgOrderFromAppending,"//OrderItems[" + strvarOrderItemCount + "]//@Amount") = System  m.Convert.ToString(varOrderItemCount) ;
   xpath(msgOrderFromAppending,"//OrderItems[" + strvarOrderItemCount + "]//@Qty") = System.Convert.ToString(varOrderItemCount) ;
   xpath(msgOrderFromAppending,"//OrderItems[" + strvarOrderItemCount + "]//@Description") = "Description for Order Item : " +  System.Convert.ToString(varOrderItemCount) ; 
}


5) Use a .Net helper class to create the message.

As in the above example , instead of using XMLDOM type code in the orchestration, a helper
.NET class could of been called in the orchestration to create the message.
An example helper class would look like the below, just returning an XMLDocument that could be assigned to
the message inside the orchestration.

public static XmlDocument GetXmlDocumentOrderTemplate()
{
// Code to create the message.

}

Now inside the orchestration, the code to call the .Net helper class to construct the message.

Construct myOrderMessage
{

  myOrderMessage = HelperClass.GetXmlDocumentOrderTemplate 


}

Also look here


Download the above examples , Here

BizTalk 2004 Map. Mapping two input messages into one output message , plus matching content from one message to another.

I have had the problem of having a message such as a Order message, being delivered 
as two separate messages. For example, the Order header Records delivered as one XML message,
and then all the Order Items that belong to Order header Records delivered as a separate XML message.

The two separate messages being delivered look something like below:

Order Headers:

<ns0:Orders Correlator="1" xmlns:ns0="http://IDREFTest.Orders">
  <Order OrderID="6" OrderedBy="BillyBobJim" />
  <Order OrderID="1" OrderedBy="Bob" />
  <Order OrderID="2" OrderedBy="Bobby" />
  <Order OrderID="3" OrderedBy="BobbyJohn" />
  <Order OrderID="4" OrderedBy="BobbyNoItems" />
  <Order OrderID="5" OrderedBy="BillyBob" />
  <Order OrderID="0022" OrderedBy="BillyBobJim" />
</ns0:Orders>
  
  
Order Items:

<ns0:Order Correlator="1" xmlns:ns0="http://IDREFTest.OrderItems">
  <OrderItems OrderID="1" Qty="1" />
  <OrderItems OrderID="1" Qty="11" />
  <OrderItems OrderID="1" Qty="111" />
  <OrderItems OrderID="2" Qty="2" />
  <OrderItems OrderID="2" Qty="22" />
  <OrderItems OrderID="2" Qty="222" />
  <OrderItems OrderID="3" Qty="3" />
  <OrderItems OrderID="3" Qty="33" />
  <OrderItems OrderID="3" Qty="333" />
  <OrderItems OrderID="3" Qty="3333" />
  <OrderItems OrderID="5" Qty="5" />
  <OrderItems OrderID="5" Qty="1" />
  <OrderItems OrderID="5" Qty="11" />
  <OrderItems OrderID="5" Qty="111" />
  <OrderItems OrderID="6" Qty="333" />
  <OrderItems OrderID="6" Qty="3333" />
  <OrderItems OrderID="0022" Qty="3" />
  <OrderItems OrderID="0022" Qty="33" />
</ns0:Order>

My goal then was to create a final Order XML Message that looks like the below (combining
information from the two messages into one message, plus also matching the correct order items with
the correct order headers). I wanted to accomplish this by just using BTS maps and functoids
that are installed with BTS2004.

<ns0:Orders xmlns:ns0="http://IDREFTest.OrdersandOrderItems">
 <Order OrderedBy="BillyBobJim" OrderID="6">
    <OrderItems OrderID="6" Qty="333" />
    <OrderItems OrderID="6" Qty="3333" />
</Order>
 <Order OrderedBy="Bob" OrderID="1">
    <OrderItems OrderID="1" Qty="1" />
    <OrderItems OrderID="1" Qty="11" />
    <OrderItems OrderID="1" Qty="111" />
 </Order>
    <Order OrderedBy="Bobby" OrderID="2">
    <OrderItems OrderID="2" Qty="2" />
    <OrderItems OrderID="2" Qty="22" />
    <OrderItems OrderID="2" Qty="222" />
  </Order>
  <Order OrderedBy="BobbyJohn" OrderID="3">
     <OrderItems OrderID="3" Qty="3" />
     <OrderItems OrderID="3" Qty="33" />
     <OrderItems OrderID="3" Qty="333" />
     <OrderItems OrderID="3" Qty="3333" />
  </Order>
 <Order OrderedBy="BobbyNoItems" OrderID="4" />
 <Order OrderedBy="BillyBob" OrderID="5">
     <OrderItems OrderID="5" Qty="5" />
     <OrderItems OrderID="5" Qty="1" />
     <OrderItems OrderID="5" Qty="11" />
     <OrderItems OrderID="5" Qty="111" /> 
  </Order>
 <Order OrderedBy="BillyBobJim" OrderID="0022">
     <OrderItems OrderID="0022" Qty="3" />
     <OrderItems OrderID="0022" Qty="33" />
 </Order>
</ns0:Orders> 

Therefore to accomplish this:

1) Created an Orchestration that contains a parallel convoy, that correlates on
the Correlator attribute on both the Orders.xsd and OrderItems.xsd properties.

2) Created a Map with two inputs (Order Header and Order Items) and one output (OrdersAndOrderItemsAll). 
To create a map with multiple inputs and outputs, create the needed messages
in the Orchestration View. For example , in this case an OrdersOnly message,
OrderItemsOnly Message and the OrdersAndOrderItemsAll Message.
Drop a transform shape on your orchestration and then choose the source(input) message(s).
In this case there are two : OrdersOnly message and OrderItemsOnly message.
Choose the destination(output) message(s) : OrdersAndOrderItemsAll message.
In the Transform Configuration dialog box, choose -> new map. In this case
a new map is created that looks like the below (minus any of the links and functoids between source and
destination messages). These were put in later.

 


3) In the map as above, links and two looping functoids were used to create an interim message (OrdersAndOrderItemsAll)
All OrderHeader records will contain all the OrderItem records.

4) Create another map that looks like the below, using the OrdersAndOrderItemsAll message as the source and using a new
message OrdersAndOrderItems as the destination. The equal functoid is used to include only the correct orderitems with each
order header. This map creates the desired resultant xml instance.

 

Download the complete solution Here.

What I like about this solution, is that it works. What I don't like about this solution is the interim message, with each order header message
including all the order items. In my case the incoming OrderHeader message and OrderItem messages were not terribly large, therefore
the interim message was not huge. Of course there may be other ways to accomplish this, possibly using custom xslt.

Passing an XML instance to a Web Service Method via a BizTalk 2004 Orchestration Web Port -> Choices.

Need to call a web method from a BizTalk 2004 Orchestration Web Port and pass an instance of XML to the web method.
Below are some options:


1) Pass the XML as a string.

For example your Web Method that you need to call from your orchestration might look something like this:

[WebMethod]
ProcessOrder (string myOrder)
{

 System.XML.XMLDocument myOrderDom = new System.XML.XMLDocument()
 myOrderDom.load(myOrder)
   // Now have code to process the order
 System.Xml.XmlNodeList nodeList = myOrderDom.GetElementsByTagName("OrderItems");
 System.Diagnostics.Debug.WriteLine("The number of Order Items passed in with the XMLDocument is: " + nodeList.Count.ToString());
 foreach(System.Xml.XmlNode orderItemNode in nodeList)
 {
  System.Diagnostics.Debug.WriteLine("The order item node is : " + orderItemNode.OuterXml);

 } 
 

}

In the Orchestration :
To create the XML string representation of the message in your orchestration:
a) In your orchestration create a variable of type System.XML.XMLDocument

System.XML.XMLDocument varXMLDOM

b) In an expression shape assign the XMLDocument type variable to the message that
you want to pass to the XMLDocument.

varXMLDOM = msgIncomingOrder;

c) Finally set the message that contains the string parameter that will be passed to the Web Method:

msgXMLStringRequest.XMLAsString = varXMLDOM.OuterXml;


2) Pass the XML as a System.XML.XMLDocument
For example your Web Method that you need to call from your Orchestration might look something like this:


[WebMethod]
ProcessOrder (system.XMl.XMLDocument myOrder)
{

 System.Xml.XmlNodeList nodeList = myOrder.GetElementsByTagName("OrderItems");
 System.Diagnostics.Debug.WriteLine("The number of Order Items passed in with the XMLDocument is: " + nodeList.Count.ToString());
 foreach(System.Xml.XmlNode orderItemNode in nodeList)
 {
  System.Diagnostics.Debug.WriteLine("The order item node is : " + orderItemNode.OuterXml);

 }

}


In the Orchestration :
To set the message that contains the XMLDocument parameter that will be passed to the Web Method , simply set the web message to the orchestration message :
msgXMLDomRequest.XMLAsXMLDocument = msgIncomingOrder;


3) Pass the XML as a Strongly Typed Class

Your order class (defined on the Web Service side) might look something like this :

[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://PassingXMLToWS.Orders")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="http://PassingXMLToWS.Orders", IsNullable=false)]
public class Orders
{   
 [System.Xml.Serialization.XmlElementAttribute("OrderItems", Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
 public OrdersOrderItems[] OrderItems;

 [System.Xml.Serialization.XmlAttributeAttribute()]
 public string OrderId;
   
 [System.Xml.Serialization.XmlAttributeAttribute()]
 public string TotalAmount;
}


[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://PassingXMLToWS.Orders")]
public class OrdersOrderItems
{
   
 [System.Xml.Serialization.XmlAttributeAttribute()]
 public string OrderItemId;
   
 [System.Xml.Serialization.XmlAttributeAttribute()]
 public string ItemAmount;
}

Note: I created the above class by using the xsd utility that is installed with VS.Net.
a) I created an xsd BizTalk Schema, and called it Orders.xsd
b) I then generated the above class, by opening up the Visual Studio Command Prompt and then
typing in the following :
c:\passxmltowebservice> xsd Orders.xsd /c /l:cs
c) The xsd utility then created a class for me called, Orders.cs, which I then copied over to
my web service project.


Your Web method that accepts an Order class might look like the below:

[WebMethod]
public void AcceptOrderClass(Orders myOrders)
{
 System.Diagnostics.Debug.WriteLine("In AcceptOrderClass");
 System.Diagnostics.Debug.WriteLine("The Id of the Order passed in is: " + myOrders.OrderId);
 foreach (OrdersOrderItems orderItem in myOrders.OrderItems)
 {
  System.Diagnostics.Debug.WriteLine("The Id of the Order Item is " + orderItem.OrderItemId); 
 }
}


In your BizTalk project, when the Web Reference is added,
an XSD schema will be created that represents the Orders class (Called Reference.xsd).
Inside your Orchestration you create a message of type Reference.xsd and then construct this message, just like any other message.
When your message reaches the web service side it will automatically be converted to an instance of -> public class Orders  


Here is a sample of each.

 

 


 

Passing a Binary as a parameter to a Web Method from a BizTalk 2004 Orchestration Web Port

Christof Claessens has an excellent entry on how to pass a binary object
to a Send Port, such as a file send port. The binary that is saved by the 
send port using his method will automatically be saved to its native format when written out to a file. For
example : c:\myBTS2004SendLocation\MyExcelFile.xls

But using the above will not necessarily work when calling a web method
via a BizTalk Orchestration Web Port as below:

[WebMethod]
ProcessBinary (System.XML.XMLDocument binaryDocument)
{

}

The Web Method is expecting well formed XML that is not present in the binary document.
When the Web Service is called from the orchestration, the Web Service will throw an error.

A possible solution is to convert the binary document to an array Of bytes
and then call a web method via a BizTalk Orchestration Web Port as below:

[WebMethod]
ProcessBinary (System.Byte[] binaryDocument)
{

}

But when you try to add the Web Reference to a BizTalk project, you will get an error such as:
Could not generate BizTalk files. An (WebMethodName) operation parameter
or return type is an Array. Array types are not supported.

A very simple solution is to convert the binary document to an array of bytes,
then convert the array of bytes to a string using something like the code below:

byte[] myBytes;
FileStream fs = new FileStream(@"C:\myExcelDoc.xls",FileMode.Open, FileAccess.Read);
BinaryReader r = new BinaryReader(fs);
myBytes = r.ReadBytes(Convert.ToInt32(r.BaseStream.Length));
// Now convert the array of bytes to a string.
string myBase64String;
myBase64String = Convert.ToBase64String(myArrayofBytes);

The above string can now be passed to the below web method via a
BizTalk Orchestration Web Port as below:

[WebMethod]
ProcessBinary (System.String binaryDocument)
{

 byte[] arrayOfBytes;
 arrayOfBytes = Convert.FromBase64String(binaryDocument);
 FileStream fs = new FileStream(@“c:\fileout.xls“, FileMode.CreateNew);
 BinaryWriter w = new BinaryWriter(fs);
 w.Write(arrayOfBytes);  

}


Here download the complete solution.

 

 


 

BizTalk 2004 Presentation

I am doing a BizTalk 2004 presentation on Wed. June 16 at the Metro Toronto .NET User Group. So if you can make it please come.

http://www.metrotorontoug.com/User+Group+Events/97.aspx