Just a preamble here I received the requirement from a third party vendor to add a specific prefix to my soap response. Even know in XML these two formats are equal.
<m:HelloWorld xmlns:m="http://someurl.com/WebServices/"> | |
<!--Same as --> | |
<HelloWorld xmlns="http://someurl.com/WebServices/"> | |
<soapenv:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> | |
<soapenv:Body> | |
<!--Also Same as --> | |
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> | |
<soap:Body> |
I was required to add it regardless because I assume they were parsing this manually with java or some other client. I came upon these two articles by one of my favorite developers/bloggers Scott Hanselman really the basis of everything I've done here so here is credit where it is do.
Modifying the namespace PREFIX of the root node of the body of a SOAP Web Services Response....whew![XmlRootForcePrefix(Prefix='foo')
He apparently ran into the same exact issues with a client who was not satisfied with the soap response he was giving them even know it was exactly the same as the one they were requesting. Anyone that makes you add a prefix to your soap response is probably doing something terribly wrong at the risk of my soul going to hell I was not about to email Scott to confirm the fact I am a scrub and indeed was in need of a fix I did some research and was able to create my own implementation. ***WARNING DO NOT USE UNLESS YOU ARE BETWEEN A ROCK AND A HARD PLACE GO WCF INSTEAD***
First you will want to register the soap extension in your web.config file under the system.web tag as follows:
<webServices> | |
<soapExtensionTypes> | |
<add type="Namespace.YourSoapExtNameExtension,YourAssemblyFileName" priority="1"/> | |
</soapExtensionTypes> | |
</webServices> |
Just replace the items accordingly and know your assembly file name is most likely your project name if your project is named test then test is your most likely candidate.
Here is the code and once again credit to Scott Hanselman because I used another article he wrote for the implementation of removing white spaces to write this extension with some minor changes in naming convention and the addition of serialization to the xmldocument object and then writing it back to the response stream.
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Text; | |
using System.Web; | |
using System.Web.Services.Protocols; | |
using System.Xml; | |
namespace Prefix | |
{ | |
public class PrefixExtension : SoapExtension | |
{ | |
// Fields | |
private Stream newStream; | |
private Stream oldStream; | |
private void AddPrefix() | |
{ | |
this.newStream.Position = 0L; | |
this.newStream = this.ProcessXML(this.newStream); | |
this.Copy(this.newStream, this.oldStream); | |
} | |
public MemoryStream ProcessXML(Stream streamToPrefix) | |
{ | |
streamToPrefix.Position = 0L; | |
XmlTextReader reader = new XmlTextReader(streamToPrefix); | |
XmlWriterSettings settings = new XmlWriterSettings(); | |
// This is where the magic happens. I'm removing some of the default namespaces then adding soapenv instead of regular soap. | |
// There are many other things you can do once you get the response into the xmldocument object. | |
// After you are done it converts it back then writes it to the response. | |
XmlDocument doc = new XmlDocument(); | |
doc.Load(reader); | |
doc.DocumentElement.Prefix = "soapenv"; | |
doc.DocumentElement.RemoveAttribute("xmlns:soap"); | |
doc.DocumentElement.RemoveAttribute("xmlns:xsi"); | |
doc.DocumentElement.FirstChild.Prefix = "soapenv"; | |
XmlReader reader2 = new XmlNodeReader(doc); | |
settings.Encoding = Encoding.UTF8; | |
MemoryStream outStream = new MemoryStream(); | |
using (XmlWriter writer = XmlWriter.Create(outStream, settings)) | |
{ | |
do | |
{ | |
writer.WriteNode(reader2, true); | |
} | |
while (reader2.Read()); | |
writer.Flush(); | |
} | |
outStream.Seek(0, SeekOrigin.Begin); | |
return outStream; | |
} | |
public override void ProcessMessage(SoapMessage message) | |
{ | |
switch (message.Stage) | |
{ | |
case SoapMessageStage.BeforeSerialize: | |
case SoapMessageStage.AfterDeserialize: | |
return; | |
case SoapMessageStage.AfterSerialize: | |
this.AddPrefix(); | |
return; | |
case SoapMessageStage.BeforeDeserialize: | |
this.GetReady(); | |
return; | |
} | |
throw new Exception("invalid stage"); | |
} | |
public override Stream ChainStream(Stream stream) | |
{ | |
this.oldStream = stream; | |
this.newStream = new MemoryStream(); | |
return this.newStream; | |
} | |
private void GetReady() | |
{ | |
this.Copy(this.oldStream, this.newStream); | |
this.newStream.Position = 0L; | |
} | |
private void Copy(Stream from, Stream to) | |
{ | |
TextReader reader = new StreamReader(from); | |
TextWriter writer = new StreamWriter(to); | |
writer.WriteLine(reader.ReadToEnd()); | |
writer.Flush(); | |
} | |
public override object GetInitializer(Type t) | |
{ | |
return typeof(PrefixExtension); | |
} | |
public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute) | |
{ | |
return attribute; | |
} | |
public override void Initialize(object initializer) | |
{ | |
//You'd usually get the attribute here and pull whatever you need off it. | |
PrefixAttribute attr = initializer as PrefixAttribute; | |
} | |
[AttributeUsage(AttributeTargets.Method)] | |
public class PrefixAttribute : SoapExtensionAttribute | |
{ | |
// Fields | |
private int priority; | |
// Properties | |
public override Type ExtensionType | |
{ | |
get { return typeof(PrefixExtension); } | |
} | |
public override int Priority | |
{ | |
get { return this.priority; } | |
set { this.priority = value; } | |
} | |
} | |
} | |
} |
Last of all just add the attribute to your webmethods that you would like to use this prefix on. [Prefix] also make sure you are referencing System.ComponentModel.DataAnnotations
Hopefully this helps some poor guy out there that has been put in a hard spot like myself last week. I could not find anywhere this information was in one place and was accessible.