Refereced from:
http://coding.baneworld.nl/2009/02/client-found-response-content-type-of-multipartrelated-but-expected-textxml/
While creating a desktop client for a third party SOAP web service I ran into the following error.
Client found response content type of ‘multipart/related’, but expected ‘text/xml’.
After trying a few different methods to generate the SOAP interfaces and classes I still was gettings errors.
Spending some time “googling” for a solution I found some references MTOM, Java/.NET interop issues, HTTP Status: 100 Continue, MIME and several other possible cauzes.
Just to make sure the webservice was working correctly I used soapUI to check all operations. Everything was working fine.
Testing all SOAP operations from .NET i found out that all invalid calls returned a correct SOAP response containing the error.
All valid calls generated the Client found response content type of ‘multipart/related’, but expected ‘text/xml’ error.
Using a custom SoapExtension i dumped all requests and response to log files. All valid ops requests got a response containing the expected ops result with one big issue. The response was in MIME and not the SOAP XML .NET expected.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ------=_Part_0_9729661.1239018752715 Content-Type: application/xop+xml; charset=UTF-8; type="text/xml"; Content-Transfer-Encoding: binary Content-ID: <root.message@cxf.apache.org> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header><api-version>1.5.8</api-version></soap:Header> <soap:Body> <ns2:getDatabaseFieldsResponse xmlns:ns2="http://api.xxxxxxxx.xxx/"> <databaseField><label>Email-adres</label><name>email</name><defaultValue></defaultValue><type>email</type><key>true</key><mandatory>true</mandatory><minimumLength>0</minimumLength><maximumLength>0</maximumLength></databaseField> </ns2:getDatabaseFieldsResponse> </soap:Body> </soap:Envelope> ------=_Part_0_9729661.1239018752715-- |
instead of
1 2 3 4 5 6 7 8 9 | <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header><api-version>1.5.8</api-version></soap:Header> <soap:Body> <ns2:getDatabaseFieldsResponse xmlns:ns2="http://api.xxxxxxxx.xxx/"> <databaseField><label>Email-adres</label><name>email</name><defaultValue></defaultValue><type>email</type><key>true</key><mandatory>true</mandatory><minimumLength>0</minimumLength><maximumLength>0</maximumLength></databaseField> </ns2:getDatabaseFieldsResponse> </soap:Body> </soap:Envelope> |
After spending some extra time searching the web for anwsers, which I didn’t find, i figured i could do a simple ‘Quick & Dirty’ solution using a SoapExtension to fix the response.
By using a custom SoapExtension I could intercept the response and modify the content so .NET would except it. Info on creating and using a SoapExtension to intercept SOAP calls can be found here (MSDN).
First thing I needed to do was write a small regular expression to extract the SOAP Envelope from the response stream.
The regex I use is “<soap:Envelope(.*?\n*?)*?</soap:Envelope>“.
Using this regex on the response content matches the SOAP Envelope, which I then use to make a correct response stream. By making a new Stream which I fill with a xml declaration and the SOAP Envelope, replacing the original Stream with this new Stream.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | /* RegEx to select the soap Envelope */ Regex m_Regex = new Regex("<soap:Envelope(.*?\n*?)*?</soap:Envelope>", RegexOptions.IgnoreCase | RegexOptions.Compiled); /* fix stream */ protected internal void FixStream(Stream from, Stream to) { TextReader reader = new StreamReader(from); TextWriter writer = new StreamWriter(to); string content = reader.ReadToEnd(); string newcontent = ""; Match regMatch; for (regMatch = m_Regex.Match(content); regMatch.Success; regMatch = regMatch.NextMatch()) { // Process the words int nStart = regMatch.Index; int nLenght = regMatch.Length; newcontent += content.Substring(nStart, nLenght); } writer.WriteLine("<?xml version="1.0" encoding="utf-8"?>;" + newcontent); writer.Flush(); } |
Having made my SoapExtension, which I gave the name ModifierExtension, all I needed to do was add the custom soapExtensionType to the system.web/webServices/soapExtensionTypes section of the App.config file.
1 | <add group="Low" priority="1" type="Minc.SoapExtensions.ModifierExtension, Minc.SoapExtensions" /> |
Now the desktop client works as expected, getting a correct response on each call.
Complete source code for ModifierExtension.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | using System; using System.Web.Services; using System.Web.Services.Protocols; using System.IO; using System.Text.RegularExpressions; namespace Minc.SoapExtensions { public class ModifierExtension : SoapExtension { private Stream m_OldStream; private Stream m_NewStream; private Regex m_Regex; // Save the Stream representing the SOAP request or SOAP response into a local memory buffer. public override Stream ChainStream(Stream stream) { m_OldStream = stream; m_NewStream = new MemoryStream(); return m_NewStream; } public ModifierExtension() { /* RegEx to select the soap Envelope */ m_Regex = new Regex("<soap:Envelope(.*?\n*?)*?</soap:Envelope>", RegexOptions.IgnoreCase | RegexOptions.Compiled); } public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute) { return ""; } // The SOAP extension was configured to run using a configuration file // instead of an attribute applied to a specific Web service // method. public override object GetInitializer(Type webServiceType) { return ""; } // Receive the file name stored by GetInitializer and store it in a // member variable for this specific instance. public override void Initialize(object initializer) { } // If the SoapMessageStage is such that the SoapRequest or // SoapResponse is still in the SOAP format to be sent or received, // save it out to a file. public override void ProcessMessage(SoapMessage message) { switch (message.Stage) { case SoapMessageStage.BeforeSerialize: break; case SoapMessageStage.AfterSerialize: WriteOutput(message); break; case SoapMessageStage.BeforeDeserialize: WriteInput(message); break; case SoapMessageStage.AfterDeserialize: break; default: throw new Exception("invalid stage"); } } /* process incoming message*/ private void WriteInput(SoapMessage message) { /* set ContentType to 'text/xml' to match the modified content*/ message.ContentType = "text/xml"; FixStream(m_OldStream, m_NewStream); m_NewStream.Position = 0; } /* process outgoing message*/ public void WriteOutput(SoapMessage message) { m_NewStream.Position = 0; Copy(m_NewStream, m_OldStream); } /* copy stream */ protected internal void Copy(Stream from, Stream to) { TextReader reader = new StreamReader(from); TextWriter writer = new StreamWriter(to); writer.WriteLine(reader.ReadToEnd()); writer.Flush(); } /* fix stream */ protected internal void FixStream(Stream from, Stream to) { TextReader reader = new StreamReader(from); TextWriter writer = new StreamWriter(to); string content = reader.ReadToEnd(); string newcontent = ""; Match regMatch; for (regMatch = m_Regex.Match(content); regMatch.Success; regMatch = regMatch.NextMatch()) { // Process the matches. Should only be one Match, so newcontent += regMatch.ToSting(); would be enough. int nStart = regMatch.Index; int nLenght = regMatch.Length; newcontent += content.Substring(nStart, nLenght); } //Add xml start tag and soap envelope to output stream writer.WriteLine("<?xml version="1.0" encoding="utf-8"?>" + newcontent); writer.Flush(); } } // Create a SoapExtensionAttribute for the SOAP Extension that can be // applied to a Web service method. [AttributeUsage(AttributeTargets.Method)] public class ModifierExtensionAttribute : SoapExtensionAttribute { private int m_Priority; public override Type ExtensionType { get { return typeof(ModifierExtension); } } public override int Priority { get { return m_Priority; } set { m_Priority = value; } } } } |
Complete system.web section of App.config file
1 2 3 4 5 6 7 8 9 10 11 12 | <?xml version="1.0" encoding="utf-8"?> <configuration> ... <system.web> <webServices> <soapExtensionTypes> <add group="Low" priority="1" type="Minc.SoapExtensions.ModifierExtension, Minc.SoapExtensions" /> </soapExtensionTypes> </webServices> </system.web> ... </configuration> |