Let me describe again, the problem I was having. I’m consuming a WCF service from BizTalk using the ESB Toolkit. And, when an exception occurs, I am unable to handle the error elegantly. In fact, the full error is relayed back to the consumer of my service without me being able to stop it.
The Solution – A Generic Orchestration
During my initial introduction to the ESB Toolkit I was looking at the samples it came with. In particular, I was diving into the Scatter-Gather example and discovered an orchestration that does much of what I we will be looking at today. It uses the resolvers of the calling itinerary to Scatter messages to other services. So, from this example I though to myself, let me see if I can write a generic itinerary processing orchestration that I can use anywhere. As it turns out, you can.
This is how I built the solution…
Step 1 – Helper Classes
I have built a MessageFactory helper class that creates SOAP Faults for when we trap exceptions in our orchestration. It uses the ResponseBodyWriter class from the part I to build the SOAP Fault. Here’s how:
StringBuilder stringBuilder = new StringBuilder();
using (XmlWriter xmlWriter = XmlWriter.Create(stringBuilder))
{
using (XmlDictionaryWriter xmlDictionaryWriter =
XmlDictionaryWriter.CreateDictionaryWriter(xmlWriter))
{
ResponseBodyWriter bodyWriter = new ResponseBodyWriter("soap:Client",
faultString,
action,
errorMessage,
exception);
Trace.WriteLine("[ESB MessageFactory] Creating SOAP Fault");
bodyWriter.WriteBodyContents(xmlDictionaryWriter);
xmlDictionaryWriter.Flush();
}
}
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(stringBuilder.ToString());
return xmlDocument;
This helper also has a method that promotes message properties for us. We use this primarily to ensure that the message type is promoted when we send messages from our orchestration. We use the standard XMLReceive pipeline to do the job as described in this article by Ronald Lokers here.
public static void PromoteMessageProperties(XLANGMessage message)
{
Trace.WriteLine("[ESB MessageFactory] Promoting message properties");
ReceivePipelineOutputMessages pipelineMessages = XLANGPipelineManager.ExecuteReceivePipeline(
typeof(Microsoft.BizTalk.DefaultPipelines.XMLReceive),
message);
pipelineMessages.MoveNext();
pipelineMessages.GetCurrent(message);
}
Step 2 – Create the Orchestration
Note: My sample is built using BizTalk 2009, Visual Studio 2008 and the ESB Toolkit 2.0.
Orchestration overview
The orchestration grabs a message from the message box and loads the associated itinerary from the context properties of the message. From the itinerary, it finds all the resolvers of the current itinerary service. It loops through each resolver performing their actions until either there are no more resolvers or an error occurs.
The logic I have used is based on there being a result message from the last resolver operation. So, for the first resolver to work, I copy the inbound message to my temporary result message. This concept is also used after a transform step. The copying of messages like this is not something I like doing as it adds an overhead of duplicate messages and all the performance problems there of. However, it’s a cost taken at the benefit of having a generic solution.
1 – Adding references
You’ll need to add the following references to your assembly:
- Microsoft.XLANGs.Pipeline
- Microsoft.Practices.ESB.Adapter
- Microsoft.Practices.ESB.ExceptionHandling
- Microsoft.Practices.ESB.ExceptionHandling.Schemas.Faults
- Microsoft.Practices.ESB.Itinerary
- Microsoft.Practices.ESB.Itinerary.PipelineComponents
- Microsoft.Practices.ESB.Itinerary.Schemas
- Microsoft.Practices.ESB.PipelineComponents
- Microsoft.Practices.ESB.Resolver
- Microsoft.Practices.ESB.Transform
2 – Messages and variables
Here are the messages I have used:
Message
Name<o:p>
| Type<o:p>
| Use<o:p>
|
mInboundMessage<o:p>
| Microsoft.XLANGs.BaseTypes.Any<o:p>
| The original message received<o:p>
|
mOutboundMessage<o:p>
| Microsoft.XLANGs.BaseTypes.Any<o:p>
| The final message returned to the
itinerary<o:p>
|
mSendMessage<o:p>
| Microsoft.XLANGs.BaseTypes.Any<o:p>
| The message that gets sent to
sub-services<o:p>
|
mReplyMessage<o:p>
| Microsoft.XLANGs.BaseTypes.Any<o:p>
| The response message from a sub-service<o:p>
|
And these are my variables:
Variable
Name<o:p>
| Type<o:p>
| Use<o:p>
|
vItinerary<o:p>
| Microsoft.Practices.ESB.Itinerary.SerializableItineraryWrapper<o:p>
| Holds the itinerary<o:p>
|
vItineraryStep<o:p>
| Microsoft.Practices.ESB.Itinerary.SerializableItineraryStepWrapper<o:p>
| Holds the current service/step in the
itinerary<o:p>
|
vResolvers<o:p>
| Microsoft.Practices.ESB.Itinerary.ResolverCollection<o:p>
| Holds all the resolvers in the current
step<o:p>
|
vResolver<o:p>
| System.String<o:p>
| The current resolver being executed<o:p>
|
vResolverDictionary<o:p>
| Microsoft.Practices.ESB.Resolver.ResolverDictionary<o:p>
| The settings from the resolver<o:p>
|
vResolverNumber<o:p>
| System.Int32<o:p>
| For looping<o:p>
|
vTransformType<o:p>
| System.String<o:p>
| For transforming messages<o:p>
|
vMapCLRType<o:p>
| System.Type<o:p>
|
vTransportType<o:p>
| System.String<o:p>
| For message routing<o:p>
|
vTransportLocation<o:p>
| System.String<o:p>
|
3 - Receive-Send Port and shape
The orchestration has a single Receive-Send port with both receive and send message types being of type Microsoft.XLANGs.BaseTypes.Any. It is a direct port so that it uses the message box to receive messages.
Note: all the messages in the orchestration are of xs:any type because it means the orchestration can be generic and process messages of any type.
4 – Receive shape
The receive shape has a filter expression so that it grabs the correct messages from the message box. Here is what I have used.
(Microsoft.Practices.ESB.Itinerary.Schemas.ServiceName == "ProcessItinerary") &&
(Microsoft.Practices.ESB.Itinerary.Schemas.ServiceType == "Orchestration") &&
(Microsoft.Practices.ESB.Itinerary.Schemas.ServiceState == "Pending") &&
(Microsoft.Practices.ESB.Itinerary.Schemas.IsRequestResponse == true)
Note: the ServiceName is configured in the itinerary by selecting the service from a dropdown. The dropdown is populated from the esb.config file. So, the name you use in the esb.config file must be the same as the filter value above.
Here is what the start of the orchestration looks like:
5 – Load the itinerary step
When we have received a message, we load the itinerary that comes with it in the context properties. This is how:
vItinerary = new Microsoft.Practices.ESB.Itinerary.SerializableItineraryWrapper();
vItineraryStep = new Microsoft.Practices.ESB.Itinerary.SerializableItineraryStepWrapper();
vItinerary.Itinerary = Microsoft.Practices.ESB.Itinerary.ItineraryOMFactory.Create(mInboundMessage);
vItineraryStep.ItineraryStep = vItinerary.Itinerary.GetItineraryStep(mInboundMessage);
6 – Get all the resolvers
From the itinerary step, we grab all the resolvers:
vResolvers = vItineraryStep.ItineraryStep.ResolverCollection;
vResolverNumber = 0;
These steps look like this :
Note: If we don’t have any resolvers, I throw an exception which is converted into a SOAP Fault later.
7 – Create our first result message
We create a copy of the inbound message so that we can run our first resolver a bit later.
mReplyMessage = mInboundMessage;
mReplyMessage(*) = mInboundMessage(*);
8 – Execute the resolver
Then we loop through each resolver and perform its tasks. The first step is resolve the resolver and to then grab it’s settings. Here’s how:
vResolver = vResolvers.Current;
vResolverNumber = vResolverNumber + 1;
vResolverDictionary = Microsoft.Practices.ESB.Resolver.ResolverMgr.Resolve(mInboundMessage, vResolver);
vTransformType = vResolverDictionary.Item("Resolver.TransformType");
vTransportLocation = vResolverDictionary.Item("Resolver.TransportLocation");
vTransportType = vResolverDictionary.Item("Resolver.TransportType");
9 – Perform transforms
If the resolver has a transform type, then perform the transform:
vMapCLRType = System.Type.GetType(vTransformType);
transform(mSendMessage) = vMapCLRType(mReplyMessage);
mSendMessage(*) = mReplyMessage(*);
Note: I copy the result of the from the transform to the reply message so that it is available to the next resolver.
10 – Route the message
When the transport type and location are known, we will send the message to the sub-service.
mSendMessage = mReplyMessage;
mSendMessage(*) = mReplyMessage(*);
Microsoft.Practices.ESB.Adapter.AdapterMgr.SetEndpoint(vResolverDictionary, mSendMessage);
ESB.ExceptionHandling.ServiceModel.MessageFactory.PromoteMessageProperties(mSendMessage);
SendMessage(Microsoft.XLANGs.BaseTypes.Address) = vTransportLocation;
SendMessage(Microsoft.XLANGs.BaseTypes.TransportType) = vTransportType;
11 – Send port details
We use a dynamic Request-Response port to send the message and receive a response. This way, the port can be configured by the itinerary
12 – Advance the itinerary
When there are no more resolvers to process, we create our final result message and advance the itinerary to the next service. Here is how:
mOutboundMessage = mReplyMessage;
mOutboundMessage(*) = mReplyMessage(*);
mOutboundMessage(Microsoft.Practices.ESB.Itinerary.Schemas.IsRequestResponse) = mInboundMessage(Microsoft.Practices.ESB.Itinerary.Schemas.IsRequestResponse);
mOutboundMessage(Microsoft.Practices.ESB.Itinerary.Schemas.ServiceName) = mInboundMessage(Microsoft.Practices.ESB.Itinerary.Schemas.ServiceName);
mOutboundMessage(Microsoft.Practices.ESB.Itinerary.Schemas.ServiceType) = mInboundMessage(Microsoft.Practices.ESB.Itinerary.Schemas.ServiceType);
mOutboundMessage(Microsoft.Practices.ESB.Itinerary.Schemas.ServiceState) = "Complete";
ESB.ExceptionHandling.ServiceModel.MessageFactory.PromoteMessageProperties(mOutboundMessage);
vItinerary.Itinerary.Advance(mOutboundMessage, vItineraryStep.ItineraryStep);
vItinerary.Itinerary.Write(mOutboundMessage);
13 – Return the response
The final step is to return the response message to the message box to be processed by the next itinerary service.
14 – Exception Handlers
To catch exceptions, I use a scope with exception handlers. In the exception handers I create a SOAP Fault message from the exception by using my message helper class mentioned above.
vResolverNumber = vResolvers.Count;
mReplyMessage = ESB.ExceptionHandling.ServiceModel.MessageFactory.CreateSoapFaultMessage(exSoapException);
mReplyMessage(*) = mInboundMessage(*);
Step 3 – Deploying The Orchestration
To deploy, just compile the assembly, add it to the GAC and deploy it to BizTalk.
Configure the send port configuration in the BizTalk console to use the standard XMLTransmit and XMLReceive. It won’t be executing any steps in the itinerary so don’t use any of the ESB Toolkit pipelines.
Now, to make the orchestration available to the ESB Toolkit and Visual Studio, open the esb.config file and add a new Itinerary Service as below. You can find the esb.config file here:
%Program Files%\Microsoft BizTalk ESB Toolkit 2.0\esb.config
<configuration>
<esb>
<itineraryServices>
<itineraryService id="{your own GUID>}" name="{service name}"
type="{Full type name of orchestration}"
scope="Orchestration"
stage="None" />
</itineraryServices>
</esb>
</configuration>
Here is what I have used for my sample.
<itineraryService
id="3CCA3815-E5F3-47a7-B864-7644E1A39087"
name="ProcessItinerary"
type="ESB.ExceptionHandling.ProcessItinerary, ESB.ExceptionHandling,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=a496c9267b296339"
scope="Orchestration"
stage="None" />
Remember to restart IIS and all BizTalk hosts to pick up the config changes.
Step 4 – Building your itinerary
I took the itinerary from part I and changed it to use my orchestration rather than an off-ramp. Here is what looks like now. The change is that we have an orchestration extender with a single resolver that will route our message for us.
The “Route to WCF Service” resolver has been configured to use a WCF-WSHttp transport type. Here are the settings I have used. You can use the BRE as I have for my customer but for this example I’m going static:
Property<o:p>
| Value<o:p>
|
Resolver Implementation<o:p>
| Static Resolver Extension<o:p>
|
Message-Exchange Pattern<o:p>
| Two-Way<o:p>
|
Transport Name<o:p>
| WCF-WSHttp<o:p>
|
Transport Location<o:p>
| http://nowhere/nowhere.svc<o:p>
|
Action<o:p>
| AnAction<o:p>
|
Target Namespace<o:p>
| http://namespace.org<o:p>
|
Endpoint Configuration<o:p>
| CloseTimeout=00:00:10&OpenTimeout=00:00:10&SecurityMode=None&SendTimeout=00:00:10<o:p>
|
I have set the endpoint timeouts to be low so that I can test the orchestration without waiting the default timeout for an error to occur.
Once you have your itinerary, you can deploy it as normal and you’re ready to test.
Step 5 – Testing
I have been using soapUI to test my service and the result I get is below. It’s in the format on my response schema because the last step of my itinerary transforms the SOAP Fault I get from my orchestration for me.
<ns0:Response xmlns:ns0="http://ESB.ExceptionHandling.ResponseMessage">
<ResponseElement1>An unexpected error occured</ResponseElement1>
<ResponseElement2>System.TimeoutException: The HTTP request to 'http://nowhere/nowhere.svc' has exceeded the allotted timeout of 00:00:09.9950000. The time allotted to this operation may have been a portion of a longer timeout.</ResponseElement2>
<ResponseElement3/>
</ns0:Response>
Note: at the moment my sample leaves a suspended message in BizTalk. I have overcome this in the past but haven’t had the time to resolve this error yet. When I do, I’ll update the sample.
Points of Interest
The strength of the ESB Toolkit does not only lie in the realms of the BizTalk pipeline. It can also be extended to orchestrations with very little effort.
However, I have used this generic orchestration to create services that don’t consume other services but simply transform messages for me. I can do this very quickly by building the right itinerary and deployment is a breeze.