Serialising SOAP parameters in C# - c#

I'm trying to write a .Net client to a vendor's SOAP Service, but I'm having trouble getting the parameters to the SOAP messages to serialise to a form that the service recognises.
Using wsdl.exe I generated a service proxy class which works fine in itself. However, one of the messages takes an argument which is an array of key/value pairs - this is the bit I'm having problems with.
The WSDL for the message is:
<message name='Execute'>
<part name='ContextHandle' type='xsd:string'/>
<part name='ScriptLanguage' type='xsd:string'/>
<part name='Script' type='xsd:string'/>
<part name='Params' type='xsd:anyType'/>
</message>
The service proxy class has this code:
[System.Web.Services.WebServiceBindingAttribute(Name="EngineSoapBinding", Namespace="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts/wsdl/")]
internal partial class Service : System.Web.Services.Protocols.SoapHttpClientProtocol {
....
[System.Web.Services.Protocols.SoapRpcMethodAttribute("http://www.smarteam.com/dev/ns/iplatform/embeddedscripts/action/Execute", RequestNamespace="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts", ResponseNamespace="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts", Use=SoapBindingUse.Literal)]
[return: System.Xml.Serialization.SoapElementAttribute("Result")]
public object Execute(string ContextHandle, string ScriptLanguage, string Script, object Params) {
object[] results = this.Invoke("Execute", new object[] {
ContextHandle,
ScriptLanguage,
Script,
Params});
return ((object)(results[0]));
}
....
}
In the vendor's documentation the Params argument should be an array of key/value pairs.
I've been able to capture network traffic from another working client to this service and got the following example of a message that the service recognises (SOAP envelope removed and formatted for clarity):
<STES:Execute xmlns:STES="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts" xmlns:sof="http://www.smarteam.com/dev/ns/SOF/2.0">
<ContextHandle>0019469#00228</ContextHandle>
<ScriptLanguage>javascript</ScriptLanguage>
<Script><![CDATA[Context.Result = Context.Params("Number");]]></Script>
<Params SOAP-ENC:arrayType="sof:DictionaryItem[2]">
<sof:DictionaryItem>
<key xsi:type="xsd:string">Number</key>
<value xsi:type="xsd:int">10</value>
</sof:DictionaryItem>
<sof:DictionaryItem>
<key xsi:type="xsd:string">Hello</key>
<value xsi:type="xsd:string">World</value>
</sof:DictionaryItem>
</Params>
</STES:Execute>
I've tried various data structures for the Params argument, but nothing I've tried gives anything close to this XML serialisation.
The closest I've been able to get is to write a DictionaryItem class which implements IXmlSerializable with the following WriteXml method:
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement("key");
writer.WriteValue(Key);
writer.WriteEndElement();
writer.WriteStartElement("value");
writer.WriteValue(Value);
writer.WriteEndElement();
}
I then give the Params argument a List<DictionaryItem> which results in the following serialization on the wire.
<Execute xmlns="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts">
<ContextHandle xmlns="">0022541#00228</ContextHandle>
<ScriptLanguage xmlns="">javascript</ScriptLanguage>
<Script xmlns="">Context.Result = Context.Params("Number");</Script>
<Params xmlns="">
<DictionaryItem xmlns="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts">
<key>Number</key>
<value>10</value>
</DictionaryItem>
<DictionaryItem xmlns="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts">
<key>Hello</key>
<value>World</value>
</DictionaryItem>
</Params>
</Execute>
Can anyone point me in the right direction to get this working?
** Update **
It doesn't surprise me in the slightest that this vendor is using a deprecated message format. Their whole product is a mess, and I'd gladly ditch it if I could. It's written in .Net, but has a COM API and this web service in a deprecated format. They do supply a client library for the web service, but it's writtn in Java. Huh?
I'm going to go back to my original idea and write a wrapper around the Java client, using ikvmc. At least I know I can get that to work, even if all the type conversion will be messy.
As for picking an answer #Cheeso and #Aaronaught have both been very helpful, so I've flipped a coin and given it to #Cheeso.

The example message you've shown there shows a message using what is known as "SOAP Section 5 encoding". SOAP encoding (not to say SOAP) was deprecated by all major tools and services vendors a looooong time ago, due to problems with compatibility and interoperability. Like in 2004 or so.
Seriously. Nobody should be using that stuff any longer. There is no excuse.
But even so, you should be able to get it to work.
The challenge is to get the XML namespaces right on each of the request and response elements. Just looking at the example request message - the one that "works" - you can see it's sort of deranged. There's the namespace with the STES prefix on the toplevel request element - Execute. Then, all the child elements get no namespace at all. This is weird.
The same weird on/off namespace thing occurs in the Params array. The wrapper element is in the namespace with the sof prefix. But the key and value child elements are not in that namespace- they are in no namespace at all.
In your attempt, you have a couple mismatches then;
In your case, the DictionaryItem element is in the http://www.smarteam.com/dev/ns/iplatform/embeddedscripts namespace. It should be in the http://www.smarteam.com/dev/ns/SOF/2.0 namespace.
In your case, the key and value elements are in the
http://www.smarteam.com/dev/ns/iplatform/embeddedscripts namespace. They should be in no namespace at all.
Normally a proper WSDL means you do not have to worry about any of these things. I am puzzled as to why your WSDL is not generating a proxy that does the right thing. What I recommend people do in this case is get a wSDL that really works. Oftentimes that means writing a mock version of the service in ASMX.
If you can generate a service in ASMX that accepts messages of the form accepted by the real service, and if you can generate a client that interacts with that ASMX service, then the client should also work with the real service. the reason I recommend ASMX is that it's so easy to tweak and retry things.
Toward that end, here's what I cam eup with.
<%# WebService Language="c#"
Class="Cheeso.CooperService"
%>
using System.Web.Services;
using System.Web.Services.Description;
using System.Web.Services.Protocols;
using System.Xml.Serialization;
using System.Collections;
namespace Cheeso
{
[SoapType(Namespace="http://www.smarteam.com/dev/ns/SOF/2.0")]
public class DictionaryItem
{
public string key { get; set; }
public string value { get; set; }
}
[System.Web.Services.WebService
(Name="EngineSoapBinding",
Namespace="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts/wsdl/")]
public class CooperService : System.Web.Services.WebService
{
[WebMethod]
[SoapRpcMethod
("http://www.smarteam.com/dev/ns/iplatform/embeddedscripts/action/Execute",
RequestNamespace="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts",
ResponseNamespace="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts",
Use=SoapBindingUse.Encoded)]
[return: System.Xml.Serialization.SoapElementAttribute("Result")]
public object Execute(string ContextHandle,
string ScriptLanguage,
string Script,
DictionaryItem[] Params)
{
return "The answer is 42. What is the question?";
}
}
}
This ASMX file should produce a WSDL and an interface that is equivalent to your real service. Generate the WSDL from it (using the ?wsdl query), and then write a test client. Examine the messages on the wire and tweak as necessary.
You can see that I applied a REAL type to the Params array. Also I decorated that type with the SoapType attribute and specified the desired xml namespace.
In your problem statement you didn't describe the response message. You'll need to go through a similar exercise - tweaking and adjusting - in order to shape the response message "expected" by your client to match the responses actually generated by the real service.
Also, remember that the xmlns prefixes are not significant. It's nto the prefix you need to match, it's the XML namespace itself. You don't need STES:Execute. You can use any namespace prefix, as long as it maps to the correct xml namespace.
Good luck.
If you get a chance, convince them to move to a WS-I compliant service interface. Interop is much easier when the service complies with the WS-I recommendations.
EDIT
This is a trace of the actual message from the client, generated using that WSDL:
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts/wsdl/"
xmlns:types="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts/wsdl/encodedTypes"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<q1:Execute xmlns:q1="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts">
<ContextHandle xsi:type="xsd:string">00913983</ContextHandle>
<ScriptLanguage xsi:type="xsd:string">Canadian, eh?</ScriptLanguage>
<Script xsi:type="xsd:string">To be or not to be....</Script>
<Params href="#id1" />
</q1:Execute>
<soapenc:Array id="id1"
xmlns:q2="http://www.smarteam.com/dev/ns/SOF/2.0"
soapenc:arrayType="q2:DictionaryItem[2]">
<Item href="#id2" />
<Item href="#id3" />
</soapenc:Array>
<q3:DictionaryItem id="id2"
xsi:type="q3:DictionaryItem"
xmlns:q3="http://www.smarteam.com/dev/ns/SOF/2.0">
<key xsi:type="xsd:string">17</key>
<value xsi:type="xsd:string">s9dkjdls</value>
</q3:DictionaryItem>
<q4:DictionaryItem id="id3"
xsi:type="q4:DictionaryItem"
xmlns:q4="http://www.smarteam.com/dev/ns/SOF/2.0">
<key xsi:type="xsd:string">fish</key>
<value xsi:type="xsd:string">barrel</value>
</q4:DictionaryItem>
</soap:Body>
</soap:Envelope>
Even though this is different than your target message, this should be parseable by your server-side, if it conforms to SOAP v1.1 section 5 encoding spec. This request message uses the "multiple reference" serialization whereas your example target message uses "single reference". But they should be equivalent to the server side. Should be.
But as I said originally, there were lots of problems getting SOAP section 5 encoding to work interoperably.

The WCF solution is really rather simple, just use these classes in your import:
[DataContract(Namespace = "http://www.smarteam.com/dev/ns/iplatform/embeddedscripts")]
[KnownType(typeof(SofDictionaryItem[]))]
[XmlSerializerFormat(Style = OperationFormatStyle.Rpc, Use = OperationFormatUse.Encoded)]
public class Execute
{
[DataMember(Order = 0)]
public string ContextHandle { get; set; }
[DataMember(Order = 1)]
public string ScriptLanguage { get; set; }
public string Script { get; set; }
[DataMember(Name = "Script", Order = 2, EmitDefaultValue = false)]
private CDataWrapper ScriptCData
{
get { return Script; }
set { Script = value; }
}
[DataMember(Order = 3)]
public object Params { get; set; }
}
[DataContract(Namespace = "http://www.smarteam.com/dev/ns/SOF/2.0", Name = "DictionaryItem")]
public class SofDictionaryItem
{
[DataMember]
public object Key { get; set; }
[DataMember]
public object Value { get; set; }
}
I'm using Marc Gravell's CDataWrapper here to force the CDATA tags around the Script.
The DataContractSerializer will generate output that's nearly identical to what you're seeing over the wire already:
<Execute xmlns="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<ContextHandle>0019469#00228</ContextHandle>
<ScriptLanguage>javascript</ScriptLanguage>
<Script><![CDATA[Context.Result = Context.Params("Number")]]></Script>
<Params i:type="a:ArrayOfDictionaryItem" xmlns:a="http://www.smarteam.com/dev/ns/SOF/2.0">
<a:DictionaryItem>
<a:Key i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">Number</a:Key>
<a:Value i:type="b:int" xmlns:b="http://www.w3.org/2001/XMLSchema">10</a:Value>
</a:DictionaryItem>
<a:DictionaryItem>
<a:Key i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">Hello</a:Key>
<a:Value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">World</a:Value>
</a:DictionaryItem>
</Params>
</Execute>
The only potential problem is the ArrayOfDictionaryItem, which is a convention that .NET always seems to use for array types. If you actually look at the generated WSDL for these types, you'll see that it actually references the soapenc:arrayType, but that may not be sufficient here if the endpoint is unaware of this convention. If that is the case, then unfortunately I think you'll have to resort to IXmlSerializable, because I've never been able to find a way to disable the ArrayOf generation in .NET.
As Cheeso mentions, the RPC/encoded SOAP format is officially deprecated by the WS-I and should never be used in production services anymore. One of the reasons it was deprecated was because it was lousy for interop and painful to implement. If possible, you really should talk to the vendor about getting an update, which ought to be using the standard document/literal wire format.

Related

WCF: Array Parameter

I would like to define a C# WCF service that exposes an operation invoked by messages of a given format. An example of that message is given below. That format is defined by a client that I do not have control over. I control the service side only.
In summary, I have a WCF operation that is supposed to take an array as its only parameter. The array parameter is represented in the way the example message below shows. That parameter, however, is currently not correctly being de-serialized and ends up being empty on service side, although it appears correctly represented in the SOAP message from the client. How do I change my WCF service for the parameter to be correctly deserialized?
(I know there are a bunch of questions that seem very similar and lengthy MSDN articles on how to de-serialize collections and arrays in various formats, but for some reason I keep running into issues or I am simply not able to take the answer to a similar enough question and apply it to my scenario).
Here are some of the many things I have already tried:
Varying the OperationFormatStyle and OperationFormatUse attributes on the OperationContract.
Using and not using ServiceKnownType attributes for string[].
Using collections instead of array.
Debugging into the WCF assemblies using .NET framework source stepping.
Reading up on SOAP section 5 and 7 encodings.
Adjusting the namespace parameters of the data contract members.
Changing the names of the array elements in the data contract (ItemNameAttribute).
I think I have already come closer to something that works compared to the original code. Before, with OperationFormatStyle.Rpc and OperationFormatUse.Encoded, I simply received a FaultException stating that the request failed to de-serialize.
With the code below, at least I have no exception any more, but the de-serialized array on service side is empty.
Here is an example of the message I am receiving, which is supposed to invoke an operation with a single, string array type argument:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="urn:iControl" xmlns:types="urn:iControl/encodedTypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<s:Header xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<To s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">https://127.0.0.1/iControl/iControlPortal.cgi</To>
<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">urn:iControl:GlobalLB/DataCenter</Action>
</s:Header>
<soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<q1:get_object_status xmlns:q1="urn:iControl:GlobalLB/DataCenter">
<data_centers href="#id1" />
</q1:get_object_status>
<soapenc:Array id="id1" soapenc:arrayType="xsd:string[3]">
<Item>Datacenter 5d4afe7f-f4e0-4660-9ff1-9c1f8a15a1df</Item>
<Item>Datacenter 70d88638-4622-46f1-8978-773898f57b0f</Item>
<Item>Datacenter c8fc4b53-af74-4f71-b7a7-9102fbd684b6</Item>
</soapenc:Array>
</soap:Body>
</soap:Envelope>
Here is the operation contract I have eventually arrived at. The original one was generated with "svcutil" I believe, but I do not have the original WSDL files used back then anymore. I have full control over this part, though, so I can adjust code on service side to whatever works. There is a custom operation selector doing the dispatching, but that all seems to be working, because the implementing method on service side is invoked and when I simply substitute a string for the string array (while leaving everything else the same), the parameter ends up in the method correctly.
[GeneratedCode("System.ServiceModel", "4.0.0.0")]
[ServiceContract(Name = "IGlobalLB.DataCenterPortType", Namespace = "urn:iControl", ConfigurationName = "IGlobalLBDataCenterPortType"), DispatchByBodyElementBehavior]
public interface IGlobalLBDataCenterPortType
{
// ...
[OperationContract(Action = "urn:iControl:GlobalLB/DataCenter", ReplyAction = "ra")]
[XmlSerializerFormat(Style = OperationFormatStyle.Document, SupportFaults = true, Use = OperationFormatUse.Literal)]
[ServiceKnownType(typeof(string[]))]
get_object_statusResponse get_object_status(get_object_statusRequest request);
// ...
}
Here is what the request data contract looks like. As said above, passing primitive parameters such as a plain string, instead of the string array (i.e. changing the type of data_centers from string[] to string), works as expected.
[DebuggerStepThrough()]
[GeneratedCode("System.ServiceModel", "4.0.0.0")]
[EditorBrowsable(EditorBrowsableState.Advanced)]
[MessageContract(WrapperName = "get_object_status", WrapperNamespace = "urn:iControl:GlobalLB/DataCenter",
IsWrapped = true)]
[KnownType(typeof(string[]))]
public partial class get_object_statusRequest
{
[MessageBodyMember(Namespace = "", Order = 0)]
public string[] data_centers;
public get_object_statusRequest()
{
}
public get_object_statusRequest(string[] data_centers)
{
Console.WriteLine("XXX: " + String.Join(" ", data_centers));
this.data_centers = data_centers;
}
}
Here is the operation implementation:
public get_object_statusResponse get_object_status(get_object_statusRequest request)
{
Console.WriteLine("Arguments: " + request.data_centers);
Console.WriteLine("Datacenters:");
foreach (var d in request.data_centers)
{
Console.WriteLine(d);
}
return new get_object_statusResponse();
}
The output is as follows, i.e. none of the array elements make it into the method. I would have expected the strings in the sample message to be contained in the array passed.
Arguments: System.String[]
Datacenters:

Does UTF-8 encoding have to be explicitly defined in a web service response?

I am supporting a C# (.asmx) web service built on .NET 3.5. It is a very, very simple service that returns, among a few other things, a street address. The scope of the application that provides the service was recently widened and now the DB behind it houses international addresses as well. This means Latin foreign language characters can be stored (accent marks, umlauts, etc) and consequently returned in a web service response.
When I test the service locally using soapUI's automatically generated requests and without adding any special headers or any other intstructive information, I see this element exactly as it's stored in the database - with its accent mark:
<CompositeStreet>140-146 RUE EUGÈNE DELACROIX</CompositeStreet>
However, when a connecting system calls the service the IT staff report that the response contains the question mark typically used as the replacement for a non-supported character:
<compositeStreet>140-146 RUE EUG?NE DELACROIX</compositeStreet>
I'm unsure whose issue this is and I'm concerned that the simplistic design of the service may mean that it falls on my shoulders to recode to somehow ensure any consumer is guaranteed to see the data in UTF-8 encoding. I've been under the impression that UTF-8 was the default encoding and nothing explicit was required. (Please save the discussion about upgrading to WCF for another thread - it's not in scope for this right now.)
Can you have a look at the structure of the service and tell me if there is actually something that needs to be done on my side? (Code drastically slimmed down for the sake of this discussion)
The ASMX page - basically the WebMethod accepts a request and returns the response:
namespace Company.IT.Network.LocationStore.LS_Services
{
using . . .;
[WebService(Namespace = "Company.IT.Network.LocationStore.LS_Services.ClliRecordService")]
public class ClliRecordService : System.Web.Services.WebService
{
public ClliRecordService() { }
[WebMethod(Description = "Lengthy description")]
public VZBServiceResponse ClliLookup_VzbSite(VZBServiceRequest sRequest)
{
VZBServiceResponse sResponse = new VZBServiceResponse(sRequest);
return sResponse;
}
}
And the serializable class that is a property of the VZBServiceResponse type which ends up in the XML response. You'll see all there is is the [Serializable] attribute and the setting of the order of the elements returned.
namespace Company.IT.Network.LocationStore.LS_Lib.BO
{
using ...;
[Serializable]
public class Address
{
private string _compositeStreet;
private string _buildingName;
[XmlElement(Order = 0)]
public string BuildingName
{
get { return _buildingName; }
set { _buildingName = value; }
}
[XmlElement(Order = 1)]
public string CompositeStreet
{
get { return _compositeStreet; }
set { _compositeStreet = value; }
}
}
}
There is really not much more to it than that. No formatting of the XML response through a memory stream (which I've seen in some posts) or specific handling through Serialize or Deserialize methods.
Are there recommendations to improve on this so that service consumers are guaranteed to be presented foreign language characters or is that really up to the calling system? Thank you for any guidance!

How to reduce WCF soap message size to save bandwidth?

I've written a MessageSizeInspector class to inspect the size of the messages the service receives from the client. Here is what it looks like:
//the actual implementation logs more!
//but in this question, my only concerns is request.ToString()
public sealed class MessageSizeInspector :
BehaviorExtensionElement, //to enable it to be used in config
IDispatchMessageInspector, //service-side inspector
IEndpointBehavior //so we can apply it on endpoint
{
if (request != null )
{
Logger.Verbose("Message = {0}\n", request.ToString()));
}
//more
}
And it logs message of this format:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<To s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://localhost:8961/EngineService</To>
<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/IEngineService/GetMousePatternID</Action>
</s:Header>
<s:Body>
<GetMousePatternID xmlns="http://tempuri.org/">
<args>
<accelerator xmlns="http://schemas.datacontract.org/2004/07/Runaware.Insight.EngineService" />
<accessKey xmlns="http://schemas.datacontract.org/2004/07/Runaware.Insight.EngineService" />
<controlId xmlns="http://schemas.datacontract.org/2004/07/Runaware.Insight.EngineService">66222</controlId>
<!--- deleted the rest to avoid verbosity -->
</args>
</GetMousePatternID>
</s:Body>
</s:Envelope>
As you can see the actual message is tiny, but the XML elements and namespaces make it too huge. Namespaces, in particular, are too big in comparison to the actual message.
My question is : how can we reduce the size of the soap messages? As the xmlns are same for all elements, the WCF could optimize this but it doesn't. Can we make it use shorter namespace of our choice? There are few elements such as accelerator and accessKey which do not even have any value (as you can see by scrolling right), but they're still there! Can we make WCF to omit them and assume their values null (or default value) on the service side?
In short, anything we can do to save bandwidth?
I'm assuming that the above message is what comes the client, exactly in the same format, with same XML elements and namespaces.
The server is written in C# and WCF, and the client is written in C++ using Windows Web Services API. The service and client both written by me. So I've full control over them. If required, I may change it to save bandwidth!
Aside from the anwsers that the others already said (eg: WCF compressing)...
Try setting Name and Namespaces on DataContracts, DataMembers as well as ServiceContracts.
Example:
[DataContract(Name = "a", Namespace = "")]
class Person
{
[DataMember(Name = "a")]
public string FullName;
[DataMember(Name = "b)]
public int Age;
}
This will make Person to be called as "a" from namespace "" and the property FullName to be called as "a", making the XML shorter like:
<a> -- Represents the Person class
<a>Name of the person</a> -- Represents the FullName property
<b>38</b>
</a>
Properties and classes must have different "short" names from DataContract and DataMembers.
Reference: https://bkiener.wordpress.com/2010/05/04/optimize-data-contracts-for-better-wcf-performance/
Hmm, well my first approach would be to use WCF Compression. I can only give you the link, because I've no experience with it. But I assume, that it would be a pretty good improvement.
Question is, do you have influence on the client. Because its configuration must also be edited.
Edit: Question is answered in one of your comments. :o)
Welcome to SOAP and the problem many people meet with ignorance. XML is nice, SOAP too, but there IS an overhead of having a text based expressive system. And the bandwidth hit is hugh, especially for small messages.
THere is not a lot you can do - outside compression in IIS and then dropping soap / xml and go web api with JSON. The web servviec / soap standard is quite - hm - explicit and a standard. Bypassing it (drop requirement, use something else) is the only way to really deal with it.
Since you have full control over everything, why not change to use WebAPI instead?
Take a look at this question before you make your final decision: Do I need WCF if I can use ASP.net Web API

.NET webservice client null properties in result

Upon seemingly successfully consuming a web service in a c# console application, inspection of the returned object shows the properties are all null!
After much searching for answers, I've tracked the issue down to a namespace problem in the SOAP XML -- using fiddler, my slightly modified SOAP response looks like so:
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<MyResponse xmlns="http://foo.com">
<FIRST_NM xmlns="">Michael</FIRST_NM>
</MyResponse>
</soapenv:Body>
</soapenv:Envelope>
The only part I added was the:
...FIRST_NM xmlns="">Michael... (the bold part) using fiddler.
Once I did this as a man-in-the-middle during a debug session -- voila! The FIRST_NM value of "Michael" was displayed to me in my client app for the first time ever. I decided to try this because I noticed that the request going out from my client had such an attribute, and the response did not.
OK, so diagnosis completed. I am turning to the step where I make it work. Assuming I have no control over the server side of things, I tried opening up the Reference.cs file and making some changes, such as changing this:
[System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, Order=3)]
public string FIRST_NM {
get {
return this.fIRST_NMField;
}
set {
this.fIRST_NMField = value;
this.RaisePropertyChanged("FIRST_NM");
}
}
to this:
[System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, Namespace="http://foo.com", Order=3)]
public string FIRST_NM {
...
I added Namespace="http://foo.com", to the XmlElementAttribute. My thinking was that without the xmlns="" that the namespace "http://foo.com" was inherited from parent "MyResponse" tag and I'd get a match.
That, however, did not work. Any direction you all could point me in would be greatly appreciated.
I was able to resolve the issue by removing the following line(s) completely from the .NET-generated code:
[System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, Order=3)]
I suppose since the namespace was inherited from the parent tag that it was actually "Qualified" not "Unqualified". Removing that led to the default Form behavior, None (which defers to the document).
Thank you to the community -- I figured this out largely based on recommendations I found in related SO Questions.

XElement not declared when using Service Reference Proxy with XmlAnyElementAttribute

I am working on a Windows Phone 7 app that talks to a 3rd party Soap service. I used Add Service Reference to the wsdl to generate the proxy class. Some calls work, but certain outbound calls that make use of an 'sObject' class result in the error "The type System.Xml.Linq.XElement was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically"
The relevant part of the sObject class is defined (as autogenerated)
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.1")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:sobject.partner.soap.sforce.com")]
public partial class sObject : object, System.ComponentModel.INotifyPropertyChanged {
<snipped ....>
[System.Xml.Serialization.XmlAnyElementAttribute(Namespace="urn:sobject.partner.soap.sforce.com", Order=3)]
public System.Xml.Linq.XElement[] Any {
get {
return this.anyField;
}
set {
this.anyField = value;
this.RaisePropertyChanged("Any");
}
}
The code I use to create a sObject:
sObject post = new sObject();
XElement postEls = new XElement("sObject",
new XElement("Type", "TextPost"),
new XElement("ParentId", userInfo.userId),
new XElement("Body", postBody)
);
post.Any = postEls.Elements().ToArray();
Steps I Have Tried:
Adding [System.Xml.Serialization.XmlInclude(typeof(System.Xml.Linq.XElement))] to the sObject class. Same error, it seems like this should have worked.
Adding [System.Xml.Serialization.XmlElement()] to the Any property. When I did this the message was serialized, but -as expected- incorrect (the Any elements should not be there, the goal is just the XElements[] as output)
<sObject>
<type xmlns="urn:sobject.partner.soap.sforce.com">FeedPost</type>
<Id xsi:nil="true" xmlns="urn:sobject.partner.soap.sforce.com" />
<Any xmlns="urn:sobject.partner.soap.sforce.com">
<Type xmlns="">TextPost</Type>
</Any>
<Any xmlns="urn:sobject.partner.soap.sforce.com">
<ParentId xmlns="">XXXXXX</ParentId>
</Any>
<Any xmlns="urn:sobject.partner.soap.sforce.com">
<Body xmlns="">Test post from WP7!</Body>
</Any>
</sObject>
As an aside, I suspect this is related, calls that return sObjects inbound from the 3rd party service have their Any property null, despite the data being there in the RPC Response.
It turns out the exception was due to the serialization of XElement[], currently an issue in Windows Phone 7.
As noted by Vijay Verma here, The linked MSDN article on XmlSerializer.Serialize states under Platform Notes:
Silverlight for Windows Phone:
The XmlSerializer.Serialize method throws an InvalidOperationException if the XmlSerializer object is initialized with a type parameter that contains an array of objects of type XElement.
Vijay listed a workaround of using a single XElement and parsing the field separately. This would work if you control both sides of the SOAP message. Unfortunately I do not so the request is invalid on the 3rd party's side.
This explains the InvalidOperationException with XElement was not expected, although not the answer I was hoping for. Hopefully it helps someone else who may have control over both sides of the service.

Categories