How to reduce WCF soap message size to save bandwidth? - c#

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

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:

Generating WCF web service (SOAP) from WSDL to accept/return XML rather than serialized types

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...
}

RequestSecurityTokenResponse won't deserialize

I've got a WCF client connecting to an STS server, which I don't have any control over (it's a 3rd party PHP service).
After days of research I managed to talk to the server in a way it accepts using purely WCF. Of course, it would have been easy to just put some characters onto the network, ignoring all the SOAP stuff. But in the end I managed to guess every configuration parameter right, so the STS service answers my request like this
<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
<Body>
<RequestSecurityTokenResponse xmlns="http://schemas.xmlsoap.org/ws/2005/02/trust">
<TokenType>http://schemas.xmlsoap.org/ws/2005/02/sc/sct</TokenType>
<RequestedSecurityToken>
<SecurityContextToken xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<Identifier>bipro:D5J9M0...</Identifier>
</SecurityContextToken>
</RequestedSecurityToken>
</RequestSecurityTokenResponse>
</Body>
</Envelope>
But now I have trouble to extract the identifier value. For my proxy class (SecurityTokenServicePortTypeClient : ClientBase<SecurityTokenServicePortType>, SecurityTokenServicePortType) I tried every imaginable combination of ServiceContract, DataContract and XmlSerialization on all the types. But all I get is null.
The (heavily modified) interface for the service contract looks like this
[ServiceContract(Namespace = "http://schemas.xmlsoap.org/ws/2005/02/trust")]
public interface SecurityTokenServicePortType {
[OperationContract(Action = "urn:RequestSecurityToken", ReplyAction = "*")]
object RequestSecurityToken();
}
The (heavily modified) implementing class has a method like this
object SecurityTokenServicePortType.RequestSecurityToken() {
var x = base.Channel.RequestSecurityToken();
return x;
}
x is always null.
Instead of a return type of object it was originally RequestSecurityTokenResponse and so on.
I had the same problem with WSE years ago and I was able to solve that by just using the right combination of for example XmlElementAttribute to control the deserialization process. But this time it doesn't seem to help.
Thanks for any advice!
Björn

.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.

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.

Categories