Thursday, October 4, 2012

Add Soap Header to dynamic Web Service call using Reflection in C#.Net


I've been struggling with this one for a while.

Situation: We need to call different web services on the fly (dynamically). The service maybe hosted in Java or Linux or whatever environment. We know the URLs, Soap header names, etc.

Solution 1: Read the WSDL from the URL, then compile the assembly and get the object. Using reflection, invoke the web method. Same for Soap Header too. Use reflection, instantiate the soap header, set the properties.

Solution 2: If instantiating the SOAP Header as said in Solution 1 is an issue for some reason then write a custom SOAP extension to add the Header to the out going SOAP message.

The links below show how to do it (for solution 2).
IMPORTANT: I tried the solution suggested in link-1 and I was getting the "XML not well formed", inner exception: Root element missing error. After a lot of tweaking (with fiddler) and searching on google, found the article on link-2, below which overrides the "Before Deserialize" and copies the tweaked SOAP to the output stream. That got me rid of the XML well formed error.

1. http://forums.asp.net/t/1137408.aspx
2. http://blog.encoresystems.net/articles/how-to-capture-soap-envelopes-when-consuming-a-web-service.aspx

Thanks to the posters of the above links!!!

NOTE: Another issue I had was not including the xmlns (namespace) in the SOAP header. Do include it.

Let me know if you still have issues.

Happy Dynamic web service calling.
=============================
Below is my code for the Soap Extension


    public class CarrierCallbackServiceExtension : SoapExtension
    {
        private bool outgoing = true;
        private bool incoming = false;
        private Stream outputStream;
        public Stream oldStream;
        public Stream newStream; 
 
        public override System.IO.Stream ChainStream(System.IO.Stream stream)
        {
            outputStream = stream;
            oldStream = stream; 
            newStream = new MemoryStream();
            return newStream; 
        }
 
        public override object GetInitializer(Type serviceType)
        {
            return null; 
        }
 
        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
        {
            throw new NotImplementedException();
        }
 
        public override void Initialize(object initializer)
        {
            return; 
        }
 
        public void WriteInput(SoapMessage message)
        {
            Copy(oldStream, newStream);
            newStream.Position = 0; 
        }         void Copy(Stream from, Stream to)         {             TextReader reader = new StreamReader(from);             TextWriter writer = new StreamWriter(to);             writer.WriteLine(reader.ReadToEnd());             writer.Flush();         }                  public override void ProcessMessage(SoapMessage message)         {             switch (message.Stage)             {                 case SoapMessageStage.BeforeSerialize:                     string clientResponse = GetXmlFromCache();                      break;                 case SoapMessageStage.BeforeDeserialize:                     WriteInput(message);                     break;                  case SoapMessageStage.AfterDeserialize:                     break;                  case SoapMessageStage.AfterSerialize:                     {                         string soapBodyString = GetXmlFromCache();                         int startPosition = soapBodyString.IndexOf("StringComparison.OrdinalIgnoreCase);                         int endPosition = soapBodyString.Length - startPosition;                         soapBodyString = soapBodyString.Substring(startPosition, endPosition);                         string xmlVersionString = @"";                                                  string soapEnvelopeBeginString =                             @"";                         string soapEnvHeaderString = @"";                         string soapEnvHeaderString2 = "
";                         string soapEnvHeaderString3 = "";                         Stream appOutputStream = new MemoryStream();                         StreamWriter soapMessageWriter = new StreamWriter(appOutputStream);                         soapMessageWriter.Write(xmlVersionString);                         soapMessageWriter.Write(soapEnvelopeBeginString);                         // The heavy-handed part - forcing the right headers AND the uname/pw :)                         soapMessageWriter.Write(soapEnvHeaderString);                         soapMessageWriter.Write("USERNAMESTRING");                         soapMessageWriter.Write(soapEnvHeaderString2);                         soapMessageWriter.Write("PASSWORDSTRING");                         soapMessageWriter.Write(soapEnvHeaderString3);                         // End clubbing of baby seals                         // Add the soapBodyString back in - it's got all the closing XML we need.                         soapMessageWriter.Write(soapBodyString);                         // write it all out.                         soapMessageWriter.Flush();                         appOutputStream.Flush();                         appOutputStream.Position = 0;                                                  XElement xe = XElement.Load(appOutputStream);                                                   XDocument xdoc = new XDocument(new XDeclaration("1.0""utf-8""yes"));                         xdoc.Add(xe);                         string output = StringWritingHelper(xdoc);                         //xdoc.Save(outputStream);                         //StreamReader reader = new StreamReader(appOutputStream);                         StreamWriter writer = new StreamWriter(this.outputStream);                         //writer.Write(reader.ReadToEnd());                         writer.Write(output);                         writer.Flush();                         appOutputStream.Close();                         this.outgoing = false;                         this.incoming = true;                         break;                      }             }         }         private string StringWritingHelper(XDocument sourceDocument)         {             StringBuilder sbResponse = new StringBuilder();             using (StringWriterUtf8 stringWriter = new StringWriterUtf8(sbResponse))             {                 sourceDocument.Save(stringWriter);             }             return sbResponse.ToString();         }         private string GetXmlFromCache()         {             newStream.Position = 0;             string soapResponse = ExtractFromStream(newStream);             return soapResponse;          }         private string ExtractFromStream(Stream target)         {             if (target != null)             {                 return (new StreamReader(target)).ReadToEnd();              }             return string.Empty;          }     }