WCF, NuSOAP and ArrayOfString

By Jerome at April 16, 2007 15:21 Tags: , , , ,

When exposing a WebService via WCF, you might want to expose something like this :


[DataContract]
public class SomeContract
{
  [DataMember] 
  public string[] Values { get; set; }
}

For that particular data contract, WCF will be generating a WSDL with something like this :


<xs:complexType name="SomeContract">

  <xs:sequence>

    <xs:element minOccurs="0" name="Values" nillable="true"

                type="q1:ArrayOfstring"

                xmlns:q1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />

  </xs:sequence>

</xs:complexType>

With ArrayOfString being defined like this :


  <xs:schema elementFormDefault="qualified"

             targetNamespace="http://schemas.microsoft.com/2003/10/Serialization/Arrays"

             xmlns:xs="http://www.w3.org/2001/XMLSchema"

             xmlns:tns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">

    <xs:complexType name="ArrayOfstring">

      <xs:sequence>

        <xs:element minOccurs="0" maxOccurs="unbounded" name="string" nillable="true" type="xs:string"/>

      </xs:sequence>

    </xs:complexType>

    <xs:element name="ArrayOfstring" nillable="true" type="tns:ArrayOfstring"/>

  </xs:schema>

In general, that would be fine. The type "ArrayOfString" is defined in a different namespace, but this should not be a problem.

So, to use that particular type in a method call, you should have a document like this one :


<SomeContract>

  <Values xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">

    <a:string>My Value</a:string>

  </Values>

</SomeContract>

"string" elements are contained in a different namespace from the SomeContract element. However, the NuSOAP stock version 0.7.2 has a problem with that kind of schema, and generates instead something like this :


<SomeContract>

  <Values>

    <string>My Value</string>

  </Values>

</SomeContract>


When the WCF deserializer receives a document like this one, it does not find the "Values" member in the namespace he's looking and ends up creating a SomeContract instance with a null array of strings.

Since there's no way of fixing NuSOAP, you may need to tweak your contract to help NuSOAP serializing your data without a namespace.

The CollectionDataContract attribute seems to be the way to go, since there is a way to specify the namespace to use when generating the metadata. The service contract then looks like this :


  [DataContract(Namespace = "http://my.name.space")] 
  public class SomeContract 
  { 
    [DataMember] 
    public ArrayOfString InvalidIdentifiers { get; set; } 
  } 

  [CollectionDataContract(ItemName="string", Namespace="http://my.name.space")]
  public class ArrayOfString : List<string> { } 

Thereby placing everything in the "http://my.name.space" namespace.

You might need to tweak a bit the ArrayOfString class, especially if you need to assign it from an actual string[] instance, but you get the idea.

Playing with WCF and NuSOAP 0.7.2

By Jerome at January 25, 2007 10:24 Tags: , , , ,

.NET 3.0 has now been released, along with Windows Communication Foundation (WCF). I thought I could give it a shot for one of my projects at work, where I have to create an internal web service. The problem is that the client at the other end will be using NuSOAP 0.7.2 to contact this WebService, so I had to make sure it would work fine.

First observation, compared to ASP.NET generated WebService, the WCF wsdl is much more complex. Actually, it has a little bit more information such as the validation rules for a guid, but it also has its schema split up into multiple XSD files. I was a bit worried that NuSOAP wouldn't handle that well, but it does fine... I also wanted to be able to expose a signature like this :

1:     [DataContract]
2:     public class Parameter
3:     {
4:         private string _key;
5:         private object _value;
6:  
7:         [DataMember]
8:         public string Key
9:         {
10:             get { return _key; }
11:             set { _key = value; }
12:        }
13:  
14:        [DataMember]
15:        public object Value
16:        {
17:             get { return _value; }
18:             set { _value = value; }
19:        }
20:    }

You'll notice that the second property is an object, and that implies that the serializer/deserializer handles properly types defined at runtime, by using xsd:anyType in the schema.

So, after a few attempts to get working linux LiveCD distro, for which none of them had PHP compiled the correct --enable-soap flag, I decided to fall back from the native PHP SOAP extensions to the OpenSource SOAP library NuSOAP and use EasyPHP on Windows.

First, I had to change NuSOAP's response encoding to UTF-8 using soap_defencoding, which seems to be the default for WCF, then I had to figure out how to pass an arry of structures to call my method.

So, for the following signature :

    void MyMethod(string a, string b, Parameter[] parameters)

The caller's php parameter structure should be :

$params = array(
'a' => $a,
'b' => $b,
'parameters' => array(
'Parameter' => array(
array('Key' => 'abc', 'Value' => 10),
array('Key' => 'def', 'Value' => 42)
)
),
); 

Notice that you have to place a "Parameter" element inside the "parameters" parameter.

Then, to call the method, use the following line :

    $client->call("MyMethod", array($params));

by encapsulating the parameter array once again. This makes a lot of levels to call one little function... I prefer the .NET way of doing thing :)

An other problem came up right at this point : The array, although the information being in the soap body, was not filled on the .NET side. After comparing with a .NET to WCF call, I figured that there was a missing namespace. This is what is generated by default with the code I've presented above using NuSOAP :

<parameters>
<Parameter>
<Key>abc</Key>
<Value>10</Value>
</Parameter>
<Parameter>
<Key>def</Key>
<Value>42</Value>
</Parameter>
</parameters>

And this is what .NET is generating :

<parameters>
<Parameter xmlns="http://schemas.datacontract.org/2004/07/MyService">
<Key>abc</Key>
<Value>10</Value>
</Parameter>
<Parameter xmlns="http://schemas.datacontract.org/2004/07/MyService">
<Key>def</Key>
<Value>42</Value>
</Parameter>
</parameters>

For some reason, the WCF basicHttp binding point is generating two different namespaces for the service contract and for the data contract. To fix this, you just have to specify explicitly a namespace for each ServiceContract and DataContract attribute of your service :

    [DataContract("http://mywebservice.example.com")]

The other problem was about using an unspecified data type as a member of a structure, a System.Object in my case. Well, it turns out that NuSOAP does not support it, as it does not include the data type of the serialized element, so the WCF deserializer cannot interpret it. I changed the data type back to string, unfortunately losing the type information. I can get it from somewhere else but still, this can lead to serialization problems related to culture (comma being a dot and the opposite depending on systems, for instance).

Anyway, there are a few things to remember to get things to work fine with NuSOAP :

  • Change the encoding to UTF-8 or whatever encoding you choose to use,
  • Don't forget to specify the name of the type of an element in an array in PHP, 
  • Do not expose unspecified parameters or attributes,
  • Explicitly specify the namespace of each DataContract and ServiceContact attribute of your service.

It's been a while since I've written a line of PHP code, and I didn't miss it at all. I'm going back to WCF now :)

About me

My name is Jerome Laban, I am a Software Architect, C# MVP and .NET enthustiast from Montréal, QC. You will find my blog on this site, where I'm adding my thoughts on current events, or the things I'm working on, such as the Remote Control for Windows Phone.