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.
Related
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:
I have a large WSDL file that I need to generate a WCF Web service from. I can generate a service using svcutil.exe, however it's not generating what I need.
I need a service that accepts/returns XML, rather than serialized types. The reason for this is if there is an error in the incoming XML it will fail before it hits my code - we can't have this. We need to intercept the XML before any serialization happens to catch it.
Is this possible?
Or is there a way I can modify the generated services so I can work with the raw XML rather than the derived "Message" type?
Effecitvely I want something similar to:
XmlDocument PersonRevised(XmlDocument request);
Current code:
[ServiceContract(Namespace = "urn:hl7-org:v3")]
public interface IPRPA_AR101202
{
[OperationContract(Name = "PersonRevised", Action = "urn:hl7-org:v3/PRPA_IN101204")]
PersonRevisedResponse PersonRevised(Message request);
}
public class PRPA_AR101202 : WCFServiceBase, IPRPA_AR101202
{
PersonRevisedResponse IPRPA_AR101202.PersonRevised(Message request)
{
PersonRevised pr = this.ParseMessage<PersonRevised>(request, HL7_XML_NAMESPACE);
PersonRevisedResult result = new PersonRevisedResult();
PersonRevisedResponse r = new PersonRevisedResponse(result);
return r;
}
}
update:
Based on the answer I was able to create a WCF service that accepted a string, however now I am getting null on the implemented services that are based off of WSDL contracts( on the input parameter); regardless of whether it's a string or an XmlDocument/XmlNode.
Thoughts?
I need a service that accepts/returns XML, rather than serialized
types
In that case you are better off using POX and not using SOAP/WSDL at all. There are some resources for this here and here.
The reason for this is if there is an error in the incoming XML it
will fail before it hits my code - we can't have this.
I kind of know what you're saying here. It is annoying that any serialization exceptions will kill the channel rather than bubble back to the client, however, the whole point of exposing a service metadata endpoint is that clients will always serialize types which are exposed outside the service boundary correctly because that's what the WSDL is supposed to be for.
Effecitvely I want something similar to: XmlDocument
PersonRevised(XmlDocument request);
As you are no doubt aware, exposing a XmlDocument type is not equivalent to exposing XML. Exposing XmlDocument will not be pretty.
If you absolutely need full control over the deserialization, you will have to expose your operation as accepting a parameter of type string. Then you can do what you want with it.
public string PersonRevised(string request)
{
// Deserialize here...
}
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
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.
A problem with "Add Service Reference", and actually with SvcUtil over all its features.
In order to reproduce you just need to add an OperationContract with argument or returning the following class :
[XmlSchemaProvider("MySchema")]
public class MyStructure : IXmlSerializable
{
private XmlElement e;
private static void Func(object o, ValidationEventArgs args)
{
}
public static XmlQualifiedName MySchema(XmlSchemaSet xs)
{
//xs.XmlResolver = new XmlUrlResolver();
XmlSchema s = XmlSchema.Read(new XmlTextReader(new StringReader("<?xml version=\"1.0\"?><xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"><xs:complexType name=\"MyStructure\"><xs:sequence><xs:any /></xs:sequence></xs:complexType></xs:schema>")), null);
xs.Add(s);
return new XmlQualifiedName("MyStructure");
}
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema()
{
throw new NotImplementedException();
}
public void ReadXml(XmlReader reader)
{
XmlDocument doc = new XmlDocument();
e = (XmlElement)doc.ReadNode(reader);
}
public void WriteXml(XmlWriter writer)
{
e.WriteTo(writer);
}
#endregion
}
The result is that when you use AddWebReference or AddSerivceReference without a reference to the class library containing the MyStructure type, everything will be fine ad you will get an xmlElement representation at the auto created proxy.
However, when you have a reference you will get the following warning :
================
Warning 1 Custom tool warning: Cannot import wsdl:portType
Detail: An exception was thrown while running a WSDL import extension: System.ServiceModel.Description.DataContractSerializerMessageContractImporter
Error: Referenced type 'ServiceLibrary.MyStructure, ServiceLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' with data contract name 'MyStructure' in namespace '' cannot be used since it does not match imported DataContract. Need to exclude this type from referenced types.
XPath to Error Source: //wsdl:definitions[#targetNamespace='http://tempuri.org/']/wsdl:portType[#name='IService1'] \Projects\WCFSample\WCFExample\TestAddReference\Service References\ServiceReference1\Reference.svcmap 1 1 TestAddReference
======================
And no proxy will be generated for you.
Now, the internet is full with descriptions of this when you have a generic DataContract, and/or using IsReference attribute.
This is a much serious problem, since any non-typed data will do this problem.
Could not find any way to solve the problem. What if I want to know the type at the client side, by sharing the class library of the contracts ?
This type of exception generally means there is at least one difference in the type contracts generated by the service as compared to the referenced types (as the message indicates!). But it may not be obvious at first glance, as I found out. Make sure all nested and referenced types are up to date with the server. In my case, nested types were updated on the server. I thought I had updated by locally referenced assembly (and the shared reference types) but I missed some. It took close examination to find the culprit.
See additional information in this question
I have a suggestion:
I had similar errors, including:
the .svcmap file cannot be found. It may have been moved or deleted. To generate a new .svcmap file, delete the service reference and add it again.
And at that point, no way to delete the service reference unless I close VS2010 and open it again.
The situation is: my WCF service is running, I programmatically added a Description.ServiceMetadataBehavior at an HTTP address that I define.
In VS2010, I try to add a service reference at the HTTP address, I see my service, I add the reference, and voila, errors and warning.
The problem: my HTTP address is containing some key words that WCF doesn't like. Specifically the word COM (it breaks with LPT too).
So my solution: modify my HTTP address not to have the word COM. It worked for me.
If the service is hosted over HTTPS, go into the server's IIS Manager. Under "SSL Settings" for the site, make sure "Require SSL" is checked, and check the Client Certificates radio button for "Accept".