.NET webservice client null properties in result - c#

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.

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:

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

SalesForce query method does not return useable data

I am trying to integrate with SOAP web services from SalesForce. My goal is to simply pull down the user data from SalesForce so we can integrate it into our system. I am able to login successfully using the proxy class (C#) I generated from the partner WSDL on the SalesForce website. The next thing I want to do is query the Users, pulling down FirstName, LastName, ect...
It is important to know that I was forced to modify the generated proxy code because it generated incorrectly. It populated the xml serialization fields to match private fields which throws an exception when making web service calls. It also generated a blank namespace for the "Any" property of the sObject class which I removed. I took advice from this StackOverflow topic if you'd like to see why I made these choices.
This is what my query looks like:
The qResult contains exactly 3 records (which is accurate since I only have 3 users). The issue is that every property on the sObject except "type" are always null.
I can't figure if I am making a simple error when trying to query or if the fact that I had to modify the proxy class and remove the namespace on the Any field is causing this issue. I am not sure what to do at this point. That same query in the developer console (at SalesForce.com) returns the correct data. It's also worth mentioning that my result of the query returned the proper amount of elements, they just don't have any data in them. It must be something simple... like adding back the namespace binding but figuring out what it should be or adding an xmlbinding to the property.
I was just wondering if anyone knew what to do about this. Maybe someone has had the same thing happen to them?
EDIT: So I took the time to perform some incredibly hacky techniques for intercepting requests from the generated webservice client, and I ended up getting the raw data that is being sent from SalesForce in response to my request. Here it is:
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:partner.soap.sforce.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:sf="urn:sobject.partner.soap.sforce.com">
<soapenv:Body>
<queryResponse>
<result xsi:type="QueryResult">
<done>true</done>
<queryLocator xsi:nil="true"/>
<records xsi:type="sf:sObject">
<sf:type>User</sf:type>
<sf:Id xsi:nil="true"/>
<sf:FirstName>Bob</sf:FirstName>
<sf:LastName>Dyllan</sf:LastName>
<sf:Email>bob#bob.com</sf:Email>
<sf:CompanyName xsi:nil="true"/>
<sf:Phone xsi:nil="true"/>
<sf:Title xsi:nil="true"/>
</records>
<records xsi:type="sf:sObject">
<sf:type>User</sf:type>
<sf:Id xsi:nil="true"/>
<sf:FirstName>Zachary</sf:FirstName>
<sf:LastName>Sheppard</sf:LastName>
<sf:Email>some_guy#yahoo.com</sf:Email>
<sf:CompanyName>Yahoo</sf:CompanyName>
<sf:Phone xsi:nil="true"/>
<sf:Title xsi:nil="true"/>
</records>
<records xsi:type="sf:sObject">
<sf:type>User</sf:type>
<sf:Id xsi:nil="true"/>
<sf:FirstName xsi:nil="true"/>
<sf:LastName>Chatter Expert</sf:LastName>
<sf:Email>noreply#chatter.salesforce.com</sf:Email>
<sf:CompanyName xsi:nil="true"/>
<sf:Phone xsi:nil="true"/>
<sf:Title xsi:nil="true"/>
</records>
<size>3</size>
</result>
</queryResponse>
</soapenv:Body>
</soapenv:Envelope>
It does seem to a serialization issue with the "sf" properties on these sObjects... but how do I make that match up with my C#? Here is the C# as it currently is for the sObject class:
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:sobject.partner.soap.sforce.com")]
public partial class sObject
{
/// <remarks/>
public string type;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("fieldsToNull", IsNullable=true)]
public string[]
fieldsToNull;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
public string
Id;
/// <remarks/>
public System.Xml.XmlElement[]
Any;
}
After an incredibly long day of research and frustration, I finally came across the correct attribute for this "Any" property of SalesForce's sObject class. SalesForce was trying to scrape up the relevant xml elements into this sObject class which allows them to leave the sObject class as a dynamic type. So in order to force the C# class to scrape in all the xml tags into the "Any" property we simply apply this:
[System.Xml.Serialization.XmlAnyElement]
So to be clear, When you generate the WSDL from your SalesForce account it may not be correct out of the box. You will need to rename some string arguments that are passed into attributes as well as replace the empty namespace on the "Any" property of the sObject class with the XmlAnyElement attribute in order to get a successful result from the query call of the API. The changes needed to make the client function can be found in this StackOverflow topic topic.
I am now able to successfully call the query function of the SalesForce API and get back the fields I queried for... I feel so much better!

Serialising SOAP parameters in 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.

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