While investigating how to make C# and PHP interact with each other I run into an interesting extension of WCF Service Model for working over XML-RPC protocol. I played a bit with this extension and after fixing some bug in its source code I made it work with my little custom XML-RPC server implemented as Joomla XML-RPC plugin.
The bug was in the XML parsing code. For example, if my XML-RPC server returns the following XML:
<methodResponse>
<params>
<param>
<value>
<struct>
<member>
<name>total_charcount</name>
<value>
<i4>11005</i4>
</value>
</member>
<member>
<name>total_spacecount</name>
<value>
<i4>1433</i4>
</value>
</member>
</struct>
</value>
</param>
</params>
</methodResponse>
I got XmlException with message “Start element ‘member’ expected. Found end element ‘struct’ from namespace ”. Line 28, position 10.’ To fix this problem I made the following changes in XmlRpcDataContractSerializer.cs:
private static object DeserializeStruct(XmlDictionaryReader reader, Type targetType)
{
if (targetType.IsDefined(typeof(DataContractAttribute), false))
{
Dictionary<string, MemberInfo> dataMembers = GetDataMembers(targetType);
object targetObject = Activator.CreateInstance(targetType);
reader.ReadStartElement(XmlRpcProtocol.Struct);
//Fixed by Dmitriano
//reader.NodeType returns Whitespace instead of some element
//so I replaced it with reader.MoveToContent()
//while (reader.NodeType != XmlNodeType.EndElement)
while (reader.MoveToContent() != XmlNodeType.EndElement)
{
string memberName;
reader.ReadStartElement(XmlRpcProtocol.Member);
reader.ReadStartElement(XmlRpcProtocol.Name);
memberName = reader.ReadContentAsString();
reader.ReadEndElement();
reader.ReadStartElement(XmlRpcProtocol.Value);
reader.MoveToContent();
if (dataMembers.ContainsKey(memberName))
{
MemberInfo member = dataMembers[memberName];
if (member is PropertyInfo)
{
((PropertyInfo)member).SetValue(
targetObject,
Deserialize(reader, ((PropertyInfo)member).PropertyType),
BindingFlags.Instance | BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.NonPublic,
null, null,
CultureInfo.CurrentCulture);
}
else if (member is FieldInfo)
{
((FieldInfo)member).SetValue(
targetObject,
Deserialize(reader, ((FieldInfo)member).FieldType),
BindingFlags.Instance|BindingFlags.SetField|BindingFlags.Public|BindingFlags.NonPublic,
null,
CultureInfo.CurrentCulture);
}
}
reader.ReadEndElement(); // value
reader.ReadEndElement(); // member
}
reader.ReadEndElement(); // struct
reader.MoveToContent();
return targetObject;
}
else
{
throw new InvalidOperationException();
}
}
Now it can even parse more complex data structures like this:
<methodResponse>
<params>
<param>
<value>
<struct>
<member>
<name>total_charcount</name>
<value>
<i4>11005</i4>
</value>
</member>
<member>
<name>total_spacecount</name>
<value>
<i4>1433</i4>
</value>
</member>
<member>
<name>summaries</name>
<value>
<array>
<data>
<value>
<struct>
<member>
<name>article_id</name>
<value>
<i4>272</i4>
</value>
</member>
<member>
<name>title</name>
<value>
<string>Аллантоин</string>
</value>
</member>
<member>
<name>charcount</name>
<value>
<i4>1476</i4>
</value>
</member>
<member>
<name>spacecount</name>
<value>
<i4>189</i4>
</value>
</member>
</struct>
</value>
<value>
<struct>
<member>
<name>article_id</name>
<value>
<i4>274</i4>
</value>
</member>
<member>
<name>title</name>
<value>
<string>Кислота стеариновая 98%</string>
</value>
</member>
<member>
<name>charcount</name>
<value>
<i4>1505</i4>
</value>
</member>
<member>
<name>spacecount</name>
<value>
<i4>196</i4>
</value>
</member>
</struct>
</value>
</data>
</array>
</value>
</member>
</struct>
</value>
</param>
</params>
</methodResponse>
in C# this XML turns into
[DataContract(Namespace="http://site.com/data")]
public struct ContentSummary
{
[DataMember]
public int article_id;
[DataMember]
public string title;
[DataMember]
public int charcount;
[DataMember]
public int spacecount;
};
[DataContract(Namespace="http://site.com/data")]
public struct TotalContentSummary
{
[DataMember]
public int total_charcount;
[DataMember]
public int total_spacecount;
[DataMember]
public ContentSummary[] summaries;
};
After doing some further experimentation I realized that this handmade extension can not handle ObservableCollection or Generic.List. If I generate WCF client proxy in a way described in article How To Create A WCF Client Proxy Without Having a Deployed WCF Service and specify ObservableCollection as the collection type with the following commands:
svcutil /d:.\gen MyApp.exe svcutil *.wsdl *.xsd /l:C# /out:Reference.cs /noconfig /s /ct:System.Collections.ObjectModel.ObservableCollection`1 /ser:Auto /tcv:Version35 /edb /r:"c:\Progra~1\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll"
it throws InvalidOperationException in XmlRpcDataContractSerializer.cs at this point:
public static object Deserialize(XmlDictionaryReader reader, Type targetType)
{
object returnValue = null;
if (reader.IsStartElement())
{
switch (reader.LocalName)
{
case XmlRpcProtocol.Nil:
returnValue = null;
break;
case XmlRpcProtocol.Bool:
returnValue = Convert.ChangeType((reader.ReadElementContentAsInt()==1),targetType);
break;
case XmlRpcProtocol.ByteArray:
if (targetType == typeof(Stream))
{
returnValue = new MemoryStream(reader.ReadElementContentAsBase64());
}
else
{
returnValue = Convert.ChangeType(reader.ReadElementContentAsBase64(), targetType);
}
break;
case XmlRpcProtocol.DateTime:
returnValue = Convert.ChangeType(reader.ReadElementContentAsDateTime(),targetType);
break;
case XmlRpcProtocol.Double:
returnValue = Convert.ChangeType(reader.ReadElementContentAsDouble(),targetType);
break;
case XmlRpcProtocol.Int32:
case XmlRpcProtocol.Integer:
returnValue = Convert.ChangeType(reader.ReadElementContentAsString(),targetType);
break;
case XmlRpcProtocol.String:
if (targetType == typeof(Uri))
{
returnValue = new Uri(reader.ReadElementContentAsString());
}
else
{
returnValue = Convert.ChangeType(reader.ReadElementContentAsString(), targetType);
}
break;
case XmlRpcProtocol.Struct:
returnValue = DeserializeStruct(reader, targetType);
break;
case XmlRpcProtocol.Array:
if (targetType.IsArray || targetType is IEnumerable || targetType is IList || targetType is ICollection)
{
reader.ReadStartElement(XmlRpcProtocol.Array);
ArrayList arrayData = new ArrayList();
reader.ReadStartElement(XmlRpcProtocol.Data);
reader.MoveToContent();
while (reader.IsStartElement(XmlRpcProtocol.Value))
{
reader.ReadStartElement();
arrayData.Add(Deserialize(reader, targetType.GetElementType()));
reader.ReadEndElement();
reader.MoveToContent();
}
if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == XmlRpcProtocol.Data)
{
reader.ReadEndElement();
}
reader.ReadEndElement();
if (targetType is IEnumerable || targetType is IList || targetType is ICollection)
{
returnValue = arrayData;
}
else
{
returnValue = arrayData.ToArray(targetType.GetElementType());
}
}
else
{
throw new InvalidOperationException();
}
break;
}
}
return returnValue;
}
so I need to leave default collection type that is Array and generate client proxy with the following command:
svcutil *.wsdl *.xsd /l:C# /out:Reference.cs /noconfig /s /ser:Auto /tcv:Version35 /edb


Works wonderfully! 🙂