One of the characteristics that separates the good from the great Biztalk developers and architects, is handling of errors, particularly SOAP errors. This is often not tested sufficiently.
This blog by Shashidharan Krishnan does a good job of explaining how to catch a SOAP error in a BizTalk Orchestration:
I’d like to throw in some ideas that I implemented today.
Based on the blog above, I created the xpath below, a slightly simpler form to grab two of the main SOAP Fields. I only wanted the two most critical, in order to save space (I’ll discuss more about my column size limitations below). I didn’t want all the namespaces and SOAP wrappers, that just cluttered up any error that the user might see.
//SOAPEXECPTION_11 is my 'Exception Object Name' specified in the Catch (of the Scope) Properties strSoapFaultcode = xpath(SOAPEXCEPTION_11, "string(//*[local-name()='faultcode'])"); strSoapFaultstring = xpath(SOAPEXCEPTION_11, "string(//*[local-name()='faultstring'])"); // format my own condensed error consisting of the above two parts strValue = "SoapFaultCode=" + strSoapFaultcode + " SoapFaultString=" + strSoapFaultstring; // Single quotes cause SQL to blow up, // truncate to 1000 (so we can put two fault codes in one DB field strValue = strValue.Replace("'","''"); vWebServiceAResponseMessage = strValue.Substring (0,System.Math.Min(strValue.Length,vMaxSizeEachSoapError));
The site where I’m working wanted to store the error back in a database table via a web service. Basically, the is a table that represents the canonical schema. A “poller” job extracts data from that table, writing a canonical XML to disk. An orchestration picks it up, and processes it. That same orchestration then calls a web services that was provided to me to update the same row of that table with the success or failure codes. If any webservice or SOAP errors are encountered, an flag is set, and that row can be re-queued to be be polled and processed again later after those errors have been fixed.
The database column for the error was a max of 2000 characters, and was not an XML column, just a varchar2(2000) column in an Oracle database. So if I passed to big of a field, the web service would blow up with a size issue on the SQL column. So it became my job to make sure my errors fit.
Now, to further complicate matters, imagine this scenario. I called WebService-A, then WebService-B. Each could have a SOAP error. Then I call WebService-C to actually update the table. So basically, I had to get a max of 1000 characters of error for each of the WebServices A and B, for a max of 2000. In some cases, you would not even call WebService-B if it were dependent on results from A; but in my case, they wanted to store the error from A into the backend ERP system (which is what WebService-B was updating).
So I was also dealing with a scenario where A could succeed and B could fail, A could fail and B could succeed, both could succeed or both could fail. I did this with just a normal decide shape with 4 or 5 branches. I set string variables there, then had one construct/message assignment afterward, using those string variables to construct the message request for WebService-C.
Further, if the SOAP message had single quotes in it, that was messing up SQL, so I had turn each single quote into two single quotes. (If you are not familiar with this, the classic case is LastName = O’Brian needs to be treated as O”Brian. Typcially, the database server is set up to handle to consecutive single quotes as one single quote, and helps avoid confusion with the single quotes used in the SQL syntax.
Here are two other thoughts to consider. Should your orchestration be flexible enough to handle SOAP 1.1 and SOAP 1.2 errors? I usually don’t go that far, but the guy I worked with did. It’s nice and flexible, but causes a little extra bloat in the orchestration.
For the final thought, there can be “hard” SOAP errors, or soft business/errors. At this client, the web service typically return a Result that was either set to “Ok” or some error. So in addition to checking the SOAP errors, I had to check that error as well. This is fairly typical.
In other orchestrations that I’ve recently written, I created a common error handler, then threw an internal exception or set an “IsError” variable/flag, so that the common error handler could run once as needed. That way, I could treat SOAP 1.1, SOAP 1.2, or soft/business errors using the same logic. Orchestrations don’t allow easily for re-useable code. You either have to call a sub-orchestration, use direct-binding to call another orchestration, call C# code, or try to create a code block that runs based on flags. Having separate orchestrations can make restart a big pain.
And what do you do when your exception handling webservice itself has an error? I’ll leave that for you and your architect. Here, we “pretty print” all XML request/responses that have errors, and email them to a distribution group.
How to cause a SOAP error to Test a BizTalk Orchestration
Changing the URL didn’t do it, that caused an XLANG/s exception, with a service not found exception, as shown below:
Error Description: System.ServiceModel.EndpointNotFoundException: There was no endpoint listening at http://mysite.com/MyService/MyServiceName2.svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more
In the real world, an error like this would be fixed by changing the URL and resuming the orchestration.
One thing I do to force a SOAP exception is to edit the WCF SendPort, and modify the “Action” block on the General tab. I save the original values to NotePad++, then remove or change the values of the: <Operation Name=”value”…
You may have to restart the host instance, then run the test.
This results in the following error:
a:ActionNotSupported The message with Action '<BtsActionMapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Operation Name="MyMethod" Action="http://tempuri.org/IMyService/MyMethod" /> </BtsActionMapping>' cannot be processed at the receiver, due to a ContractFilter mismatch at the EndpointDispatcher. This may be because of either a contract mismatch (mismatched Actions between sender and receiver) or a binding/security mismatch between the sender and the receiver. Check that sender and receiver have the same contract and the same binding (including security requirements, e.g. Message, Transport, None).
When you are done testing your exceptions, then copy back what you saved in NotePad++.
If you happen to be the author of the webservice (or the author is inclined to work closely with you), you might want to code in a web service exception that will be sent back as SOAP exception. Use some flag or freaky/sneaky set of values to trigger the exception (make sure those values would never occur in the real production environment).