Suppose you have taken over someone else’s BizTalk orchestration, and it’s huge. You make some changes, and you get a compile error like this:

error X2110: use of unconstructed message 'ExceptionMsg'

Sometimes you can click on the error, and it will take you to the proper location. If that doesn’t work, here the “secret trick”.

Rename the message in the Orhcestration View. For example, put a “2” or the word “bad” on the end.

Now, go to the Orchestration, and scroll down looking for the red exclamation points. That should show you everywhere that message is used.

Now, you should be able to find the cause of the error with a little scrolling and opening a minimal number of shapes.

Same tricks works for variables, to find or cross reference where a variable is used in the orchestration.

Had to fix some code today left by a prior BizTalk developer.

Error:

Shape name: CreateTableTypeLogMessage
ShapeId: 734241d3-f9aa-41da-8696-511010d815f7
Exception thrown from: segment 2, progress 102
Inner exception: There is no value associated with the property ‘EDI.ISA_Segment’ in the message.

Explanation:

When you drop an EDI file, all these fields will exist, and the code would work properly. I was testing by dropping an XML file (the same as created by the BizTalk EDI Receive Pipeline). When you drop an XML file, there are of course no promoted EDI fields. So the developer should check for this and handle accordingly (substituting some dummy/null values instead).

In this case, in Production the error wouldnot occur, becausse there we would presumably always be dropping EDI files. But it is nice to have the flexibility to drop an XML X12 file when needed.

See the “Code Before” shown below. This was in a catch exception block under a scope. The developer was logging almost all the fields in the ISA/GS headers. BizTalk provides promoted fields for some of them, but not all. So he had helper routine in C# which would return the nth item of header.

Code Before:

isaSegment = msgInbound(EDI.ISA_Segment);
gsSegment = msgInboundEDI.GS_Segment);

xpath(msgError,"/*[local-name()='usp_InsertEDILogRecords']/*[local-name()='EDILogRecords']/*[local-name()='udtt_EDITransactionLog']/*[local-name()='ISASender']") = Inbound_CPChemX12(EDI.ISA06);

xpath(msgError,"/*[local-name()='usp_InsertEDILogRecords']/*[local-name()='EDILogRecords']/*[local-name()='udtt_EDITransactionLog']/*[local-name()='ISADate']") = CPChemHelpers.StringSplitter.SplitString(isaSegment,'*',9);

After: Using the “exists” keword

You can make sure the promoted field is there by using the “exists” syntax.
“if (promotedField exists message)” is the syntax, where “exists” is the literal keyword, and the other two would be your values.

NOTE: You cannot use an “if” statement in a “message assignment shape”, only in an “expression shape”. So you would have to set some of these values in an expression shape in variables, and then use those variables in the message assignment shape. So the code below would NOT work as is.

The other alterantive is to use the BizTalk Decide Shape, and have two construct/message-assignment shapes, such that only the appropriate one would execute.

if (EDI.ISA_Segment exists msgInbound)
  {
     isaSegment = msgInbound(EDI.ISA_Segment);
     gsSegment = msgInboundEDI.GS_Segment);

     xpath(msgError,"/*[local-name()='usp_InsertEDILogRecords']/*[local-name()='EDILogRecords']/*[local-name()='udtt_EDITransactionLog']/*[local-name()='ISASender']") = Inbound_CPChemX12(EDI.ISA06);

     xpath(msgError,"/*[local-name()='usp_InsertEDILogRecords']/*[local-name()='EDILogRecords']/*[local-name()='udtt_EDITransactionLog']/*[local-name()='ISADate']") = CPChemHelpers.StringSplitter.SplitString(isaSegment,'*',9);
  }
else 
  {
     // put blank or other values in those fields as desired. 
  }

Decide Shape Solution

The rule itself can be set in the Properties window or by click on the Decide shape. There is a red exclamation here because I changed the message name when I took the screen shot.

How can you get values from the ISA/GS into variables in your BizTalk Orchestration?

Link to a good easy to read summary of ISA/GS segments in EDI:
https://www.availity.com/documents/ISA-IEA-GS-GE_QuickReference.pdf

I have included screen shots of their PDF here:

To get the Intellisense and compile/build to work, you will have to add a reference to Microsoft.BizTalk.Edi.BaseArtifacts.dll.

// define the variables that start with "str" and "char".
// msg204In in the message name that was in a prior receive shape. 
strISASegment = msg204In(EDI.ISA_Segment);
strGSSegment = msg204In(EDI.GS_Segment);

// position 103 always contains the field separator, 
// ISA Segment is fixed Length 
charEDIFieldSeparator = System.Convert.ToChar(strISASegment.Substring(103,1)); 

strISA13 = CSharp.Helpers.EDI.getEDIFieldFromSegment
      (strISASegment, charEDIFieldSeparator, 13); 
strGS06  = CSharp.Helpers.EDI.getEDIFieldFromSegment
      (strGSSegment,  charEDIFieldSeparator, 06); 

EDI.ISA_Segment and EDI.GS_Segment return the entire segment as a string. You will probably need a C# helper class to split based on the delimiter, as shown in the next code sample below.

The ISA Segment is fixed length and ends with three delimiter characters, for example: *:~.
That means the asterisk is the delimiter for fields, tilda is the delimiter for lines, and the colon is the delimiter for components or sub-elements (less frequently used). So you can get the field separator by doing a substring on character in the 104th position (103 if zero based).

public static class EDI
    {
        public static string getEDIFieldFromSegment(string stringToSplit, char separator, int positionToReturn)
        {
            string[] resultString;
            resultString = stringToSplit.Split(separator);
            if (resultString.Length >= positionToReturn)
                return resultString[positionToReturn];
            else
                return "getEDIFieldFromSegment:Error: Position Requested ${positionToReturn} greater than number of items ${resultString.length}";
        }

    }

NOTE: The above technique works with any and all ISA/GS fields. https://docs.microsoft.com/en-us/biztalk/core/edi-context-properties shows all the EDI fields available.

In other words, for these fields, you can do the following:

strGS06 = msg204In(EDI.GS06);

I found this related blog after I wrote the above: https://social.technet.microsoft.com/wiki/… But I’ve found that old blogs on Microsoft sites can often disappear and links don’t work after a few years. That blog also shows how to use the ISA/GS fields in a map by loading them into an XMLDocument.

Related Related MSDN forum discussion

The “ContextAccessor” is a third party tool that is also available to access these fields from maps, but it works differently. It uses a functoid to access the context fields, and has the ability to get each individual field. It was hosted on CodePlex, but I need to research where it’s new home resides.

Two Issues with Variable Initial Values

When doing a build of a BizTalk Orchestration, I saw this error:

illegal escape '\I'
illegal escape '\C'
illegal escape '\D'

The issue was that I had an orchestration variable that had the following ”
“inital value” (in the properties window): “e:\Integration\Config\DL.TL2000.Shipment.config.
Just like in C#, changed to add the @ sign so that blackslash would not be an escape character.

Similar Common Issue

If you put the set the initial to a string such as ‘abc’, but you don’t put in quotes around, you will get these two error:

1) identifier 'abc' dos not exist in "OrchestrationName'; are you missing an assembly reference.
2) cannot find symbol 'abc' 

I needed to implement RawString functionality to be able to read a non-XML file into an orchestration. Scott Colestock had a great article on this on his TraceOfThought blog, but when I needed it the other day, it had been hacked (hopefully only temporary).

1. Deploy the C# code towards the bottom of this log; it is not a pipeline, so can be added to new class to an existing C# helper library you might have, or you can put it in its own assembly.

2. Add a reference to this C# Assembly in your orchestration.

3. In your orchestration, create a multipart message type. It will have only one part, but that will be the RawString class from the code below.

4. In the orchestration, create a nessage of type System.Xml.XmlDocument, and assign this to your cativating receive. But wait Neal, I thought you said we wanted to receive a non-XML message. Trust me, this will work.

5. In the orchestration, add a Message Assignment shape and code similar to below:

//convert the message received to a new message with a multiple-part-type of RawString 
msg_rcv_RawString.MessagePart_1 = msg_XML_Doc; 

6. At this point, you can access the message as a string, for example:

varText = msg_rcv_RawString.MessagePart_1.ToString();

Voila – you have now have the text of the file in a variable called varText. You can then parse it, or do whatever you want with it.

The code below needs to be in a C# helper class that you can access within your orchestration. (Obviously give it a strong name and GAC it.) I can’t remember which site I got this code from, but I think it’s out there on several.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.IO;
using Microsoft.XLANGs.BaseTypes;

namespace REI.BizTalkNonDB.Helpers
{

    public abstract class BaseFormatter : IFormatter
    {
        public virtual SerializationBinder Binder
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        public virtual StreamingContext Context
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        public virtual ISurrogateSelector SurrogateSelector
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        public abstract void Serialize(Stream stm, object obj);
        public abstract object Deserialize(Stream stm);
    }


    public class RawStringFormatter : BaseFormatter
    {
        public override void Serialize(Stream s, object o)
        {
            RawString rs = (RawString)o;
            byte[] ba = rs.ToByteArray();
            s.Write(ba, 0, ba.Length);
        }

        public override object Deserialize(Stream stm)
        {
            StreamReader sr = new StreamReader(stm, true);
            string s = sr.ReadToEnd();
            return new RawString(s);
        }
    }

    [CustomFormatter(typeof(RawStringFormatter))]
    [Serializable]
    public class RawString
    {
        [XmlIgnore]
        string _val;

        public RawString(string s)
        {
            if (null == s)
                throw new ArgumentNullException();
            _val = s;
        }

        public RawString()
        {
        }

        public byte[] ToByteArray()
        {
            return Encoding.UTF8.GetBytes(_val);
        }

        public override string ToString()
        {
            return _val;
        }
    }


}

The following is the generally document way fo doing this:

System.Convert.ToString(Microsoft.XLANGs.Core.Service.RootService.InstanceId);

However, sometimes, for no apparent reason, it won’t compile, or at least shows a “red dot” on the upper right corner of the expression shape, even though it is technically correct. Sometimes if you open it, and close it, or move it and move it back, then the error will go away. But this is a bug, and should not be required.

New alternative way to get the Orchestration InstanceID:

strProcessId = System.Convert.ToString
(YourOrchestrationName 
(Microsoft.XLANGs.BaseTypes.InstanceId));

YourOrchestrationName is the TypeName found in the Orchestration Properties.

This has not caused the “stupid” problem of the compile errors and having to touch the shape, and move it.

One way to check for missing data is to do an xpath count.

The two-step approach (put the xpath in a variable first). Some might find this code cleaner, but you use a variable. Note that you have to use the conversion statement to convert the string result of the xpath to a number, if you need to test that number as I do in the following “if” statement.

 

strCountMessageKeysXPath = "count(//*[local-name()='MessageKey']";

intCountMessageKeys =
System.Convert.ToInt32(
xpath(msgCreateSPServiceOrderAckResp.parameters,strCountMessageKeysXPath)
);

// Yes you can put if statement in expression shapes (but not message assignment shapes) 
if (intCountMessageKeys >= 2) 
{
   strXPath = "//*[local-name()='MessageKey'][2]"; 
   strWebRespMessage2Key = xpath(msgCreateSPServiceOrderAckResp.parameters,"string(" + strXPath + ")"); 
}

 

The one-step approach:

 

strCountMessageKeysXPath = ;

intCountMessageKeys =
System.Convert.ToInt32(
xpath(msgCreateSPServiceOrderAckResp.parameters,"count(//*[local-name()='MessageKey']")
);
// Yes you can put if statement in expression shapes (but not message assignment shapes) 
if (intCountMessageKeys >= 2) 
{
   strXPath = "//*[local-name()='MessageKey'][2]"; 
   strWebRespMessage2Key = xpath(msgCreateSPServiceOrderAckResp.parameters,"string(" + strXPath + ")"); 
}

 

 

NOTE: The reason I have .parameters after the message is that it is a multi-part message.
If you don’t get that correct, you may get the error “The expression you have entered is not valid”, or “An xpath expression must be of the form” if you hover over the error.