2012年3月19日

[.Net] Generic SOAP Proxy

A soap generic proxy I wrote about 6 years ago. The proxy parses web service definition and provides the ability to invoke remote web services without generating proxy classes. The most important part of this generic proxy is how we parse WSDL and create a HTTP POST to that service.

To retrieve WSDL

public void ConnectWebService()
{
System.Net.WebRequest.DefaultWebProxy = iWebProxy;
AcceptAllCertificates();
const string XPATH_TO_WEB_METHOD_INFORMATION_NODE = "//*/types/schema/element";// "/types/schema/element[@name=\"{0}\"]/*";
const string XPATH_TO_WEB_METHOD_PARAMETERS = "sequence/element";
string resp = GetWSDL(WebServiceUrl + "?wsdl");

if (OnWSDLRetrieved != null)
OnWSDLRetrieved(this, new SoapProxyWSDLEventArgs(resp));

wsdlDoc.LoadXml(resp);
targetNamespace = wsdlDoc.DocumentElement.Attributes.GetNamedItem("targetNamespace").Value;
List<string> references = new List<string>();
if (OnSoapActionRetrieved != null)
{
foreach (System.Xml.XmlNode node in wsdlDoc.SelectNodes(XPATH_TO_WEB_METHOD_INFORMATION_NODE))
{
string action = node.Attributes.GetNamedItem("name").Value;
string paramType = string.Empty;
string paramName = string.Empty;
SoapProxySoapActionEventArgs e = new SoapProxySoapActionEventArgs(action);

if (node.SelectSingleNode("complexType").ChildNodes.Count > 0)
{
//this method has parameters
foreach (System.Xml.XmlNode paramElement in node.SelectNodes("complexType/sequence/element"))
{

if (paramElement.Attributes.GetNamedItem("ref") == null)
{
paramName = paramElement.Attributes.GetNamedItem("name").Value;
paramType = (paramElement.Attributes.GetNamedItem("type") == null ? "object" :
paramElement.Attributes.GetNamedItem("type").Value);
}
else
{
System.Xml.XmlNode refNode = wsdlDoc.SelectSingleNode("//*/types/schema/element[@name=\"" +

paramElement.Attributes.GetNamedItem("ref").Value + "\"]");
paramName = refNode.Attributes.GetNamedItem("name").Value;
paramType = (refNode.Attributes.GetNamedItem("type") == null ? "object" :
paramElement.Attributes.GetNamedItem("type").Value);
references.Add(paramName);
}
}
}
if (!references.Contains(action))
OnSoapActionRetrieved(this, e);
}
}
}






 



To invoke web method




public string InvokeWebMethod(string methodName, params object[] parameters)
{
try
{
WebRequest.DefaultWebProxy = iWebProxy;

byte[] requestData = CreateHttpRequestData(methodName, parameters);
string uri = WebServiceUrl + "?op=" + methodName;
HttpWebRequest httpRequest = (HttpWebRequest)HttpWebRequest.Create(uri);
httpRequest.Method = "POST";
httpRequest.KeepAlive = false;
httpRequest.ContentType = contentType;// "text/xml; charset=utf-8";// "application/x-www-form-urlencoded";
httpRequest.ContentLength = requestData.Length;

string action = (targetNamespace.EndsWith("/") ? targetNamespace + methodName : targetNamespace + "/" + methodName);
httpRequest.Headers.Add("SOAPAction", "\"" + GetSOAPAction(methodName) + "\"");

//httpRequest.Expect = "100-continue";
httpRequest.Timeout = TimeOut;
HttpWebResponse httpResponse = null;
string response = string.Empty;
try
{
httpRequest.GetRequestStream().Write(requestData, 0, requestData.Length);
httpRequest.GetRequestStream().Flush();
if (httpRequest.HaveResponse || TimeOut > 0)
{
httpResponse = (HttpWebResponse)httpRequest.GetResponse();
System.IO.Stream baseStream = httpResponse.GetResponseStream();
System.IO.StreamReader responseStreamReader = new System.IO.StreamReader(baseStream);
response = responseStreamReader.ReadToEnd();
responseStreamReader.Close();
System.Diagnostics.Debug.WriteLine("[WebService-Resp]HTTP State:" + httpResponse.StatusCode.ToString());
}
else
{
response = "Response not available(" + GetSOAPAction(methodName) + ")";
}
}
catch (WebException e)
{
const string CONST_ERROR_FORMAT = "<?xml version=\"1.0\" encoding=\"utf-8\"?><Exception><{0}Error>{1}” +

“<InnerException>{2}</InnerException></{0}Error></Exception>";
response = string.Format(CONST_ERROR_FORMAT, methodName, e.ToString(), (e.InnerException != null ?

e.InnerException.ToString() : string.Empty));
}
return response;
}
catch (Exception exp)
{
WriteLog("Exception while InvokeWebMethod:" + exp.Message);
WriteLog(" ->" + exp.StackTrace);
if (exp.InnerException != null)
{
WriteLog("InnerException:" + exp.InnerException.Message);
WriteLog(" ->" + exp.InnerException.StackTrace);
}
throw exp;
}
}




Create HTTP POST Data



private byte[] CreateHttpRequestData(string methodName, params object[] parameters)
{
System.Xml.XmlNode node = wsdlDoc.SelectSingleNode("//*/types/schema/element[@name=\"" + methodName + "\"]");
string text = GetSOAPRequestTemplate(soapVersion);
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
doc.LoadXml(text);
System.Xml.XmlNode soapBody = doc.DocumentElement.ChildNodes[0];
string ns = targetNamespace;

System.Xml.XmlNode methodNode = doc.CreateNode(System.Xml.XmlNodeType.Element, methodName, "");
System.Xml.XmlAttribute attr = doc.CreateAttribute("xmlns");
attr.Value = ns;
methodNode.Attributes.Append(attr);
int i = 0;
foreach (System.Xml.XmlNode paramElement in node.SelectNodes("complexType/sequence/element"))
{
string paramName = "", paramType = "";
if (paramElement.Attributes.GetNamedItem("ref") == null)
{
paramName = paramElement.Attributes.GetNamedItem("name").Value;
paramType = (paramElement.Attributes.GetNamedItem("type") == null ? "object" :
paramElement.Attributes.GetNamedItem("type").Value);

System.Xml.XmlNode paramNode = doc.CreateNode(System.Xml.XmlNodeType.Element, paramElement.Attributes.GetNamedItem("name").Value, "");
paramNode.InnerXml = (string)parameters[i++];// System.Web.HttpUtility.HtmlEncode((string)parameters[i++]);// "<![CDATA[" + (string)parameters[i++] + "]]>";
methodNode.AppendChild(paramNode);
}
else
{
WriteLog("paramElement=" + paramElement.OuterXml);
WriteLog("ref=" + paramElement.Attributes.GetNamedItem("ref").Value);
string refName = paramElement.Attributes.GetNamedItem("ref").Value.IndexOf(":") > 0 ? paramElement.Attributes.GetNamedItem("ref").Value.Split(':')[1] : paramElement.Attributes.GetNamedItem("ref").Value;
WriteLog("XPath=" + "//*/types/schema/element[@name=\"" + paramElement.Attributes.GetNamedItem("ref").Value + "\"]");
WriteLog("XPath=" + "//*/types/schema/element[@name=\"" + refName + "\"]");

//System.Xml.XmlNode refNode = wsdlDoc.SelectSingleNode("//*/types/schema/element[@name=\"" + paramElement.Attributes.GetNamedItem("ref").Value + "\"]");
System.Xml.XmlNode refNode = wsdlDoc.SelectSingleNode("//*/types/schema/element[@name=\"" + refName + "\"]");

WriteLog("refNode=" + refNode.OuterXml);
paramName = refNode.Attributes.GetNamedItem("name").Value;
WriteLog("paramName=" + paramName);
paramType = (refNode.Attributes.GetNamedItem("type") == null ? "object" :
paramElement.Attributes.GetNamedItem("type").Value);

System.Xml.XmlNode paramNode = doc.CreateNode(System.Xml.XmlNodeType.Element, paramName, "");
paramNode.InnerXml = (string)parameters[i++];//System.Web.HttpUtility.HtmlEncode((string)parameters[i++]);// "<![CDATA[" + (string)parameters[i++] + "]]>";
methodNode.AppendChild(paramNode);
}
}
text = text.Replace("<GENERATED_XML_DATA/>",methodNode.OuterXml);//"<![CDATA![" + methodNode.OuterXml + "]]>");
//doc.LoadXml(text);
//soapBody.AppendChild(methodNode);
{
//doc.Save(@"C:\content.xml");
}
if (wsdlDoc.FirstChild is System.Xml.XmlDeclaration)
{
return System.Text.Encoding.GetEncoding(((System.Xml.XmlDeclaration)wsdlDoc.FirstChild).Encoding).GetBytes(text);
}
return System.Text.Encoding.Unicode.GetBytes(text);//doc.OuterXml);
}





 


And a template for SOAP 1.1 & 1.2 messages




<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd=http://www.w3.org/2001/XMLSchema

xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GENERATED_XML_DATA/>
</soap:Body>
</soap:Envelope>







<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd=http://www.w3.org/2001/XMLSchema

xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<GENERATED_XML_DATA/>
</soap12:Body>
</soap12:Envelope>




2012年3月2日

[WCF] Server did not recognize the value of HTTP Header SOAPAction:[some namespace]

I have a web service as the following :

   1: [WebService(Namespace = "http://app.trend.com/services/flowService/xml")]



   2: [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]



   3: public class Processor : System.Web.Services.WebService {



   4:    [WebMethod]



   5:    public Step GetStepInfo(string msgInstId, string stepId) {...}



   6:     //...



   7: }






For the Step class returned by this GetStepInfo() method, the definition is as the following :





   1: [Serializable]



   2: public class Step : BaseStep {



   3:    public StepNode() { }



   4:    



   5:    public string ID { get; set; }



   6:  



   7:    public string Name { get; set; }



   8:    //...



   9: }






And BaseStep:





   1: [Serializable]



   2: public class BaseStep{        



   3: public string Comments { get; set; }



   4: }






To consume this web service via WCF client, for some design concerns, I need to create a channel to operate the service, not just reference the service and invoking the method generated by the system. So I created a IProcessor interface and start to create certain methods…etc.



These is what I created and how I invoke the service :





   1: [ServiceContract(Namespace = "http://app.trend.com/services/flowService/xml")]



   2: public interface IProcessor{



   3:     //[OperationContract(Action = "http://app.trend.com/services/flowService/xml/GetStepInfo", ReplyAction="*")]



   4:     [OperationContract]



   5:     Trend.BPM.APPs.XmlFlowEngine.StepNode GetStepInfo(string msgInstId, string stepId);



   6:  



   7:     //[System.ServiceModel.OperationContractAttribute(Action = "http://app.trend.com/services/flowService/xml/StartCase", ReplyAction = "*")]



   8:     [OperationContract]



   9:     bool StartCase(string flowName, string msgId, string currentStepID, string owner, string action, string comments, string paramatersXml);



  10:  



  11:     //[System.ServiceModel.OperationContractAttribute(Action = "http://app.trend.com/services/flowService/xml/ReleaseCase", ReplyAction = "*")]



  12:     [OperationContract]



  13:     string ReleaseCase(string msgId, string stepID, string owner, string action, string comments, string paramaterXML);



  14:  



  15:     //[System.ServiceModel.OperationContractAttribute(Action = "http://app.trend.com/services/flowService/xml/GetNextApproverEx", ReplyAction = "*")]



  16:     [OperationContract]



  17:     bool GetNextApproverEx(string msgId, string flowName, string currentStep, string currentApprover, string category, out string nextApprover, out string newStep);



  18: }




And how I invoke the service:





   1: ChannelFactory<IXmlFlowEngine> channel = new ChannelFactory<IXmlFlowEngine>(new BasicHttpBinding(BasicHttpSecurityMode.None));



   2: var svc = channel.CreateChannel(new EndpointAddress(url));






Everything looks good until I actually run the program, I got the following error:




Server did not recognize the value of HTTP Header SOAPAction:http://app.trend.com/services/flowService/xml/IProcessor/GetStepInfo




I have this error because WCF serializer does not recognize the namespace and could not serialize/deserialize the message to objects. So I add namespace to my interface and Setp/StepBase class as following





   1: [ServiceContract(Namespace = "http://app.trend.com/services/flowService/xml", Name = "IProcessor")]



   2: public interface IXmlFlowEngine {



   3:     [OperationContract(Action = "http://app.trend.com/services/flowService/xml/GetStepInfo", ReplyAction="*")]



   4:     Trend.BPM.APPs.XmlFlowEngine.StepNode GetStepInfo(string msgInstId, string stepId);



   5:  



   6:     [System.ServiceModel.OperationContractAttribute(Action = "http://app.trend.com/services/flowService/xml/StartCase", ReplyAction = "*")]



   7:     bool StartCase(string flowName, string msgId, string currentStepID, string owner, string action, string comments, string paramatersXml);



   8:  



   9:     [System.ServiceModel.OperationContractAttribute(Action = "http://app.trend.com/services/flowService/xml/ReleaseCase", ReplyAction = "*")]



  10:     string ReleaseCase(string msgId, string stepID, string owner, string action, string comments, string paramaterXML);



  11:  



  12:     [System.ServiceModel.OperationContractAttribute(Action = "http://app.trend.com/services/flowService/xml/GetNextApproverEx", ReplyAction = "*")]



  13:     bool GetNextApproverEx(string msgId, string flowName, string currentStep, string currentApprover, string category, out string nextApprover, out string newStep);



  14: }






And explicitly specify the Name property on each class member





   1: [Serializable]



   2: [DataContract(Name="Step",Namespace="http://app.trend.com/services/flowService/xml")]



   3: public class Step : XmlFlowNode {



   4:     public StepNode() { }



   5:     [DataMember(Name="ID",EmitDefaultValue=false)]



   6:     public string ID { get; set; }



   7: }






   1: [Serializable]



   2: [DataContract(Name="StepBase",Namespace="http://app.trend.com/services/flowService/xml")]



   3: public class StepBase{



   4:     [DataMember(Name="Comments",EmitDefaultValue=false)]



   5:     public string Comments { get; set; }



   6: }




 



And this fixed the issue.

About Me