Help with SoapExtender
-
Thursday, February 23, 2012 8:46 PM
I am trying to build a SoapExtender for use with Salesforce API. This is a Soap web service that I do not have control over. Problem is test sites have a Namespace prefix on all custom fields, while production does not. The idea is I want to add WSDL from production.xml provided from Salesforce. When a SOAP request is made, I want to intecept and modify the Soap Message, to add Namespace prefix. This seems to work ok. However, I also want to modify the response XML before serialization to remove any Namespace prefix on custom fields. Thi seemingly works, but then dies as sson as I leave the BeforeSerialize message state. I get a "not well formed XML" error, with inner exception of "Root Element missing". Heres the code I wrote.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using System.Text.RegularExpressions; using System.Xml; namespace SF_NamespaceTranslation { public class SF_SoapExtender : System.Web.Services.Protocols.SoapExtension { private Stream oldStream; private Stream newStream; private string sforce_Namespace; public override Stream ChainStream(Stream stream) { oldStream = stream; newStream = new MemoryStream(); return newStream; } public MemoryStream AddNamespaceToCustomFields(Stream streamToFix) { streamToFix.Position = 0L; StreamReader sr = new StreamReader(streamToFix); string strRawContents = sr.ReadToEnd(); string strRevisedContents = strRawContents; Regex RegexObj = new Regex(@"\S*__c\S*"); MatchCollection mc = RegexObj.Matches(strRawContents); foreach (Match m in mc) { strRevisedContents = strRevisedContents.Replace(m.Value, sforce_Namespace + "__" + m.Value); } XmlDocument xd = new System.Xml.XmlDocument(); xd.LoadXml(strRevisedContents); MemoryStream streamToReturn = new MemoryStream(); xd.Save(streamToReturn); return streamToReturn; } public MemoryStream RemoveNamespaceFromCustomFields(Stream streamToFix) { streamToFix.Position = 0L; StreamReader sr = new StreamReader(streamToFix); string strRawContents = sr.ReadToEnd(); string strRevisedContents = strRawContents.Replace(sforce_Namespace + "__", ""); XmlDocument xd = new System.Xml.XmlDocument(); xd.LoadXml(strRevisedContents); MemoryStream streamToReturn = new MemoryStream(); xd.Save(streamToReturn); return streamToReturn; } public override void ProcessMessage(System.Web.Services.Protocols.SoapMessage message) { switch (message.Stage) { case System.Web.Services.Protocols.SoapMessageStage.BeforeSerialize: break; case System.Web.Services.Protocols.SoapMessageStage.AfterSerialize: newStream.Position = 0; newStream = AddNamespaceToCustomFields(newStream); newStream.Position = 0; Copy(newStream, oldStream); break; case System.Web.Services.Protocols.SoapMessageStage.BeforeDeserialize: newStream.Position = 0; Copy(oldStream,newStream); newStream = RemoveNamespaceFromCustomFields(newStream); newStream.Position = 0; break; case System.Web.Services.Protocols.SoapMessageStage.AfterDeserialize: break; } } public override void Initialize(object initializer) { sforce_Namespace = System.Configuration.ConfigurationManager.AppSettings["Salesforce_Namespace"]; } public override object GetInitializer(System.Web.Services.Protocols.LogicalMethodInfo methodInfo, System.Web.Services.Protocols.SoapExtensionAttribute attribute) { return attribute; } public override object GetInitializer(Type serviceType) { return new object(); } void Copy(Stream from, Stream to) { TextReader reader = new StreamReader(from); TextWriter writer = new StreamWriter(to); writer.WriteLine(reader.ReadToEnd()); writer.Flush(); } } [AttributeUsage(AttributeTargets.Method)] public class SF_SoapExtenderAttribute : System.Web.Services.Protocols.SoapExtensionAttribute { private int priority; public override Type ExtensionType { get { return typeof(SF_SoapExtender); } } public override int Priority { get { return priority; } set { priority = value; } } } }I would appreciate any help anyone can give.
TIA,
:) David
David A. Perry
- Moved by Leo Liu - MSFT Friday, February 24, 2012 5:44 AM Moved for better support. (From:Visual C# General)
All Replies
-
Friday, February 24, 2012 5:45 AMHi David,
I am moving your thread into the ASMX Web Services and XML Serialization Forum for dedicated support.
Have a nice day,Leo Liu [MSFT]
MSDN Community Support | Feedback to us
-
Friday, February 24, 2012 2:38 PMModerator
You are almost certainly wrong about the namespace prefixes being the source of your problem. SalesForce.com's SOAP API has beein in use for many years; I used it back in 2004. There's no way they would get such a basic part of XML wrong all of a sudden. All of their client integrations would fail.
Note that the following two examples are identical:
<root xmlns="http://foo.com"> <child/> </root>and
<foo:root xmlns:foo="http://foo.com"> <foo:child /> </foo:root>
John Saunders
WCF is Web Services. They are not two separate things.
Use WCF for All New Web Service Development, instead of legacy ASMX or obsolete WSE
Use File->New Project to create Web Service Projects -
Friday, February 24, 2012 2:46 PM
I am not talking about the XML Namespace. I am talking about the fact that in my test Salesforce account when you create a Custom field called "MyCustomField__c" the enterprise WSDL generated (and expected) is to refer to this column as "CompanyName_MyCustomField__c". IN the enterprise wsdl for the production environment the enterprise WSDL generated (and expected) is to refer to it without the prefix - ie - Just "MyCustomField__c". The Soap Extender I am trying to build into my client would allow enterprise WSDL to work in either environment, without having to reimport the WSDL and recompile the project, and change all references in code to either having or not having the "CompanyName_" prefix.
If you could help me with my SoapExtender does not work I'd appreciate it.
David A. Perry
-
Monday, February 27, 2012 6:52 PM
Anyone? Please??
:) David
David A. Perry
-
Monday, February 27, 2012 6:58 PMModerator
Your code is too complex for me to see what's wrong just by looking at it.
"Root element missing" often means what it says: there is no root element in your XML, i.e. it is empty.
I recommend that you unit test your "Add" and "Remove" methods. Make sure they basically work, by themselves.e middle
Also, I have always found it necessary to start with the basic example from the SoapExtension documentation, to then make that example work, and only then, little by little, to modify it to become what I need. There are too many little unexpected issues that come from the fact that the SoapExtension is being called from the middle of ASMX processing.
John Saunders
WCF is Web Services. They are not two separate things.
Use WCF for All New Web Service Development, instead of legacy ASMX or obsolete WSE
Use File->New Project to create Web Service Projects -
Tuesday, February 28, 2012 4:41 PM
Ok - I figured it out. The main problem was not the RemoveNamespaceFromCustomFields routine, rather that the newStream stream was not in a position to be used to hold the contents of the response. I modified the BeforeDeserialize logic to use a new MemoryStream, and it worked fine. After a little more tinkering - here's the final code for my Salesforce SoapExtender:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using System.Text.RegularExpressions; using System.Xml; using System.Xml.Linq; namespace SF_NamespaceTranslation { public class SF_SoapExtender : System.Web.Services.Protocols.SoapExtension { private Stream oldStream; private Stream newStream; private string sforce_Namespace; private HashSet<string> sforce_NamespacesNotToTranslate; public override Stream ChainStream(Stream stream) { oldStream = stream; newStream = new MemoryStream(); return newStream; } public MemoryStream AddNamespaceToCustomFields(Stream streamToFix) { streamToFix.Position = 0; StreamReader sr = new StreamReader(streamToFix); string strRawContents = sr.ReadToEnd(); XDocument xd = XDocument.Parse(strRawContents); List<XElement> xel = (from el in xd.Root.Descendants() where el.Name.ToString().EndsWith("__c") == true select el).ToList(); foreach (XElement xe in xel) { bool isExcluded = false; foreach (string xns in sforce_NamespacesNotToTranslate) { if (xe.Name.LocalName.StartsWith(xns + "__") == true) { isExcluded = true; } } if (isExcluded == false) { xe.Name = sforce_Namespace + "__" + xe.Name.LocalName.ToString(); } } List<XElement> xVall = (from el in xd.Descendants() where el.Value.ToString().Contains("__c") == true && el.HasElements == false select el).ToList(); foreach (XElement xe in xVall) { Regex RegexObj = new Regex(@"\b(\w*)+__c\b"); MatchCollection mc = RegexObj.Matches(xe.Value); string strRevisedValue = xe.Value; List<string> lMatches = new List<string>(); foreach (Match m in mc) { bool isExcluded = false; foreach (string xns in sforce_NamespacesNotToTranslate) { if (m.Value.StartsWith(xns + "__") == true) { isExcluded = true; } } if (lMatches.Contains(m.Value) == false && isExcluded == false) { strRevisedValue = strRevisedValue.Replace(m.Value, sforce_Namespace + "__" + m.Value); } lMatches.Add(m.Value); } xe.Value = strRevisedValue; } MemoryStream streamToReturn = new MemoryStream(); xd.Save(streamToReturn); return streamToReturn; } public MemoryStream RemoveNamespaceFromCustomFields(Stream streamToFix) { streamToFix.Position = 0; StreamReader sr = new StreamReader(streamToFix); string strRawContents = sr.ReadToEnd(); string strRevisedContents = strRawContents.Replace(sforce_Namespace + "__", ""); System.Xml.XmlDocument xd = new System.Xml.XmlDocument(); xd.LoadXml(strRevisedContents); MemoryStream streamToReturn = new MemoryStream(); xd.Save(streamToReturn); return streamToReturn; } public override void ProcessMessage(System.Web.Services.Protocols.SoapMessage message) { switch (message.Stage) { case System.Web.Services.Protocols.SoapMessageStage.BeforeSerialize: break; case System.Web.Services.Protocols.SoapMessageStage.AfterSerialize: newStream.Position = 0; newStream = AddNamespaceToCustomFields(newStream); newStream.Position = 0; Copy(newStream, oldStream); break; case System.Web.Services.Protocols.SoapMessageStage.BeforeDeserialize: MemoryStream ms = new MemoryStream(); Copy(oldStream, ms); ms = RemoveNamespaceFromCustomFields(ms); ms.Position = 0; Copy(ms, newStream); newStream.Position = 0; break; case System.Web.Services.Protocols.SoapMessageStage.AfterDeserialize: break; } } public override void Initialize(object initializer) { sforce_Namespace = System.Configuration.ConfigurationManager.AppSettings["Salesforce_Namespace"]; sforce_NamespacesNotToTranslate = new HashSet<string>(System.Configuration.ConfigurationManager.AppSettings["Salesforce_NamespacesNotToTranslate"].Split(',').ToList<string>()); } public override object GetInitializer(System.Web.Services.Protocols.LogicalMethodInfo methodInfo, System.Web.Services.Protocols.SoapExtensionAttribute attribute) { return attribute; } public override object GetInitializer(Type serviceType) { return new object(); } void Copy(Stream from, Stream to) { TextReader reader = new StreamReader(from); TextWriter writer = new StreamWriter(to); writer.WriteLine(reader.ReadToEnd()); writer.Flush(); } } [AttributeUsage(AttributeTargets.Method)] public class SF_SoapExtenderAttribute : System.Web.Services.Protocols.SoapExtensionAttribute { private int priority; public override Type ExtensionType { get { return typeof(SF_SoapExtender); } } public override int Priority { get { return priority; } set { priority = value; } } } }Hope this helps someone.
:) David
David A. Perry
- Proposed As Answer by MH_2106 Tuesday, August 14, 2012 9:05 AM
- Marked As Answer by David Perry OH Tuesday, August 14, 2012 12:47 PM

