In this section, we will see how to create our own custom itinerary services, both on their messaging and orchestration flavors.
As we mentioned before, the messaging services are implemented as specific classes that will be invoked by the ESB Dispatcher pipeline components.
In order to implement one of those, we need to create a class that implements the IMessagingService
interface. This interface defines two properties (Name
and SupportsDisassemble
) and two methods (Execute
and ShouldAdvanceStep
) that have to be implemented:
Name
: It is the name of the service that will be used to reference the service in the itinerary. It will be the one that will identify the service once registered in the ESB configuration file.SupportsDisassemble
: It specifies if the component supports disassemble and the execution of multiple resolvers.Execute
: It is the method where the processing of the message will take place.ShouldAdvanceStep
: It instructs the Dispatcher to advance the itinerary to the next step or not once the component has finalized its processing.We will now show the implementation of the Execute
method for an itinerary service that will perform some compression logic on the body of the message if the resolver used by the service says so:
publicMicrosoft.BizTalk.Message.Interop.IBaseMessage Execute(Microsoft.BizTalk.Component.Interop.IPipelineContext context, Microsoft.BizTalk.Message.Interop.IBaseMessage msg, string resolverString, IItineraryStep step) { if (context == null) thrownewArgumentNullException("context"); if (msg == null) thrownewArgumentNullException("msg"); if (string.IsNullOrEmpty(resolverString)) thrownewArgumentException(Properties.Resources.ArgumentStringRequired, "resolverString"); ResolverInfo info = ResolverMgr.GetResolverInfo(ResolutionType.Endpoint, resolverString); if (!info.Success) thrownewException(Properties.Resources.ResolverStringInvalid, resolverString); //Resolve if the message is meant to be compressed Dictionary<string, string> resolverDictionary = ResolverMgr.Resolve(info, msg, context); if (!string.IsNullOrEmpty(resolverDictionary["Acme.RequiresCompression"])) { IBaseMessagePart bodyPart = msg.BodyPart; string tmpString = ""; if (bodyPart != null) { try { System.IO.StreamReader sr = newSystem.IO.StreamReader(bodyPart.Data); tmpString = sr.ReadToEnd(); tmpString = Acme.CompressContent(tmpString); System.IO.MemoryStream strm = newSystem.IO.MemoryStream(ASCIIEncoding.Default.GetBytes(tmpString)); strm.Position = 0; bodyPart.Data = strm; context.ResourceTracker.AddResource(strm); } catch (System.Exception ex) { throw ex; } } } return msg; }
Our resolver could be any kind of resolver that returns the RequiresCompression
items as part of its resolution result. For example, it could be a business rule that depending on certain properties of the message decides if the message is to be compressed or not.
Once our custom itinerary service is compiled, we will need to deploy it to our BizTalk environment:
ESB.Config
file (located in the ESB Toolkit installation path), we will add a new itineraryService
entry on the ItineraryServices
section, being its attributes:ID
: A guide for the service.Name
: The name returned by the Name
property of the component implemented.Type
: The fully qualified name of the class that implements the service.Scope
: It must be Messaging
, as this is a messaging service.Stage
: The stages of an itinerary where the service can run. It can be OnRampReceive
, OnRampSend
, OffRampSend
, OffRampReceive
, AllSend
, AllReceive
, or All
.The following screenshot is an example of the ESB.Config
file:
Now we will create the same compression service that we created in the previous section, but in the orchestration itinerary service flavor.
The message processing lifecycle of this type of services is pretty much the same as in the messaging services (receive, process, mark step as complete, and send) but with slight differences. Let's start designing our orchestration, and we will highlight those differences as we move on.
In the messaging services, the message is received by the Dispatcher pipeline component as the message flows through the pipeline, but the orchestration services receive them from the BizTalk message box, and so it'll need to subscribe to the messages they are meant to process. The orchestration will subscribe to those messages that match the following context properties filter:
Microsoft.Practices.ESB.Itinerary.Schemas.ServiceName
property has a value that is the name of the service implemented by the orchestration.Microsoft.Practices.ESB.Itinerary.Schemas.ServiceState
property value is Pending
.Microsoft.Practices.ESB.Itinerary.Schemas.ServiceType
property value is Orchestration
.We will drop our port shape into the orchestration design surface. The port name, port type name, and operation names should follow the naming conventions established in your solution for this type of artifact (and should be consistent across the different itinerary services you implement). We will use a direct port binding, as we will receive the messages by means of the specific filters just mentioned.
The port can be either one way or request/response (depending on our service design). The only differences if we use a request/response are:
We will now create the inbound and outbound messages for our orchestration. The type of messages sent and received by our orchestration itinerary services will be always of System.Xml.XmlDocument
type.
Once we have the port and the messages created, we will add the corresponding receive shape. The receive shape will receive the inbound message that we just created. Finally, we will set up the receive shape filter as described previously, and connect it to the receive operation of the receive port.
The processing of the message within the orchestration has three principal steps: retrieve the current itinerary state, executing the actual service process, and advancing the itinerary to the next step.
This process will give us access to the current state of the itinerary (stored in the message context) the message is flowing through and to the current itinerary step itself.
The most important information that we will be able to retrieve from the itinerary step are the resolvers that are configured for that step in the itinerary, in order to execute them (if necessary) for our later processing.
Firstly, we will create the variables that we need to store the information to be retrieved from the itinerary:
itinerary
: It is the variable of Microsoft.Practices.ESB.Itinerary.SerializableItineraryWrapper
type that will be assigned the itinerary metadata.itineraryStep
: It is the variable of Microsoft.Practices.ESB.Itinerary.SerializableItineraryStepWrapper
type that will be assigned the current itinerary step information.resolverDictionary
: It is the variable of Microsoft.Practices.ESB.Resolver.ResolverDictionary
type that will store the results from the resolution.Once we have our variables defined, we will drop an expression shape into the design surface, as you can see in the following image. In that expression shape, we will retrieve the itinerary metadata, the resolvers for the current itinerary step, and then, we will execute the resolution.
//Instantiate the itinerary and itineraryStep classes that will be used to hold the itinerary the message is going through and the current itinerary step itinerary = new Microsoft.Practices.ESB.Itinerary.SerializableItineraryWrapper(); itineraryStep = new Microsoft.Practices.ESB.Itinerary.SerializableItineraryStepWrapper(); itinerary.Itinerary = Microsoft.Practices.ESB.Itinerary.ItineraryOMFactory.Create(InboundMessage); itineraryStep.ItineraryStep = itinerary.Itinerary.GetItineraryStep(InboundMessage); //Execute the resolver this service is meant to use to resolve the information that might drive the execution of the service resolverDictionary = Microsoft.Practices.ESB.Resolver.ResolverMgr.Resolve(OutboundMessage, itineraryStep.ItineraryStep.ResolverCollection[0]); System.Diagnostics.Trace.WriteLine("ServiceName: " + itineraryStep.ItineraryStep.ServiceName); System.Diagnostics.Trace.WriteLine("ServiceType: " + System.Convert.ToString(itineraryStep.ItineraryStep.ServiceType));
Next, we will add a decision shape. Depending on the resolution result that tells us to execute the compression or not, we will construct the outbound message with the compressed inbound message, or with the unmodified inbound message.
We will use the following sentence in the Yes
branch of the decision shape:
!System.String.IsNullOrEmpty(resolverDictionary.Item("Acme.RequiresCompression"))
In order to let the message be processed by downstream services, we need to mark the current itinerary step as completed. Otherwise, when the message is published back into the message box, our orchestration will continue picking up and processing the message in an infinite loop.
We just need an additional expression shape to execute the corresponding itinerary helper classes that will do the magic.
System.Diagnostics.Trace.WriteLine(" BEGIN - Advance Itinerary"); // Call the Itinerary helper to advance to the next step itinerary.Itinerary.Advance(OutboundMessage, itineraryStep.ItineraryStep); itinerary.Itinerary.Write(OutboundMessage); System.Diagnostics.Trace.WriteLine(" FINISH - Advance Itinerary");
Finally, we will send the outbound message on its way back to the message box for further processing.
We will create a direct binding send port and the corresponding send shape. Additionally, we will create a correlation set and its corresponding correlation set type, which will be initialized in the send shape to promote the context properties needed for itinerary processing. We will name the correlation set type as itineraryAdvance
and the correlation properties will be:
Microsoft.Practices.ESB.Itinerary.Schemas.IsRequestResponse
Microsoft.Practices.ESB.Itinerary.Schemas.ServiceName
Microsoft.Practices.ESB.Itinerary.Schemas.ServiceState
Microsoft.Practices.ESB.Itinerary.Schemas.ServiceType
In case our service is a request/response one, we will need an additional correlation set and correlation set type to promote the context properties that will make the message routed to the request/response port that originally received the initial message. The correlation set type name will be itineraryRequestResponse
and the correlation properties will be:
BTS.CorrelationToken
BTS.EpmRRCorrelationToken
BTS.IsRequestResponse
BTS.ReqRespTransmitPipelineID
BTS.RouteDirectToTP
18.191.68.18