// Loosely Coupled SOAP/WSDL Client // Author: Dion Hinchcliffe // Date: June 12th, 2005 // Update: June 20th, 2005 // // Usage: oc OrderID Name Amount Price DeliveryDate // Description: Will check the contract at the WSDL URL and invoke the placeOrder operation described therein, // then wait ten (10) seconds and retrieve the order and print the order to the console // Note: Only the location, binding, operations, and dependent schemas are checked // // Update: Added support to check for mustUnderstand attributes in the contract using System; using System.Xml; using MSXML2; public class oc { public static void Main(string[] args) { // Check the number of parameters and print usage if needed if (args.Length != 6) { Console.WriteLine("Order Client Usage: oc OrderID Name Amount Price DeliveryDate"); return; } // Fetch the WSDL at the URL provided Console.WriteLine("Get the Machine Readable Service Description"); XMLHTTP xh= new XMLHTTP(); xh.open("GET", args[0], false, null, null); xh.send(null); // Check for errors (likely that URL could be wrong) if (xh.status==404) { Console.WriteLine("404: URL not found"); return; } // We have the service description XML text, now parse it XmlDocument sd = new XmlDocument(); sd.LoadXml(xh.responseText); // Make sure we have the WSDL, SOAP, and XSD namespaces declared XmlNamespaceManager nm = new XmlNamespaceManager(sd.NameTable); nm.AddNamespace("wsdl", "http://schemas.xmlsoap.org/wsdl/"); nm.AddNamespace("soap", "http://schemas.xmlsoap.org/wsdl/soap/"); nm.AddNamespace("s", "http://www.w3.org/2001/XMLSchema"); // OK, we want to place an order, and we know it's WSDL, so let's check the contract // Is there an Order Service described? System.Xml.XmlNode q = sd.SelectSingleNode("/wsdl:definitions/wsdl:service/@name[.='OrderService']", nm); if (q == null) { Console.WriteLine("UNACCEPTABLE CONTRACT CHANGE: No OrderService present"); return; } // The service is there, so lets check out the particulars, like if there's a SOAP binding q = sd.SelectSingleNode("/wsdl:definitions/wsdl:service/wsdl:port[@name='OrderServiceSoap' and @binding='tns:OrderServiceSoap']", nm); if (q == null) { Console.WriteLine("UNACCEPTABLE CONTRACT CHANGE: No SOAP binding"); return; } // There is a SOAP binding for OrderService, verify the details including the operations q = sd.SelectSingleNode("/wsdl:definitions/wsdl:binding[@name='OrderServiceSoap' and @type='tns:OrderServiceSoap' and " + "soap:binding[@transport = 'http://schemas.xmlsoap.org/soap/http' and @style = 'document'] and " + "wsdl:operation[@name = 'placeOrder' and soap:operation[@soapAction = 'http://www.hinchcliffe.org/placeOrder']] and "+ "wsdl:operation[@name = 'getOrderStatus' and soap:operation[@soapAction = 'http://www.hinchcliffe.org/getOrderStatus']]]", nm); if (q == null) { Console.WriteLine("UNACCEPTABLE CONTRACT CHANGE: Check of the OrderService binding failed"); return; } // OK, do we actually have SOAP ports for the operations we want q = sd.SelectSingleNode("/wsdl:definitions/wsdl:portType[@name='OrderServiceSoap' and " + "wsdl:operation[@name='placeOrder' and wsdl:input[@message='tns:placeOrderSoapIn'] and wsdl:output[@message='tns:placeOrderSoapOut']] and " + "wsdl:operation[@name='getOrderStatus' and wsdl:input[@message='tns:getOrderStatusSoapIn'] and wsdl:output[@message='tns:getOrderStatusSoapOut']]]", nm); if (q == null) { Console.WriteLine("UNACCEPTABLE CONTRACT CHANGE: No placeOrder() operation binding"); return; } // Now, let's check the input and output schemas of the SOAP messages, and if it all // matches, it's a wrap: the service contract is good // Step 1: Make sure the placeOrder() operation takes an order schema q = sd.SelectSingleNode("/wsdl:definitions/wsdl:types/s:schema[@targetNamespace='http://www.hinchcliffe.org/']/s:element[@name='placeOrder']/s:complexType/s:sequence/s:element[@minOccurs='1' and @ref='order']", nm); if (q == null) { Console.WriteLine("UNACCEPTABLE CONTRACT CHANGE: placeOrder() doesn't take an order structure"); return; } // Step 2: Make sure the order structure still has the fields we need and their types haven't changed q = sd.SelectSingleNode("/wsdl:definitions/wsdl:types/s:schema/s:complexType[@name='Order']/s:sequence"+ "[s:element[@name = 'orderID' and @type='s:int'] and " + "s:element[@name = 'name' and @type='s:string'] and " + "s:element[@name = 'amount' and @type='s:int'] and " + "s:element[@name = 'price' and @type='s:double'] and " + "s:element[@name = 'deliverydate' and @type='s:dateTime']]" , nm); if (q == null) { Console.WriteLine("UNACCEPTABLE CONTRACT CHANGE: order structure doesn't have fields required for nominal operation"); return; } // Step 3a: Last, check the in-sies and outies of getOrderStatus() since we need to make sure we get an order structure back q = sd.SelectSingleNode("/wsdl:definitions/wsdl:types/s:schema/s:element[@name='getOrderStatus']/s:complexType/s:sequence/s:element[@minOccurs='0' and @name='oid' and @type='s:string']" , nm); if (q == null) { Console.WriteLine("UNACCEPTABLE CONTRACT CHANGE: getOrderStatus doesn't take at least a string parameter"); return; } // Step 3b: Does getOrderStatus response at least return an order structure q = sd.SelectSingleNode("/wsdl:definitions/wsdl:types/s:schema/s:element[@name='getOrderStatusResponse']/s:complexType/s:sequence/s:element[@minOccurs='1' and @ref='getOrderStatusResult']" , nm); if (q == null) { Console.WriteLine("UNACCEPTABLE CONTRACT CHANGE: getOrderStatus doesn't return the structure type expected"); return; } // Step 3c: Does getOrderStatus response actually have the type of 'order'? q = sd.SelectSingleNode("/wsdl:definitions/wsdl:types/s:schema/s:element[@name='getOrderStatusResult' and @type='Order']", nm); if (q == null) { Console.WriteLine("UNACCEPTABLE CONTRACT CHANGE: getOrderStatus doesn't have the right structure mapped to the result"); return; } // mustUnderstand present? If there is any in this service, then we can't use it q = sd.SelectSingleNode("/wsdl:definitions/wsdl:types/@mustUnderstand", nm); if (q != null) { Console.WriteLine("UNACCEPTABLE CONTRACT CHANGE: A mustUnderstand header has been added to the contract, which we can't handle"); return; } // Checking is done, build the message according to the contractual format and send it string soapOrderRequest = ""+ ""+ ""+ ""+ ""+ ""+ args[1] + ""+ ""+ args[2] + ""+ ""+ args[3] + ""+ ""+args[4]+""+ ""+args[5]+""+ ""+ ""+ ""+ ""; // Now send the PlaceOrder message via HTTP to the SOAP server string sendURL = sd.SelectSingleNode("/wsdl:definitions/wsdl:service/wsdl:port[@name='OrderServiceSoap' and @binding='tns:OrderServiceSoap']/soap:address/@location",nm).InnerText; Console.WriteLine("Send URL: "+sendURL); // Open a new connection. xh = new XMLHTTP(); // Send the request Console.WriteLine("Sending request..."); xh.open("POST", sendURL, false, null, null); xh.setRequestHeader("Content-Type", "text/xml; charset=utf-8"); xh.setRequestHeader("SOAPAction", "http://www.hinchcliffe.org/placeOrder"); xh.send(soapOrderRequest); Console.WriteLine("Request complete, checking for SOAP faults"); // Check for success XmlDocument rs = new XmlDocument(); // Make sure we have the WSDL namespace declared XmlNamespaceManager nm2 = new XmlNamespaceManager(rs.NameTable); nm2.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/"); rs.LoadXml(xh.responseText); q = rs.SelectSingleNode("//soap:Fault" , nm2); if (q != null) { Console.WriteLine("SOAP FAULT: "+xh.responseText); return; } else { Console.WriteLine("SUCCESS!"); } // Wait 10 seconds // Build the getOrderStatus() request soapOrderRequest = ""+ ""+ ""+ ""+ ""+args[1]+""+ ""+ ""+ ""; // Send the request via HTTP to the SOAP server Console.WriteLine("Sending getOrderStatus request..."); xh= new XMLHTTP(); xh.open("POST", sendURL, false, null, null); xh.setRequestHeader("Content-Type", "text/xml; charset=utf-8"); xh.setRequestHeader("SOAPAction", "http://www.hinchcliffe.org/getOrderStatus"); xh.send(soapOrderRequest); // Check for success XmlDocument rs2 = new XmlDocument(); // Make sure we have the WSDL namespace declared XmlNamespaceManager nm3 = new XmlNamespaceManager(rs2.NameTable); nm3.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/"); rs2.LoadXml(xh.responseText); q = rs2.SelectSingleNode("//soap:Fault" , nm2); if (q != null) { Console.WriteLine("SOAP FAULT: "+xh.responseText); return; } else { Console.WriteLine("SUCCESS!"); Console.WriteLine("Order #"+args[1]+" is "+xh.responseText); } } }