Thursday, April 8, 2010

Consuming XML-RPC web services with C# - Part 2 - the OOP approach

At Part 1 I described a basic way to call and consume data from xml-rpc web services based on XML string operations.

This can be only the first approach. It's unhandy and not easy to maintain.

The next level for me would be to cast this into an object oriented data structure that could be easily maintained and customized for special needs.

At this post I try to show my object oriented approach to consume such services.

At first, we design the object model to call a method of a service. Like at Part 1 I will use the Bugzilla XML-RPC web service.

Following the specification a request must have the xml structure shown below:
<?xml version="1.0"?>
<methodCall>
  <methodName>Service.method</methodName>
  <params>
    <param>
      <value><i4>41</i4></value>
    </param>
  </params>
</methodCall>
  • methodName tags holds the name of the method to be called
  • params collection hold the parameter each with a single
    • value with value type
      • four-byte signed integer
      • boolean
      • string
      • double-precision signed floating point number
      • datetime
      • base64-encoded binary
The value could also be a struct or an array. For description take a look at xmlrpc.com.

Bugzilla uses the struct format so we have to change the xml to the following:
<?xml version="1.0"?>
<methodCall>
  <methodName>Service.method</methodName>
  <params>
    <param>
      <value>
        <struct>
          <member>
            <name>memerName</name>
            <value><string>memverValue</string></value>
          </member>
        </struct>
      </value>
    </param>
  </params>
</methodCall>

Based on this we are designing our object model like this:

The serialization of this structure is controlled by xml serialization attributes like XmlElement, XmlArray and XmlArrayItem:
[XmlRoot("methodCall")]
public class Request 
{
   [XmlElement("methodName")]
   public string Method { get; set; }

   [XmlArray("params"), XmlArrayItem("param")]
   public List Params { get; set; }
}

public class Param
{
   [XmlElement("value")]
   public Value Value { get; set; }
}

public class Value
{
   [XmlArray("struct"), XmlArrayItem("member")]
   public List<member> Member { get; set; }
}

public class Member
{
   [XmlElement("name")]
   public string Name { get; set; }

   [XmlElement("value")]
   public MemberValue Value { get; set; }
}

public class MemberValue
{
   [XmlChoiceIdentifier("ValueChoice"), XmlElement("int", typeof(int)), XmlElement("string", typeof(string)), XmlElement("datetime", typeof(DateTime)), XmlElement("double", typeof(double)), XmlElement("base64", typeof(string)), XmlElement("array", typeof(ArrayList))]
   public object Value { get; set; }

   [XmlIgnore]
   public virtual ValueType ValueChoice { get; set; }

   public enum ValueType
   {
      @string,
      @int,
      @datetime,
      @double,
      base64,
      array,
   }
}

This enables you to perform XML-RPC calls more like the object way. The example I placed at the last Part looks like this now:
// building up the request
Request requestObj = new Request()
{
   Method = "Bugzilla.version"
};

// to remove namespaces
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add(String.Empty, String.Empty);

// serialize request
using (MemoryStream memStream = new MemoryStream())
{
   using (XmlTextWriter writer = new XmlTextWriter(memStream, Encoding.UTF8))
   {
      XmlSerializer serializer = new XmlSerializer(typeof(Request));
      serializer.Serialize(writer, requestObj, namespaces);
   }

   requestData = memStream.ToArray();
}

//
// Excecute request
//
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://bugzilla.mozilla.org/xmlrpc.cgi");
request.Method = "POST";
request.UserAgent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729;)";
request.ContentType = "text/xml";
request.ContentLength = requestData.Length;

// send request data
using (Stream requestStream = request.GetRequestStream())
   requestStream.Write(requestData, 0, requestData.Length);

// get the result back
Response result = null;
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
   using (Stream stream = response.GetResponseStream())
   {
      // deserialize result
      XmlSerializer deSerializer = new XmlSerializer(typeof(Response));
      result = (Response)deSerializer.Deserialize(stream);
   }
}

The Response class has largely the same structure like the Request class.

And therefore I like and had read an article about short time here's the OOP way with fluent interfaces:
Response fluent = new Request()
   .WithMethod("Bugzilla.version")
   .Excecute("https://bugzilla.mozilla.org/xmlrpc.cgi");

Have fun!

UPDATE 2013-08-29:
Pointed by comments I missed to show you what the result classes look like. So here they are:
[XmlRoot("methodResponse", Namespace = "")]
public class Response
{
   [XmlElement("fault", Namespace = "")]
   public Fault Fault { get; set; }

   [XmlElement("params", Namespace = "")]
   public List Params { get; set; }
}

public class ResponseParams
{
   [XmlElement("param", Namespace = "")]
   public ResponseParam Param { get; set; }
}

public class ResponseParam
{
   [XmlElement("value", Namespace = "")]
   public ResponseValue Value { get; set; }
}

public class ResponseValue
{
   [XmlArray("struct", Namespace = ""), XmlArrayItem("member", Namespace = "")]
   public List Member { get; set; }
}

public class Member
{
   [XmlElement("name", Namespace = "")]
   public string Name { get; set; }

   [XmlElement("value", Namespace = "")]
   public MemberValue Value { get; set; }
}

public class MemberValue
{
   [XmlChoiceIdentifier("ValueChoice"),
   XmlElement("int", typeof(int), Namespace = ""),
   XmlElement("string", typeof(string), Namespace = ""),
   XmlElement("datetime", typeof(DateTime), Namespace = ""),
   XmlElement("double", typeof(double), Namespace = ""),
   XmlElement("base64", typeof(string), Namespace = ""),
   XmlElement("array", typeof(ArrayList), Namespace = "")]
   public object Value { get; set; }

   public enum ValueType
   {
      @string,
      @int,
      @datetime,
      @double,
      base64,
      array
   }

   [XmlIgnore]
   public virtual ValueType ValueChoice { get; set; }
}

Accessing the result via this structure could be little weird, so I've added a XSLT-Transform to put the result into more usable class:
var xslTransform = new XslCompiledTransform();
   xslTransform.Load(@"Part2\TransformResponse.xslt");

// memstream holds data from response
using (var xmlReader = XmlReader.Create(memStream))
   xslTransform.Transform(xmlReader, null, destinationStream);

/// snip

[XmlRoot("methodResponse", Namespace = "")]
public class VersionResult
{
   [XmlElement("faultString", Namespace = "")]
   public string FaultString { get; set; }

   [XmlElement("faultCode", Namespace = "")]
   public string FaultCode { get; set; }

   [XmlElement("version", Namespace = "")]
   public string Version { get; set; }
}
where the xsl looks something like this:

   

   
      
         
         
      
   

   
      <
      
      >
      
      </
      
      >
   


Now we are able to access the result in a more handy way
var version = (VersionResult)xmlSerializer.Deserialize(destinationStream);

var serverVersion = version.Version;
// instead of
serverVersion = result.Params[0].Param.Value.Member[0].Value.Value;

The codes of this sample are now available at github.

Update 2014-06-07:
Added simple method call as mentioned by Alfredo to code samples.

10 comments:

  1. this is what i am looking for butCan you please show us the response class structure code for response

    ReplyDelete
    Replies
    1. Hello jackob, I've updated the post so that the response classes are show.

      Delete
  2. how can we add a parameter and value to a call

    ReplyDelete
    Replies
    1. should be done like this:
      var requestObj = new Request
      {
      Method = "Bugzilla.version",
      Params =
      new List<Param>(new[]
      {
      new Param
      {
      Value =
      new Value
      {
      Member =
      new List(new[]
      {
      new Member
      {
      Name = "[valuename]",
      Value =
      new MemberValue
      {
      ValueChoice = MemberValue.ValueType.@string,
      Value = "[value]"
      }
      },
      })
      }
      },
      })
      };

      Delete
    2. hello torsten just want to ask if we had a response array of values how we can modify the response class in order to access the array items for example if we had a response like below:

      - " methodResponse "
      - " params "
      - " param "
      - " value "
      - " struct "
      - " member "
      " name " events " /name "
      - " value "
      - " array "
      - " data "
      - " value "
      - " struct "
      - " member "
      " name " conference " /name "
      - " value "
      - " struct "
      - " member "
      " name " uniqueId " /name "
      - " value " " int " 23058 " /int " " /value "
      " /member "
      - " member "
      " name " name " /name "
      - " value " " string " test " /string " " /value "
      " /member " " /struct " " /value "
      " /member "
      + " member "
      + " member "
      " /struct "
      " /value "
      + " value "
      + " value "
      " /data "
      " /array "
      thanks in advance

      Delete
    3. To get XMLSerializer into it seems to get weird.

      What do you want to achive?
      Maybe you should take a look at http://codinghints.blogspot.de/2010/04/consuming-xml-rpc-web-services-with-c.html

      If you want further assistance, please send me a mail to cswyzvxu@objectmail.com (valid 1 week)

      Delete
  3. HI Torsten thank you very much for the replies
    and for updating your blog it was really helpful thanks again.

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. Hello, if the method to be call doesn't wait a struct but a simple string or a list of two parameters (for example a string and an int), how to define it? When I use the request object in such methods comes the error that a string is wait instead a struct.
    Best regards
    Alfredo

    ReplyDelete
    Replies
    1. Hi Alfredo, I've updated the code samples to support calls with simple arguments.

      Take a look at github.

      Delete