MBoffin.com

Something witty this way comes.

XML-RPC Pinging in C# - Quick and Dirty

In my previous post, I talked about what pinging is and the concept behind how it is accomplished. In this post I will talk about how I figured out how to actually program this site to send a ping.

First off, I should mention that there is a very neat, well-written, well-documented, tidy, used-by-everyone library to add XML-RPC support to your .Net application. It's called XML-RPC.Net and is made by Charles Cook. If you are arriving here just trying to figure out how to add XML-RPC.Net support to your .Net application, I suggest you use Mr. Cook's library.

I have not used XML-RPC.Net simply because one of the main reasons I'm even writing this site engine in the first place is that I like to learn how to do things for myself. I wanted to learn for myself how to actually send an XML-RPC ping using code I wrote with my own bare hands. So while I'm sure XML-RPC.Net would handle my needs beautifully, this is about my want to get my own head around these things, more than my want to "just get the job done".


So where to start? I wanted to be able to ping Technorati.com, so I started at their Ping Configurations page. They list how to get the major blog engines to ping Technorati, and then they have a section on how the XML-RPC request should look if you are building your own. There are a few key pieces of data we can get from their example ping request:
  • The XML-RPC request goes to http://rpc.technorati.com/rpc/ping
  • The HTTP method being used is POST
  • The content type is text/xml
  • The data in the request is XML
  • The first parameter sent is the blog name
  • The second parameter send is the blog URL
From that, we have several good building blocks to work off of. I created a class called Ping and gave it three properties:
  • BlogName (string), default of String.Empty
  • BlogUrl (string), default of String.Empty
  • PingUrl (string), default of "http://rpc.technorati.com/rpc/ping"
I then created a .Send() method to actually send the ping. It returns a string that will be the response we get back from the ping request. Here's what the class looks like so far:

public class Ping {

   // The URL where the ping will be sent
   private string _PingUrl = "http://rpc.technorati.com/rpc/ping";
   public string PingUrl {
      get { return _PingUrl; }
      set { _PingUrl = value; }
   }

   //The URL of the blog
   private string _BlogUrl = String.Empty;
   public string BlogUrl { 
      get { return _BlogUrl; }
      set { _BlogUrl = value; }
   }

   // The name of the blog
   private string _BlogName = String.Empty;
   public string BlogName { 
      get { return _BlogName; }
      set { _BlogName = value; }
   }

   // Send the ping
   public void Send() {}
}

Now the code to actually send the ping needs to be created. Since this is an HTTP request being sent, we need to create a HttpWebRequest object that goes to the URL specified in the PingUrl property. Once created, we need to tell it that the request uses the POST method and that the content type is going to be text/xml.

HttpWebRequest webreqPing = (HttpWebRequest)WebRequest.Create(PingUrl);

webreqPing.Method = "POST";
webreqPing.ContentType = "text/xml";

Now we need to access the stream of data being sent out by this request and fill it with the XML we will be sending as the ping. First, we get the stream and assign it to a Stream object. Now we need to write some XML to that stream. The best way to do this is with an XmlTextWriter object. It's an easy way to build some XML, and even has a constructor that lets you assign a stream for the XML to be dumped into. So here's what gets added:

Stream streamPingRequest = (Stream)webreqPing.GetRequestStream();

XmlTextWriter xmlPing = new XmlTextWriter(streamPingRequest, Encoding.UTF8);

Now that we have something to make XML with, we need to build the ping request itself. We are going to make use of the following methods of the XmlTextWriter class:
  • .WriteStartDocument() - Writes the XML declaration at the start of the XML document (<?xml version="1.0" encoding="UTF-8" ?>)
  • .WriteStartElement("tag") and .WriteEndElement() - Writes the start tag (<tag>) and the end tag (</tag>) of an element.
  • .WriteElementString("tag", "value") - Writes a full start and end tag with the value in between (<tag>value</tag>)
Using these methods, we can build the request. Once we're done building the request, we close the XmlTextWriter, which sends all the XML into the HttpWebRequest's stream. Notice that we insert the blog name and URL where appropriate:

xmlPing.WriteStartDocument();
xmlPing.WriteStartElement("methodCall");
   xmlPing.WriteElementString("methodName", "weblogUpdates.ping");
   xmlPing.WriteStartElement("params");
      xmlPing.WriteStartElement("param");
         xmlPing.WriteElementString("value", BlogName);
      xmlPing.WriteEndElement();
      xmlPing.WriteStartElement("param");
         xmlPing.WriteElementString("value", BlogUrl);
      xmlPing.WriteEndElement();
   xmlPing.WriteEndElement();
xmlPing.WriteEndElement();

xmlPing.Close();

The next is to actually send the ping request and see what kind of response we get back. We'll store the response we get back in an HttpWebResponse object. Then we'll get the stream of data from that response and store it in a StreamReader object. Here's how this looks:

HttpWebResponse webrespPing = (HttpWebResponse)webreqPing.GetResponse();
StreamReader streamPingResponse = new StreamReader(webrespPing.GetResponseStream());

Now that we have the response in a StreamReader, we can save it to a string. After we do that, we don't need the ping response or the stream we sent its data to, so we can close them. Here's what we need to add:

string strResult = streamPingResponse.ReadToEnd();

streamPingResponse.Close();
webrespPing.Close();

Finally, we just need to return the string to end off our .Send() method.

return strResult;

And that's it! Here's the final class, as we've built it above:

public class Ping {

   // The URL where the ping will be sent
   private string _PingUrl = "http://rpc.technorati.com/rpc/ping";
   public string PingUrl { get { return _PingUrl; } set { _PingUrl = value; } }

   //The URL of the blog
   private string _BlogUrl = "http://" + Constants.DefaultDomain;
   public string BlogUrl { get { return _BlogUrl; } set { _BlogUrl = value; } }

   // The name of the blog
   private string _BlogName = Constants.DefaultSiteTitle;
   public string BlogName { get { return _BlogName; } set { _BlogName = value; } }

   // Send the ping
   public string Send() {

      // Create a web request to PingUrl
      HttpWebRequest webreqPing = (HttpWebRequest)WebRequest.Create(PingUrl);

      // Set the properties of the request
      webreqPing.Method = "POST";
      webreqPing.ContentType = "text/xml";

      // Get the stream for the web request
      Stream streamPingRequest = (Stream)webreqPing.GetRequestStream();

      // Create an XML text writer that writes to the web request's stream
      XmlTextWriter xmlPing = new XmlTextWriter(streamPingRequest, Encoding.UTF8);

      // Build the ping, using the BlogName and BlogUrl
      xmlPing.WriteStartDocument();
      xmlPing.WriteStartElement("methodCall");
         xmlPing.WriteElementString("methodName", "weblogUpdates.ping");
         xmlPing.WriteStartElement("params");
            xmlPing.WriteStartElement("param");
               xmlPing.WriteElementString("value", BlogName);
            xmlPing.WriteEndElement();
            xmlPing.WriteStartElement("param");
               xmlPing.WriteElementString("value", BlogUrl);
            xmlPing.WriteEndElement();
         xmlPing.WriteEndElement();
      xmlPing.WriteEndElement();

      // Close the XML text writer, flusing the XML to the stream
      xmlPing.Close();

      // Send the request and store the response, then get the response's stream
      HttpWebResponse webrespPing = (HttpWebResponse)webreqPing.GetResponse();

      StreamReader streamPingResponse = new StreamReader(webrespPing.GetResponseStream());

      // Store the result in a string
      string strResult = streamPingResponse.ReadToEnd();

      // Close the respone's stream and the response itself
      streamPingResponse.Close();
      webrespPing.Close();

      // Return the response (as a string)
      return strResult;
   }
}

Now, as you probably have guessed, this is not quite all that needs to be done. We would need to add error-checking, fail-safe's, and so forth. And not the very least, we need a way of processing that response we got. Just passing it to a string is all well and good, but it doesn't help us much. What if something went wrong with the ping? What if nothing went wrong? With the above code, the program has no way of knowing. And showing the raw response back to the user is not a very nice way of treating the user.

While there is more to be done to actually turn the above class into something really solid, stable, usable and way more object oriented, the above class at least gets you past the first hump of actually getting the ping sent and getting a response back from wherever you sent it.

Why have I laid all of this out here? Because I couldn't find anywhere out there that covered how to send an XML-RPC ping in C#. I found a million references of how to use Charles Cook's XML-RPC.Net library in your code to send an XML-RPC ping, but that's not what I wanted. Without having to pull apart the very generalized XML-RPC library and analyze the code until I could practically write the thing myself, I wanted to just know how to send a ping. Hopefully others can find this of some use.

Replies

Just for kicks, might I suggest a static overload of Send()? Perhaps something like:

public static string Send(string pingURL, string blogURL, string blogName)
{
[see above]
}


And then just have the instance overload (what's the proper term for that?) of the Send() method just be something similar to:

public string Send()
{
return Ping.Send(this.PingURL, this.BlogURL, this.BlogName);
}


I like flexible code.
dcormier - Mar 21, 2005 @ 5:00 PM - Permanent Link
Exactly right. Adding a static method would make it much easier to call from other parts of code. Instead of having to actually instantiate a Ping object, then filll its properties after the fact, you could simply call it outright, passing the needed parameters.
Dylan - Mar 21, 2005 @ 9:49 PM - Permanent Link

Post a Reply

Before you may post, you need to either log in or sign up.