Introduction
This article talks about a simple method to invoke a web service function from BizTalk in an asynchronous manner. It can be done using 2 orchestrations: Parent and Child orchestrations and using Start Orchestration shape. Also explained is an error handling and retry mechanism in case the web service we are invoking is down/unavailable. The retry mechanism is also suggested to be done using a scheduled service that looks for suspended orchestration instances and resumes these instances whenever the web service is up and running again. This will ensure that the web service calls which failed due to web service issues are executed to successful completion when the web service becomes operational at a later time.
Background
Knowledge of BizTalk, .NET, will be helpful
Developing our project
We used orchestration to invoke a web method in an asynchronous manner. There are several methods to call a web service in BizTalk, but there is no straightforward method to call it in an asynch mode. For calling a web service method in an asynchronous manner, we used two orchestrations. BizTalk supports the protocols SOAP, WCF-BasicHTTP, WCF-WSHTTP, WCF-Custom etc for calling web methods. Ours is an asmx web service with a simple method that takes an input string and concatenates "Hello World" and returns the concatenated string as response.
We chose WCF-Custom as it is the most flexible protocol configuration in BizTalk that has various options which also allows to be modified later as per future needs.
Below are the overall steps to make our BizTalk sample ready.
1. For calling any web service using WCF-... protocols, first thing we need to do is to Add Generated Items from Visual Studio. Choose the Consume WCF service and give the wsdl location of our web service.
Eg. http://<webservice url>/Helloworld.asmx?wsdl
2. On finishing of the wizard, it will generate an orchestration with the necessary request and response types, schemas for request/response and XML files for creating the send port using WCF-Custom/WCF-BasicHTTP protocols.
3. We name the orchestration as BTWebService.odx which will be the child orchestration, that makes the web service call and returns the response back to the parent using the common port that is shared between parent and child.
4. In the BTWebService orchestration, we need to create a configured port that uses the existing port type auto-generated by the Wizard. It should be created as: Send a request/Receive a response type of port to make the web service method call.
5. Create another orchestration that will be the parent(CallingOrch.odx) which will call the BTWebService orchestration.
6. Create a one way port type in Parent which will be used for sending response back from Child to Parent. A port each in parent and child has to be created(lets use the same name ParentChildCommon) using this port type. The ports will be one way-ports, which has to be configured with Direction: 'Send' in child and 'Receive' in parent
Common port in Parent:
Common port in Child:
7. In the child orchestration(BTWebService.odx) create 2 orchestration parameters in the orchestration view:
Message of SOAP request type, Port which is one way port type we created.
8. In the parent orchestration: Create a receive port, receive shape to receive the request message from a file folder. The receive message should have the multi-part message type HelloWorldSoapIn(created by the wizard) which denotes the request message.
9. Add a Start Orchestration shape to invoke the BTWebService.odx orchestration. Parent orchestration will invoke the BTWebService.odx with 2 parameters: the request message and the common port(which is shared by the parent and child). The BTWebService.odx invokes the web service using the send port that we created earlier and gets the response back and sends it to parent orchestration using the common port.
10. Add a receive and send shape in the parent after the start orchestration. The parent orchestration receives the response from child in the common port and later sends the response to a folder using another Send port.
11. Deploy the project in the admin console and configure it as follows.
Parent Orchestration:
Child Orchestration:
Configuration in BizTalk Admin console
Once the project with 2 orchestrations is deployed, we need to do the following configuration in the admin console.
1. Create a recieve port/location and send port for the parent orchestration and associate them with the logical ports.
2. In the receive location, choose XMLReceive pipeline and set the DocumentSpecNames property as:
TestWebService.BTWebService_tempuri_org+HelloWorld,TestWebService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a530ecc5d7ba9a86
Note that the root node has to be given after the schema name with a + sign.
3. Import the BTWebService_Custom.BindingInfo.xml in the application so that 2 WCF-Custom send ports are created - one with SOAP 1.1 and another with SOAP 1.2
4. Lets configure the orchestration with SOAP 1.2 send port. Set the XMLReceive pipeline in the WCF-Custom SOAP 1.2 send port. Give DocumentSpecNames property as:
TestWebService.BTWebService_tempuri_org+HelloWorldResponse,TestWebService,Version=1.0.0.0,Culture=neutral,PublicKeyToken=a530ecc5d7ba9a86
This property is not automatically set by the wizard and we need to give this property in order to read the response from the web service.
5. Start the application
Testing the application
1. Generate an instance of the schema file: BTWebService_tempuri_org.xsd which will generate the following XML that is the request to the web service:
<ns0:HelloWorld xmlns:ns0="http://tempuri.org/">
<ns0:input>srini</ns0:input>
</ns0:HelloWorld>
2. Drop the XML file in the receive location configured for the parent orchestration.
3. The XML request will be passed to the child and the child invokes the web service. The response should be sent back through the common port in parent.
4. The received response is written into the output location.
Handling scenario when web service is down
We saw the normal case when everything is working correctly. We will make few enhancements to the BTWebService.odx orchestration to handle the case when web service is down. We will introduce a loop and variables ErrorCount and Result. Add a scope to the orchestration covering the shapes that does web service calls and receivng the response. Have 2 exception blocks in the scope to catch the SOAP exception and General Exception in the same order. Inside the general exception block, increment the ErrorCount. Add a decision shape that checks if the ErrorCount >=3 && Result==0 and in this case it branches to the error handler block. If error is encountered, the web service call is retried for 3 times and after that it sends an error email and suspends the orchestration.
Modified Orchestration with error handling:
Variables: ErrorCount, Result
Scope: WS Call Proc
Exception Blocks:SOAP Exception Handler
Base System Exception Handler
In case, the execution is successful it sends the result back to parent using the ParentChildCommon port as seen above and both the orchestration will be terminated normally.
Resume execution when web service is available again
In case of suspended orchestration, assume that after some time the web service comes up and we would like to retry calling the web service and complete the calling process. It can be done manually in the Admin Console by finding the suspended BTWebService.odx orchestration instances and doing a resume. Observe that the parent orchestration CallingOrch.odx is in dehydrated state, as it is wating for the response from the child.
View of the orchestrations in the admin console:
When web service is up, we want BTWebService.odx should retry the orchestration and start execution from the place where it was suspended. The shapes right after the suspend shapes will start execution. In this case during resume, we want the orchestration to start the retry option for 3 more times. So before suspending, we reset the ErrorCount and Result back to 0 using an expression shape so that while the orchestration is resumed, it will start executing the loop from the start. If we resume when the web service is up, it should trigger the web service call and execute successfully and response should be received. Both the orchestrations should be terminated.
Suspend shape and resetting the variables before suspend:
Sending error email using SMTP Port:
In case the error count exceeds by 3, an email is sent before suspending the orchestration.
The resuming of the orchestration can be done using code. We can make this code to run as a service. The service can be configured to run frequently every 2 or 5 mins to provide quick error recovery. The service uses the orchestration name and check if there are any suspended instances. If there are any instances, it can resume these instances immediately. This approach will avoid missing processing of any web service call as the calls will automatically be made by the service when the web service becomes available. This will save the administrators from doing manual search and resume.
The following is the code that uses the orchestration name and finds all the suspended(resumable) instances of this particular orchestration and then resumes them via code.
static void Main(string[] args)
{
ResumeSvcInstByOrchestrationName("BTWebServiceClient");
}
public static void ResumeSvcInstByOrchestrationName(string strOrchestrationName)
{
try
{
const uint SERVICE_CLASS_ORCHESTRATION = 1;
const uint SERVICE_STATUS_SUSPENDED_RESUMABLE = 4;
const uint REGULAR_RESUME_MODE = 1;
string strWQL = string.Format(
"SELECT * FROM MSBTS_ServiceInstance WHERE ServiceClass = {0} AND ServiceStatus = {1} AND ServiceName = \"{2}\"",
SERVICE_CLASS_ORCHESTRATION.ToString(), SERVICE_STATUS_SUSPENDED_RESUMABLE.ToString(), strOrchestrationName);
ManagementObjectSearcher searcherServiceInstance = new ManagementObjectSearcher(new ManagementScope("root\\MicrosoftBizTalkServer"), new WqlObjectQuery(strWQL), null);
int nNumSvcInstFound = searcherServiceInstance.Get().Count;
if (nNumSvcInstFound > 0)
{
string[] InstIdList = new string[nNumSvcInstFound];
string[] ClassIdList = new string[nNumSvcInstFound];
string[] TypeIdList = new string[nNumSvcInstFound];
string strHost = string.Empty;
string strReport = string.Empty;
int i = 0;
foreach (ManagementObject objServiceInstance in searcherServiceInstance.Get())
{
if (strHost == string.Empty)
strHost = objServiceInstance["HostName"].ToString();
try
{
ClassIdList[i] = objServiceInstance["ServiceClassId"].ToString();
string test = objServiceInstance["ServiceClass"].ToString();
test = objServiceInstance["ServiceName"].ToString();
test = objServiceInstance["ServiceStatus"].ToString();
}
catch (Exception ex)
{
}
TypeIdList[i] = objServiceInstance["ServiceTypeId"].ToString();
InstIdList[i] = objServiceInstance["InstanceID"].ToString();
strReport += string.Format(" {0}\n", objServiceInstance["InstanceID"].ToString());
i++;
}
string strHostQueueFullPath = string.Format("root\\MicrosoftBizTalkServer:MSBTS_HostQueue.HostName=\"{0}\"", strHost);
ManagementObject objHostQueue = new ManagementObject(strHostQueueFullPath);
objHostQueue.InvokeMethod("ResumeServiceInstancesByID",
new object[] { ClassIdList, TypeIdList, InstIdList, REGULAR_RESUME_MODE }
);
Console.WriteLine(string.Format("Service instances with the following service instance IDs have been resumed:\n{0}", strReport));
}
else
{
System.Console.WriteLine(string.Format("There is no suspended (resumable) service instance found for orchestration '{0}'.", strOrchestrationName));
}
}
catch (Exception excep)
{
Console.WriteLine("Error: " + excep.Message);
}
}
Download the source code from the link at the top of the article.
Learnings
Hope I have illustrated adequately the various aspects of this solution.
Some of the learnings while doing this sample:
- Root node should be given after the schema name with + sign as above for the DocumentSpecNames property of the XMLReceive pipeline in order to read the XML file that corresponds to the specific root node in a multi-part schema.
- Failure cases of external services can be handled using orchestration by using the above approach. This will make our business process robust that is unaffected by the errors or unavailability of the external systems.
History