Starting BizTalk Server 2004 orchestrations using the MessageAgent.

Jesús Rodríguez.

Didiosky Benítez.

 

Introduction.

The messaging system of BizTalk Server 2004 relies over a powerful publish-subscribe architecture that uses Sql Server as primary store. The main component to interact directly with this system is the MessageAgent. The main idea of this article is to show how to uses the MessageAgent to start BizTalk orchestrations from .Net code.

 

MessageBox and the MessageAgent.

It is not an objective of this article to explain the BizTalk Server 2004 subscription mechanisms. About this topic Lee Graber post two interesting comments (Is there a Pub/Sub System Underneath BizTalk? and Okay, so Where do Subscriptions Come From?) that go deep inside this pub-sub architecture. This section however gives a briefly explanation of basic components in the BizTalk Server 2004 subscriptions system.

The BizTalk Server 2004 pub-sub system can be segmented in two main parts. The first part is the storage portion and is known as the MessageBox. This part provides the storage mediums and the primary logic to maintain the state of a subscription. The second part is the low-level API that the different BizTalk Server 2004 engines uses to interact with the MessageBox, this part is known as the MessageAgent.

The main idea behind BizTalk Server 2004 subscriptions is amazingly simple. Orchestrations are the principal subscribers via its receive actions. There are two main types of subscriptions (see Lee post) activation and correlation subscriptions. In this article we primarily focus in Instance subscriptions that always creates a new instance of a service. An activation subscription is created when you enlist a service, and immediately the subscription enters in a stopped state. In the time that the subscription remains stopped all the messages that match this subscription go to the “SuspendedQ”. When the service starts the subscription pass to the active state and the messages goes to the “WorkQ”. When you perform an “unenlist” of your services you simply destroy the subscriptions associated with those services. This simply mechanism makes possible to keep the state of the subscriptions and its associated messages.

As I pointed early, the MessageBox contains the primary logic (stored procedures, functions, triggers) and the storage representation (tables, views) to interact with subscriptions. A subscription is formed by a set of predicates that are evaluated against each incoming messages to determine is that message match with the subscription. Every message contains a series of contextual properties that are used by those predicates. When a message arrives to the MessageBox the bts_InsertProperty sp is called to insert the each contextual property in the MessageProps table. Then the bts_FindSubscriptions sp is executed to find the available subscriptions and if any subscription is founded the bts_InsertMessage is called to insert the message into the MessageBox. The predicates that forms the subscription are stored in the following tables:

  • Bitwise AND
  • Equals
  • Exists
  • GreaterThanOrEquals
  • GreaterThan
  • LessThanOrEquals
  • LessThan
  • NotEquals

 

It is obvious that the BizTalk engines needs a via to interact to the MessageBox with a different level of abstractions. That is precisely the MessageAgent role; it provides an object hierarchy that makes possible to interact with the MessageBox. The MessageAgent has its own message definition; a message has context properties and is formed by a part that contains the data. The message contents are absolutely irrelevant to the MessageAgent and would be defined by the superior tiers. There are to main types of properties: context written and promoted. The main difference is that promoted property is used against the subscriptions predicates but both types of properties are stored in the MessageBox.

 

GetObject Subscription.

BizTalk Server 2004 orchestrations can be activated in different ways. One of the most typical ways occurs when some messages arrives to a well-defined location using some transport protocol. However an orchestration can start another orchestration passing a set of parameters. This is the role performed by the Start Orchestration shape in the orchestration designer. A very recurrent question in BizTalk developers is: can we start an orchestration from .NET code passing the required parameters? Well the answer is yes and (surprise, surprise) is solved using subscriptions.

To invoke an orchestration we can use a special type of subscription that is created in the MessageBox and is know as GetObject subscription. The invoker orchestration calls the ExecuteService method of the Microsoft.BizTalk.XLANGs.BTXEngine.BTXService class to pass a well-defined message and the required parameters that activates the invoked orchestration. The Microsoft.BizTalk.XLANGs.BTXEngine.ExecMessage class abstracts this message type. In general terms all we must do to execute an orchestration is submit a message to the MessageBox that belong to the ExecMessage type and specify the orchestration that should be activated. These steps are absolutely enough to execute an orchestration that receives no parameters. In the next section we explore the basic code to submit messages to the MessageBox that starts non-parameterized and parameterized orchestrations.

Starting orchestrations using the MessageAgent.

To interact with the MessageBox we use the MessageAgent, which in managed environments is abstracted in Microsoft.BizTalk.Agent.Interop namespace.

To initialize the MessageAgent we must perform a series of basic steps that are shown in the next code section.

IBTMessageAgent agent= new BTMessageAgent() as IBTMessageAgent; agent.SetConfigDbLocation(MyServer…,  My Configuration DB…);

IBTMessageSpace sp= agent.RegisterAsPublisher(My Bts Group…, Bts Application name…);

IBTMessageSpaceEx spx= (IBTMessageSpaceEx)sp;

Guid spuid= Guid.Empty;

Guid agentid= BTXProperties.MessageAgentClassID;

agent.RegisterService(ref agentid, null);

IBTMessageBatch expex= sp.GetMessageBatch(ref spuid);

 

The code is very simple. We first create an agent and set the configuration information that includes the SQL Server name and the BizTalk management database. The following steps register the agent as publisher and obtain the message batch that will be used to submit the messages.

To start an orchestration that receive no parameters we add a few lines that deals directly with the message composition.

IBTMessageAgent agent= new BTMessageAgent() as IBTMessageAgent; agent.SetConfigDbLocation(MyServer…,  My Configuration DB…);

IBTMessageSpace sp= agent.RegisterAsPublisher(My Bts Group…, Bts Application name…);

IBTMessageSpaceEx spx= (IBTMessageSpaceEx)sp;

Guid spuid= Guid.Empty;

Guid agentid= BTXProperties.MessageAgentClassID;

agent.RegisterService(ref agentid, null);

IBTMessageBatch expex= sp.GetMessageBatch(ref spuid);

//new code…

IBTMessageAgentFactory msgfactory = (IBTMessageAgentFactory)agent;

//creates the message...

IBTMessage msg= (IBTMessage)msgfactory.CreateMessage();

msg.Context.Write("MessageType", "http://schemas.microsoft.com/BizTalk/2003/system-properties",                                  "Microsoft.BizTalk.XLANGs.BTXEngine.ExecMessage");

msg.Context.Promote("MessageType", "http://schemas.microsoft.com/BizTalk/2003/system-properties",                                    "Microsoft.BizTalk.XLANGs.BTXEngine.ExecMessage");

msg.Context.Write("ActivationServiceID", "http://schemas.microsoft.com/BizTalk/2003/system-properties", My Orchestration full name...);

msg.Context.Promote("ActivationServiceID", "http://schemas.microsoft.com/BizTalk/2003/system-properties", My Orchestration full name...);

msg.OriginatorSID= My user...;

msg.OriginatorSID= My PID…;

msg.IgnoreRoutingFailures= true;

IBTMessage[] msgs= new IBTMessage[1]{msg};

expex.PostMessages(msgs, 1);

//gets a transaction...

Type TXType = Type.GetTypeFromCLSID(new Guid("30C1F639-A3A6-4E0A-879D-0B15020754C6"));

object TXObj = Activator.CreateInstance(TXType);

IXLANGTranDispImpl TXImpl = (IXLANGTranDispImpl) TXObj;

ITransaction CurrentTX= (ITransaction)TXImpl.GetTransaction(Guid.NewGuid().ToString(), -1, 1048576);

IBTOperationStatus opst= null;

try

  {

    expex.CommitBatch(CurrentTX, CommitBatchFlags.CB_Default, out opst);

    txxx.Commit(0, 1, 0);                                

   }

catch(Exception ex)

  {

    Marshal.ReleaseComObject(opst);

  }

 

The code basically creates a new message using a Message Factory and set the MessageType and ActivationServiceID contextual properties that are evaluated against the predicates to find the available subscriptions. The final steps commit the message batch using a previously initialized transaction.

The generic code explained above works well with orchestration that receive 0 parameters. But you agree with me in that is not the most typical case. It is much more common that orchestrations receive some type of parameters as part of its activation process. The MessageAgent have no notion about parameters, it represents the parameters as a non-body MessagePart. In the case of GetObject subscription we must add a MessagePart for each parameter and one special part, known as MetaData, that describes the rest of the parameters. The MetaData is used by the superior tiers to deserialize the parameters and is required in most of the cases.

In object terms the MetaData part is typically an object of type Microsoft.BizTalk.XLANGs.BTXEngine.ExecMessage+ParamDataArray, this is a public class inside the internal ExecMesage class. This class contains an array of Microsoft.BizTalk.XLANGs.BTXEngine.ExecMessage+ParamData that encapsulates the MetaData associated with a parameter. The stream of data that the MessageAgent send to the MessageBox is the result of serializes an instance of ParamDataArray using the BinaryFormatter. Suppose that we have an orchestration that receives two string parameters an output the result of its concatenation. The next code makes an exhaustive use of reflection to obtain the binary stream that contains the metadata of the two string parameters.

//constants section…

#region consts

const string gacassb= "Microsoft.XLANGs.BizTalk.Engine, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35";

const string iFirstPropName= "iFirst";

const string nPartsPropName= "nParts";

const string TypePropName = "Type";

const string ArrayPropName= "Item";

#endregion

//this method returns the metadata relative to the two string parameters…

public Stream GetValueParamTestMetaData()

{

  Assembly btxassb= Assembly.Load(gacassb);

  Type ParamArrayType=   btxassb.GetType("Microsoft.BizTalk.XLANGs.BTXEngine.ExecMessage+ParamDataArray");

  Type ParamDataType= btxassb.GetType("Microsoft.BizTalk.XLANGs.BTXEngine.ExecMessage+ParamData");

  object[] args= new object[]{2};

  object ParamsMetaData= Activator.CreateInstance(ParamArrayType, args);

  for(int index= 0; index<= 1; index++)

  {

    //creates the first param...

    object ParamMetaData= Activator.CreateInstance(ParamDataType);

    //set the metadata properties...

    ParamDataType.GetProperty(iFirstPropName).SetValue(ParamMetaData, index + 1, null);

    ParamDataType.GetProperty(nPartsPropName).SetValue(ParamMetaData, 1, null);

    ParamDataType.GetProperty(TypePropName).SetValue(ParamMetaData, typeof(System.String), null);

    //creates the param list...                                         ParamArrayType.GetProperty(ArrayPropName).SetValue(ParamsMetaData, ParamMetaData, new     object[]{index});

  }

  //serializes the meta data...

  BinaryFormatter formatter= new BinaryFormatter();

  MemoryStream buffer= new MemoryStream();

  formatter.Serialize(buffer, ParamsMetaData);

  buffer.Seek(0, SeekOrigin.Begin);

  return buffer;

}

 

 

Now we make a little modification to our base code to include the parameters and metadata MessageParts.

//this method serializes gets the data stream of the parameter…

public Stream GetValueParamTestData(string ParamValue)

{

  MemoryStream buffer= new MemoryStream();

  BinaryFormatter formatter= new BinaryFormatter();

  formatter.Serialize(buffer, ParamValue);

  buffer.Seek(0, SeekOrigin.Begin);

  return buffer;

}

//the general code…

IBTMessageAgent agent= new BTMessageAgent() as IBTMessageAgent; agent.SetConfigDbLocation(MyServer…,  My Configuration DB…);

IBTMessageSpace sp= agent.RegisterAsPublisher(My Bts Group…, Bts Application name…);

IBTMessageSpaceEx spx= (IBTMessageSpaceEx)sp;

Guid spuid= Guid.Empty;

Guid agentid= BTXProperties.MessageAgentClassID;

agent.RegisterService(ref agentid, null);

IBTMessageBatch expex= sp.GetMessageBatch(ref spuid);

//new code…

IBTMessageAgentFactory msgfactory = (IBTMessageAgentFactory)agent;

//creates the message...

IBTMessage msg= (IBTMessage)msgfactory.CreateMessage();

msg.Context.Write("MessageType", "http://schemas.microsoft.com/BizTalk/2003/system-properties",                                  "Microsoft.BizTalk.XLANGs.BTXEngine.ExecMessage");

msg.Context.Promote("MessageType", "http://schemas.microsoft.com/BizTalk/2003/system-properties",                                    "Microsoft.BizTalk.XLANGs.BTXEngine.ExecMessage");

msg.Context.Write("ActivationServiceID", "http://schemas.microsoft.com/BizTalk/2003/system-properties", My Orchestration full name...);

msg.Context.Promote("ActivationServiceID", "http://schemas.microsoft.com/BizTalk/2003/system-properties", My Orchestration full name...);

msg.Context.Write("ParameterCount", "http://schemas.microsoft.com/BizTalk/2003/xlangs-runtime-properties", 2);

msg.OriginatorSID= My user...;

msg.OriginatorSID= My PID…;

msg.IgnoreRoutingFailures= true;

msg.SetPartCount= 3;

IBaseMessagePart Param1Part= msgfactory.CreateMessagePart();

IBaseMessagePart Param2Part= msgfactory.CreateMessagePart();

Param1Part.Charset= "utf-8";

Param2Part.Charset= "utf-8";

IBaseMessagePart MtPart= msgfactory.CreateMessagePart();

//fills the meta data...                            

MtPart.Data= GetValueParamTestMetaData();

MtPart.Charset= "utf-8";

MemoryStream Param1Strm= (MemoryStream)GetValueParamTestData("Hello");

MemoryStream Param2Strm= (MemoryStream)GetValueParamTestData("ExecMessage");

Param1Part.Data= Param1Strm;

Param2Part.Data= Param2Strm;

msg.AddPart("MetaData", MtPart, false);

msg.AddPart("Param0", Param1Part, false);

msg.AddPart("Param1", Param2Part, false);

IBTMessage[] msgs= new IBTMessage[1]{msg};

expex.PostMessages(msgs, 1);

//gets a transaction...

Type TXType = Type.GetTypeFromCLSID(new Guid("30C1F639-A3A6-4E0A-879D-0B15020754C6"));

object TXObj = Activator.CreateInstance(TXType);

IXLANGTranDispImpl TXImpl = (IXLANGTranDispImpl) TXObj;

ITransaction CurrentTX= (ITransaction)TXImpl.GetTransaction(Guid.NewGuid().ToString(), -1, 1048576);

IBTOperationStatus opst= null;

try

  {

    expex.CommitBatch(CurrentTX, CommitBatchFlags.CB_Default, out opst);

    txxx.Commit(0, 1, 0);                                

   }

catch(Exception ex)

  {

    Marshal.ReleaseComObject(opst);

  }

 

The black lines represent the adding to our base code. We basically create the MetaData part set the data using the GetValueParamTestMetaData method explained above. We also create two message parts (one for each parameter), sets its stream data and add it to the message. We also set the ParameterCount contextual property and the PartCount message property.

That’s all. With using variants of this code we can pass .Net parameters and execute orchestrations. We can use .Net parameters because in the that we can uses XMessages we have a little dependency with the types generated by BizTalk in the compilation process of our orchestrations.

The types that we use must be serializables since the BizTalk engine have to deserialize those message parts to obtain instances of the parts specified in the MetaData part. It is a typical case that some orchestrations receives generics documents represented by an instance of the System.Xml.XmlDocument class, but this class is not marked as serializable and that’s a problem. To pass XmlDocument parameters to our orchestrations we should use the Microsoft.XLANGs.RuntimeTypes.XmlDocumentSerializationProxy class to wrap the documents. The next code shows how to obtain the MetaData relative to an XmlDocument parameter.

//constants section…

#region consts

const string gacassb= "Microsoft.XLANGs.BizTalk.Engine, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35";

const string iFirstPropName= "iFirst";

const string nPartsPropName= "nParts";

const string TypePropName = "Type";

const string ArrayPropName= "Item";

#endregion

//this method returns the meta data of an XmlDocument parameter…

public Stream GetCustomMetaData()

{

  Assembly btxassb= Assembly.Load(gacassb);

  Type ParamArrayType= btxassb.GetType("Microsoft.BizTalk.XLANGs.BTXEngine.ExecMessage+ParamDataArray");

  Type ParamDataType= btxassb.GetType("Microsoft.BizTalk.XLANGs.BTXEngine.ExecMessage+ParamData");

  object[] args= new object[]{1};

  //creates the first param...

  object ParamMetaData= Activator.CreateInstance(ParamDataType);

  //set the metadata properties...

  ParamDataType.GetProperty(iFirstPropName).SetValue(ParamMetaData,  1, null);

  ParamDataType.GetProperty(nPartsPropName).SetValue(ParamMetaData, 1, null);

  ParamDataType.GetProperty(TypePropName).SetValue(ParamMetaData,         typeof(Microsoft.XLANGs.RuntimeTypes.XmlDocumentSerializationProxy), null);

  //creates the param list...

  object ParamsMetaData= Activator.CreateInstance(ParamArrayType, args);

  ParamArrayType.GetProperty(ArrayPropName).SetValue(ParamsMetaData, ParamMetaData, new object[]{0});

  //serializes the meta data...

  BinaryFormatter formatter= new BinaryFormatter();

  MemoryStream buffer= new MemoryStream();

  formatter.Serialize(buffer, ParamsMetaData);

  buffer.Seek(0, SeekOrigin.Begin);

  return buffer;

}

 

The required code to uses the XmlDocument-based Meta data is the same that our previous example with the exception that we only have to add one part that contains the Xml documents.

Where are we?

In this article we have shown how to uses the MessageAgent to submit messages to the MessageBox that starts some orchestrations. We also explore different variants to pass the required orchestration parameters. We really think that using the MessageAgent is a nice vehicle to develop powerful low-level BizTalk messaging applications. We hope that with this article we try the subscriptions interiorities much more closely to BizTalk developers.

In future posts we explore how to use the MessageAgent to start activate orchestrations without any interaction with the required adapters.

------------------------------------------------------------------

My friend Didiosky Benítez is an expert in. NET-based technologies and specifically in Web Services and messaging related technologies. He has been working with BizTalk since the 2002 version.

Published Saturday, January 08, 2005 11:42 AM by gsusx

Comments

Monday, January 10, 2005 2:13 AM by TrackBack

# BizTalk Server 2004: Invoking orchestrations in custom code

Monday, January 10, 2005 10:33 AM by Lee

# re: Starting BizTalk Server 2004 orchestrations using the MessageAgent.

Interesting article. :) It is always good to see people getting excited about the technology and really digging in. However, I want to make sure that all readers of this article understand that these are undocumented and unsupported apis which are subject to changes whenever we want since they are only internal. What is described above could be cleaned up a bit, but is pretty accurate overall. However, you obviously cannot use this in a real system, since when you called support and told them what you were doing ... :) However, if you would like to extend what you are doing here into something which is of questionable supportability (it is not currently supported but it would be interesting for us to consider supporting), you would right a custom out-of-process adapter which takes a message containing the name of the orchestration you want to start. Then a custom pipeline with a custom disassembler would use the name to construct the message as you did above to activate the orchestration and send it on. The messaging engine would interact with the internal messageagent apis and would start the orchestration. The out-of-process adapter could be called from your custom code. I think we have a sample somewhere. Again, I doubt this is supported, but it is a lot closer and is something you could ask us to consider supporting, I guess. I don't know. Other than that ... nice article. :) Glad to see you have such a deep interest. Have a happy new year.

Lee
Monday, January 10, 2005 5:41 PM by Jesus Rodriguez

# re: Starting BizTalk Server 2004 orchestrations using the MessageAgent.

I think that Lee is absolutely right. The examples in the article are illustrative about BTS 2004 interiorities. When you go to the productions enviroments you must consider other solutions.
However I still there are a set of developers that go inside the technologies and aren't merely consumers. I think that maybe this developers can find this article helpful.
I think that the solutions that Lee suggests should be considered in some scenarios. It is always helpful to have this kind of comments.

Thanks


Tuesday, January 11, 2005 12:15 AM by Erik Leaseburg

# re: Starting BizTalk Server 2004 orchestrations using the MessageAgent.

Very good and deep under-the-covers how-to article. But as Lee mentioned in his response, developers should consider other supported options such as using the SubmitDirect adapter SDK sample or writing your own out-of-process adapter.
Wednesday, January 12, 2005 8:52 AM by TrackBack

# BizTalk Server 2004: Invoking orchestrations in custom code

Jes
Wednesday, January 12, 2005 10:49 AM by Patrick Wellink

# re: Starting BizTalk Server 2004 orchestrations using the MessageAgent.

Lee believes there is a sample, but i KNOW there is a sample see the SubmitDirect sample in the adapter directory of the SDK.

It does roughly the same as this code...... Only a lot simpeler....

for (int i = 0; i < args.Length; i++)
msgs[i] = btm.CreateMessageFromString(submitURI, args[i]);
// Submit the messages
btm.SubmitMessages(msgs);

For sure you can create an XML docuemnt that contains all the parameters you need....
Create the right subscription and you are ready.....

Also a good solution cause you don't have to code anything.... just configuration.....



Wednesday, January 12, 2005 11:16 AM by Jesus Rodriguez

# re: Starting BizTalk Server 2004 orchestrations using the MessageAgent.

I know the SubmitDirect adapter. I use variants of this example many times. But I repeat that the post is only illustrative about the MessageAgent funtionality. I don't see anything wrong about adquire knowledge about the interiorities of a technology or product.
By the way to use the SubmitDirect your orchestrations must create a subscription using the SubmitDirect adapter and this isn't always possible right??.
The idea of the article is to explain the capabilities of the MessageAgent to those developers that want to know the low-level BTS infrastructure.

Thanks for your comment
Jesús
Tuesday, February 01, 2005 12:09 PM by Peter

# re: Starting BizTalk Server 2004 orchestrations using the MessageAgent.

Very interesting article, i have tried to operate this code but i don't have no response, not error, no confirmation, nothing

Do you have a sample project that you can post?

Thanks Peter
Monday, December 11, 2006 6:12 AM by Olmo

# re: Starting BizTalk Server 2004 orchestrations using the MessageAgent.

Hola Jesus. Estoy intentando implementar una prueba de concepto para llamar a una orquestación u otra dependiendo del resultado de una regla de negocio.

He intentado seguir tus indicaciones pero hay cosas que no entiendo. Por ejemplo, ¿es necesario importarse las dll de esa manera? ¿Porque no haces  una referencia normal?

¿Hay alguna forma de hacer que funcione el intellisense para estos espacios de nommbres?.

En resumidas cuentas ¿podrías mandarme el proyecto de ejemplo que explicas aqui?

mi correo es olmo.ramiro@gmail.com

Muchas gracias y un saludo.

Saturday, July 28, 2007 10:16 AM by chnking

# 深入biztalk中各种端口绑定方式(七)-- 直接绑定之Partner Orchestration

Biztalk中orchestration端口的绑定方式多种多样,有以后指定、现在指定、动态、直接绑定四种大的绑定方式,直接绑定中又分为MessageBox、Self Correlating、Partner Orchestration等三种模式,让人看得有些眼花缭乱。本文试图从一个比较全面、比较深入的视角审视biztalk中的各种端口绑定方式,在描述每种绑定方式的使用方法的同时,试图探究每种绑定方式在biztalk内部的实现方法,看看这么多绑定方式之间是不是在底层的实现机制有哪些共通之处。

Tuesday, September 25, 2007 4:37 PM by Jeff King

# re: Starting BizTalk Server 2004 orchestrations using the MessageAgent.

This post is awesome!  I'm trying to only use a small subset to programmatically push messages to the MB from within the Decode stage of a receive pipeline component.  I'm running into a problem when calling IBTMessageAgent.RegisterAsPublisher(String groupName, String applicationName), but I keep getting an exception with the message: Value does not fall within expected range.  What does 'groupName' need to be?  What does 'applicationName' need to be?  I can't find any  documentation on this method, so I've tried all types of combinations.  Any help on this would be greatly appreciated.

Thanks,

-jk

# &nbsp; A loosely coupled Scatter-and-Gather implementation In BizTalk 2006&nbsp;by&nbsp;.RICHARD

Pingback from  &nbsp; A loosely coupled Scatter-and-Gather implementation In BizTalk 2006&nbsp;by&nbsp;.RICHARD

Tuesday, March 16, 2010 7:54 AM by hwsite

# re: Starting BizTalk Server 2004 orchestrations using the MessageAgent.

Hi this is hwsite

The idea of the article is to explain the capabilities of the MessageAgent to those developers that want to know the low-level BTS infrastructure.

# A loosely coupled Scatter-and-Gather implementation In BizTalk 2006 | Gobbledygooks by Richard Hallgren

Pingback from  A loosely coupled Scatter-and-Gather implementation In BizTalk 2006 | Gobbledygooks by Richard Hallgren

Leave a Comment

(required) 
(required) 
(optional)
(required)