C# wrapper for the Google AJAX Language API

by Alex Meyer-Gleaves 28 April 2009 - 6:09 PM

Introduction

The Google AJAX Language API allows you to perform text translations using a REST based web service API. Being an AJAX targeted API the web service returns a JSON formatted response that is easy to work with in JavaScript. Working with REST web services and JSON responses in C# is also easy. I decided to check out the API by writing a C# wrapper that would allow me to easily translate a string in any of the supported languages. A quick search will show that I am certainly not the first person to do this, but I don’t care as I wanted to do it my way and for myself.

Response Classes

The DataContractJsonSerializer added to the .NET Framework 3.5 makes it easy to serialize and deserialize between JSON and CLR objects. I created classes to represented the JSON response from the web service. The first is the TranslationResponse. You will notice I have used the DataContract and DataMember attributes so I can map my Pascal cased property names to the camel cased property names used in the JSON response. Even when working with JSON it feels dirty when I don’t follow my naming conventions.

/// <summary>
///     The translation response returned from Google.
/// </summary>
[DataContract(Name = "translateResponse")]
public class TranslationResponse
{
    /// <summary>
    ///     Gets or sets the response data.
    /// </summary>
    [DataMember(Name = "responseData")]
    public ResponseData ResponseData { get; set; }

    /// <summary>
    ///     Gets or sets the response details.
    /// </summary>
    /// <remarks>
    ///     This value is only present when the request fails
    ///     and will contain a diagnostic string.
    /// </remarks>
    [DataMember(Name = "responseDetails")]
    public string ResponseDetails { get; set; }

    /// <summary>
    ///     Gets or sets the response status.
    /// </summary>
    /// <remarks>
    ///     A status other than 200 indicates failure.
    /// </remarks>
    [DataMember(Name = "responseStatus")]
    public int ResponseStatus { get; set; }
}

The second class is ResponseData that contains the translated text and source language if it was automatically detected. This occurs when the source language is not provided in the request.

/// <summary>
///     The data part of the response from Google.
/// </summary>
[DataContract(Name = "responseData")]
public class ResponseData
{
    /// <summary>
    ///     Gets or sets the translated text.
    /// </summary>
    [DataMember(Name = "translatedText")]
    public string TranslatedText { get; set; }

    /// <summary>
    ///     Gets or sets the detected source language.
    /// </summary>
    /// <remarks>
    ///     This value is only present when the source language was not provided
    ///     in the request and needed to be detected automatically.
    /// </remarks>
    [DataMember(Name = "detectedSourceLanguage")]
    public string DetectedSourceLanguage { get; set; }
}

Request Helpers

I created some classes to help make the request simple to construct. The Language class contains a property for each of the supported languages. The properties return two letter ISO language names that are used in the request. I grabbed the list from the documentation and with a quick bit of string replacement had the C# class ready to go. You didn’t really think I typed them all out did you?

/// <summary>
///     The languages supported by the Google AJAX Language API.
/// </summary>
public static class Language
{
    public static readonly string Afrikaans = "af";
    public static readonly string Albanian = "sq";
    public static readonly string Amharic = "am";
    public static readonly string Arabic = "ar";
    public static readonly string Armenian = "hy";
    public static readonly string Azerbaijani = "az";
    public static readonly string Basque = "eu";
    public static readonly string Belarusian = "be";
    public static readonly string Bengali = "bn";
    public static readonly string Bihari = "bh";
    public static readonly string Bulgarian = "bg";
    public static readonly string Burmese = "my";
    public static readonly string Catalan = "ca";
    public static readonly string Cherokee = "chr";
    public static readonly string Chinese = "zh";
    public static readonly string ChineseSimplified = "zh-CN";
    public static readonly string ChineseTraditional = "zh-TW";
    public static readonly string Croatian = "hr";
    public static readonly string Czech = "cs";
    public static readonly string Danish = "da";
    public static readonly string Dhivehi = "dv";
    public static readonly string Dutch = "nl";
    public static readonly string English = "en";
    public static readonly string Esperanto = "eo";
    public static readonly string Estonian = "et";
    public static readonly string Filipino = "tl";
    public static readonly string Finnish = "fi";
    public static readonly string French = "fr";
    public static readonly string Galician = "gl";
    public static readonly string Georgian = "ka";
    public static readonly string German = "de";
    public static readonly string Greek = "el";
    public static readonly string Guarani = "gn";
    public static readonly string Gujarati = "gu";
    public static readonly string Hebrew = "iw";
    public static readonly string Hindi = "hi";
    public static readonly string Hungarian = "hu";
    public static readonly string Icelandic = "is";
    public static readonly string Indonesian = "id";
    public static readonly string Inuktitut = "iu";
    public static readonly string Italian = "it";
    public static readonly string Japanese = "ja";
    public static readonly string Kannada = "kn";
    public static readonly string Kazakh = "kk";
    public static readonly string Khmer = "km";
    public static readonly string Korean = "ko";
    public static readonly string Kurdish = "ku";
    public static readonly string Kyrgyz = "ky";
    public static readonly string Laothian = "lo";
    public static readonly string Latvian = "lv";
    public static readonly string Lithuanian = "lt";
    public static readonly string Macedonian = "mk";
    public static readonly string Malay = "ms";
    public static readonly string Malayalam = "ml";
    public static readonly string Maltese = "mt";
    public static readonly string Marathi = "mr";
    public static readonly string Mongolian = "mn";
    public static readonly string Nepali = "ne";
    public static readonly string Norwegian = "no";
    public static readonly string Oriya = "or";
    public static readonly string Pashto = "ps";
    public static readonly string Persian = "fa";
    public static readonly string Polish = "pl";
    public static readonly string Portuguese = "pt-PT";
    public static readonly string Punjabi = "pa";
    public static readonly string Romanian = "ro";
    public static readonly string Russian = "ru";
    public static readonly string Sanskrit = "sa";
    public static readonly string Serbian = "sr";
    public static readonly string Sindhi = "sd";
    public static readonly string Sinhalese = "si";
    public static readonly string Slovak = "sk";
    public static readonly string Slovenian = "sl";
    public static readonly string Spanish = "es";
    public static readonly string Swahili = "sw";
    public static readonly string Swedish = "sv";
    public static readonly string Tagalog = "tl";
    public static readonly string Tajik = "tg";
    public static readonly string Tamil = "ta";
    public static readonly string Telugu = "te";
    public static readonly string Thai = "th";
    public static readonly string Tibetan = "bo";
    public static readonly string Turkish = "tr";
    public static readonly string Uighur = "ug";
    public static readonly string Ukrainian = "uk";
    public static readonly string Unknown = "";
    public static readonly string Urdu = "ur";
    public static readonly string Uzbek = "uz";
    public static readonly string Vietnamese = "vi";
}

I also created a TextFormat enumeration that allows you to specify the format of the text to be translated. The only choices are HTML or plain text. If the URL argument for “format” is not provided the Language API assumes that the input is plain text.

/// <summary>
///     The format of the text to be translated.
/// </summary>
public enum TextFormat
{
    /// <summary>
    ///     The text to translate is HTML.
    /// </summary>
    Html,

    /// <summary>
    ///     The text to translate is plain text.
    /// </summary>
    Text
}

Making the Request

To make the request I decided to use the HttpClient class from the WCF REST Starter Kit. I have blogged about this class previously and it makes working with REST web services a walk in the park. Before jumping into the actual implementation lets have a look at some sample calling code. The static Google class contains an overloaded Translate method that accepts parameters for the text to be translated, source language, destination language and input text format.

TranslationResponse response = Google.Translate("Hello, world!", Language.English, Language.French, TextFormat.Text);

Console.WriteLine("Status: " + response.ResponseStatus);
Console.WriteLine("Details: " + response.ResponseDetails);
Console.WriteLine("Detected Source Language: " + response.ResponseData.DetectedSourceLanguage);
Console.WriteLine("Translated Text: " + response.ResponseData.TranslatedText);

The Language class can be used to specify the source and destination languages. The language parameters are actually string values and can be provided dynamically if required. Using the Language class is simply a convenience that ensures the language values are correct and makes the method easier to use when the values can be hardcoded.

The TextFormat enumeration is used to indicate the format of the text to be translated. Because the parameter is an enumeration the value provided can never be invalid. Plain text is the default when an overload of the Translate method is used that does not require a value for the parameter.

The Translate method returns the deserialized TranslationResponse. I decided to return the response object as it is difficult to report errors without throwing an exception if only the translated text is returned. The response can also include other useful information and I didn’t want to use out parameters to return it.

If the ResponseStatus property of the response contains a value other than 200 a failure has occurred. This value can be different to the real HTTP status code returned to the HttpClient. If the call “virtually” failed the ResponseDetails property will contain an error message and the ResponseData property will be null. If the call was successful the TranslatedText property of the ResponseData will contain the translated text. The DetectedSourceLanguage property will only contain a value if the source language was not provided and was discovered during translation.

Implementation Details

The Translate method from the Google class can be seen below.

/// <summary>
///     Translates the specified text.
/// </summary>
/// <param name="text">
///     The text to translate.
/// </param>
/// <param name="sourceLanguage">
///     The language to translate from.
/// </param>
/// <param name="destinationLanguage">
///     The language to translate to.
/// </param>
/// <param name="textFormat">
///     The format of the text to be translated.
/// </param>
/// <returns>
///     A response that includes the translated text and status information.
/// </returns>
/// <exception cref="ArgumentException">
///     Thrown if the text to translate or destination language is null or empty.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
///     Thrown if the HTTP response status code is not 200.
/// </exception>
/// <exception cref="ApplicationException">
///     Thrown if the response fails due to a non-communication related problem.
///     The response details returned from Google are included in the exception message.
/// </exception>
/// <remarks>
///     If the source language is not provided it will be automatically detected.
/// </remarks>
public static TranslationResponse Translate(string text, string sourceLanguage, string destinationLanguage, TextFormat textFormat)
{
    if (string.IsNullOrEmpty(text)) throw new ArgumentException("The 'text' parameter is invalid.", "text");
    if (string.IsNullOrEmpty(destinationLanguage)) throw new ArgumentException("The 'destinationLanguage' parameter is invalid.", "destinationLanguage");

    HttpClient client = new HttpClient("http://ajax.googleapis.com/");

    HttpQueryString queryString = new HttpQueryString
    {
        {"v", "1.0"},
        {"hl", Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName},
        {"langpair", string.Format("{0}|{1}", sourceLanguage.ToLowerInvariant(), destinationLanguage.ToLowerInvariant())},
        {"q", text},
        {"format", textFormat.ToString().ToLower()}
    };

    Uri serviceUri = new Uri("ajax/services/language/translate", UriKind.Relative);

    HttpResponseMessage responseMessage = client.Get(serviceUri, queryString);
    responseMessage.EnsureStatusIsSuccessful();

    DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(TranslationResponse));
    return (TranslationResponse)serializer.ReadObject(responseMessage.Content.ReadAsStream());
}

After checking the required arguments have been provided a new instance of the HttpClient is created. The client is provided with the Google API domain name for the base address. Next the HttpQueryString class, also from the starter kit, is used to build the query string. This class takes care of all the encoding and string formatting work.

The query string arguments are straight forward:

  • “v” is the version of the API and is hardcoded to “1.0”.
  • “hl” is the host language which is retrieved from the current thread.
  • “langpair” is a pipe separated pair of two letter ISO language names.
  • “q” is the text to be translated.
  • “format” indicates the format of the text to be translated (HTML or plain text).

A new Uri instance is created with the relative address to the translation web service. When the Get method is called on the HttpClient with the relative URL and query string a HttpResponseMessage is returned. The EnsureStatusIsSuccessful method on the response is called to ensure that the HTTP status code returned is 200 (OK). An ArgumentOutOfRangeException will be thrown if the status code is not 200.

Finally, an instance of the DataContractJsonSerializer is created ready to serialize and deserialize instances of the TranslationResponse type. The HTTP response is retrieved as a Stream and provided to the ReadObject method of the DataContractJsonSerializer. The deserialized response is then returned to the caller.

Summary

The Language API is an example of another service from Google that is both cool and free. The WCF REST Starter Kit makes working with REST web services really simple, and JSON is no longer a format that is only useful to web developers working in JavaScript thanks to the DataContractJsonSerializer. I have attached a Visual Studio 2008 solution with the full source code.

GoogleTranslator.zip (136.21 kb)

Tags: ,

Garage Sale Code | Web Development | Web Services

Bugs in the bit.ly REST API

by Alex Meyer-Gleaves 17 April 2009 - 8:15 PM

Introduction

bit.ly is one of the many websites that offer URL shortening services. It’s certainly one of the better services and integrates nicely with Twitter. I decided to take a look at their REST API but quickly found problems with the expand service, including an obvious bug when results are returned as XML instead of JSON. All of the services allow you to specify JSON or XML as the format for the response. The default response format is JSON.

Invalid XML

The service for expanding a URL from its shortened representation returns XML that includes the hash for the URL as one of the elements. The problem is that the hash for a URL can, and often does, start with a numeric character. One of the naming rules for an XML element name is that it cannot start with a numeric or punctuation character.

If you run the example URL provided in the API documentation for expand in the browser, and append format=xml to the query string, you will see a parsing error regarding the element name. You can test the link below and see that an error will be displayed regardless of the browser you are using. Firefox, Internet Explorer and Google Chrome all validate an XML response before rendering it.

http://api.bit.ly/expand?version=2.0.1&shortUrl=http://bit.ly/31IqMl&login=bitlyapidemo&apiKey=R_0da49e0a9118ff35f52f629d2d71bf07&format=xml

This also means you cannot load the XML into an XDocument or XmlDocument in .NET. Both classes validate the XML and throw an XmlException if validation fails. When provided with the invalid XML both throw an exception with the same message:

System.Xml.XmlException: Name cannot begin with the '3' character, hexadecimal value 0x33. Line 5, position 4.

You can see the problem element on line 5 in the XML below. If you attempt to use the Paste XML as Types feature in the WCF REST Starter Kit with this XML you will find that it also throws the same exception.

<bitly>
    <errorCode>0</errorCode>
    <errorMessage></errorMessage>
    <results>
        <31IqMl>
            <longUrl>http://cnn.com/</longUrl>
        </31IqMl>
    </results>
    <statusCode>OK</statusCode>
</bitly>

Serialization Issues

The JSON and XML responses from the expand service are not serialization friendly for .NET consumers. Had the Paste XML as Types feature mentioned above actually managed to generate a .NET type you would find that it too was invalid. The hash name that was invalid in the XML also prevents you from creating a .NET type to use with serializers such as the XmlSerializer, DataContractSerializer and DataContractJsonSerializer.

The naming issue still exists, except this time instead of XML elements, it’s the names of .NET properties that cannot start with a number. The more important issue though is that the names of the XML element and JSON pairs are actually variable. This prevents them from being mapped to a property on a .NET type by the serializer. If the hash was a value associated with a fixed XML element name or JSON pair name you would be able to deserialize the result into a .NET type.

Conclusion

The other services in the bit.ly REST API do not suffer the same problem. They use fixed and valid names for the XML elements and JSON pairs. Despite being a little surprised to find these problems, I remain a fan of the bit.ly service and would happily recommend it. These sort of mistakes happen to all of us, but you never hope to see them find their way into a public API.

Tags: ,

Web Development

Compute any hash for any object in C#

by Alex Meyer-Gleaves 16 April 2009 - 6:36 PM

Approach

The .NET Framework already has many classes for cryptography in the System.Security.Cryptography namespace, so there is no need to worry about the hashing algorithms myself. The cryptography classes supplied by the .NET Framework expect to be given a byte array from which the hash can be computed. So, if I can create a hash from an array of bytes, and can convert any object into an array of bytes, then I will be able to calculate the hash for any object. Also, if I can select the CSP (Cryptographic Service Provider) to use for computing the hash, I can then “Compute any hash for any object”. Well, any hash algorithm supported by the .NET Framework at least.

Serialization

Starting with .NET Framework v3.5 SP1, turning any object into an array of bytes is actually very simple. This is because the DataContractSerializer was updated to allow for the serialization of POCO (Plain Old CLR Objects), and not just those marked with the Serializable or DataContract attributes. This means that we can take any object and serialize it into a MemoryStream, and then feed the array of bytes from the stream into the appropriate CSP to compute the hash.

Cryptographic Service Provider

As I mentioned before, there are many of these providers available in the .NET Framework, including providers for the common MD5 and SHA-1 hashing algorithms. The providers can be broken down as implementing one of two types of hash functions: those that require a key and those that don’t. Those that require a key are used for creating a HMAC (Hash Message Authentication Code), which is a combination of a hashing function and a secret key.

There are two abstract base classes defined for these two types of hashing functions: the HashAlgorithm and KeyedHashAlgorithm classes. The main difference is that the KeyedHashAlgorithm class has a Key property that can be used to set the secret key.

The HashAlgorithm and KeyedHashAlgorithm types.

Classes that derive from KeyedHashAlgorithm often have a constructor that excepts the key as a byte array, but this is for convenience only, and the property can be set at any time. This will make life easier when it comes to implementing support for the two types of hash functions.

Implementation

I decided to take advantage of the new Extension Methods language feature added to C# 3.0 to make the hashing methods available on all objects. The extension method for a normal hash function (one without a key) is below. You can see that the generic type parameter has a constraint indicating that the type argument must derive from HashAlgorithm and have a public parameterless constructor.

public static string GetHash<T>(this object instance) where T : HashAlgorithm, new()
{
    T cryptoServiceProvider = new T();
    return computeHash(instance, cryptoServiceProvider);
}

The extension method for a keyed hash function is similar but includes a byte array parameter for the key, and the type argument must instead derive from KeyedHashAlgorithm allowing the Key property to be set.

public static string GetKeyedHash<T>(this object instance, byte[] key) where T : KeyedHashAlgorithm, new()
{
    T cryptoServiceProvider = new T {Key = key};
    return computeHash(instance, cryptoServiceProvider);
}

Both of the extension methods call a private method to perform the actual serialization and the computation of the hash. This method creates an instance of the DataContractSerializer passing the type the extension method is being applied to into the constructor. A MemoryStream instance is created to hold the serialized representation of the object. The byte array of the stream is passed into the ComputeHash method of the HashAlgorithm instance to compute the hash. Finally, the hash is retrieved from the Hash property and converted to a base 64 string.

private static string computeHash<T>(object instance, T cryptoServiceProvider) where T : HashAlgorithm, new()
{
    DataContractSerializer serializer = new DataContractSerializer(instance.GetType());
    using (MemoryStream memoryStream = new MemoryStream())
    {
        serializer.WriteObject(memoryStream, instance);
        cryptoServiceProvider.ComputeHash(memoryStream.ToArray());
        return Convert.ToBase64String(cryptoServiceProvider.Hash);
    }
}

I also decided to add extension methods for the common MD5 and SHA-1 hash functions. These are simply helpers that call the GetHash<T> extension method providing the appropriate HashAlgorithm type for the type argument. The implementation of these helper methods are an example of how the generic extension methods are called.

public static string GetMD5Hash(this object instance)
{
    return instance.GetHash<MD5CryptoServiceProvider>();
}

public static string GetSHA1Hash(this object instance)
{
    return instance.GetHash<SHA1CryptoServiceProvider>();
}

Below is a simple example of using the GetKeyedHash<T> extension method.

byte[] key = new byte[] {1, 2, 3, 4};
DateTimeOffset now = DateTimeOffset.Now;
string hash = now.GetKeyedHash<HMACMD5>(key);
Console.WriteLine(hash);

For those like to copy and paste, here is the complete solution. I have also attached the C# code file to the end of this post. You will need to add a reference to the System.Runtime.Serialization assembly before compiling.

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Security.Cryptography;

namespace AlexMG.Shared
{
    /// <summary>
    ///     Extension methods applied to the <see cref="object"/> type.
    /// </summary>
    public static class ObjectExtensions
    {
        /// <summary>
        ///     Gets a hash of the current instance.
        /// </summary>
        /// <typeparam name="T">
        ///     The type of the Cryptographic Service Provider to use.
        /// </typeparam>
        /// <param name="instance">
        ///     The instance being extended.
        /// </param>
        /// <returns>
        ///     A base 64 encoded string representation of the hash.
        /// </returns>
        public static string GetHash<T>(this object instance) where T : HashAlgorithm, new()
        {
            T cryptoServiceProvider = new T();
            return computeHash(instance, cryptoServiceProvider);
        }

        /// <summary>
        ///     Gets a key based hash of the current instance.
        /// </summary>
        /// <typeparam name="T">
        ///     The type of the Cryptographic Service Provider to use.
        /// </typeparam>
        /// <param name="instance">
        ///     The instance being extended.
        /// </param>
        /// <param name="key">
        ///     The key passed into the Cryptographic Service Provider algorithm.
        /// </param>
        /// <returns>
        ///     A base 64 encoded string representation of the hash.
        /// </returns>
        public static string GetKeyedHash<T>(this object instance, byte[] key) where T : KeyedHashAlgorithm, new()
        {
            T cryptoServiceProvider = new T { Key = key };
            return computeHash(instance, cryptoServiceProvider);
        }

        /// <summary>
        ///     Gets a MD5 hash of the current instance.
        /// </summary>
        /// <param name="instance">
        ///     The instance being extended.
        /// </param>
        /// <returns>
        ///     A base 64 encoded string representation of the hash.
        /// </returns>
        public static string GetMD5Hash(this object instance)
        {
            return instance.GetHash<MD5CryptoServiceProvider>();
        }

        /// <summary>
        ///     Gets a SHA1 hash of the current instance.
        /// </summary>
        /// <param name="instance">
        ///     The instance being extended.
        /// </param>
        /// <returns>
        ///     A base 64 encoded string representation of the hash.
        /// </returns>
        public static string GetSHA1Hash(this object instance)
        {
            return instance.GetHash<SHA1CryptoServiceProvider>();
        }

        private static string computeHash<T>(object instance, T cryptoServiceProvider) where T : HashAlgorithm, new()
        {
            DataContractSerializer serializer = new DataContractSerializer(instance.GetType());
            using (MemoryStream memoryStream = new MemoryStream())
            {
                serializer.WriteObject(memoryStream, instance);
                cryptoServiceProvider.ComputeHash(memoryStream.ToArray());
                return Convert.ToBase64String(cryptoServiceProvider.Hash);
            }
        }
    }
}

Performance

The most expensive part of calculating the hash values is the serialization. This is not meant to be a replacement for the traditional testing of object equality and should be used carefully due to the serialization cost. I performed some basic testing of the NetDataContractSerializer and DataContractJsonSerializer as well. The DataContractJsonSerializer did not perform well when given large or complex object instances such a sizable DataSet. The NetDataContractSerializer performance was about the same as the DataContractSerializer.

ObjectExtensions.cs (3.30 kb)

Tags:

Garage Sale Code

SQL Server 2008 SP1

by Alex Meyer-Gleaves 14 April 2009 - 4:24 PM

Service Pack 1 for SQL Server 2008 has been released and is available for download. The service pack offers very little in the way of features, but is instead comprised of a rollup of cumulative updates and bug fixes. There is a note on the download page that seems like it was put there to address the disappointment that may be felt by those looking for new features:

We remain committed to our plans to keep service packs contained, focusing on essential updates only, primarily a Roll-up of Cumulative Update 1 to 3, Quick Fix Engineering (QFE) updates, as well as fixes to issues reported through the SQL Server community.

Despite the lake of sexy new features there are enough bug fixes to make installing the service pack worthwhile. The release of the first service pack for a Microsoft product is often the point at which many quality sceptical users take up the technology, so maybe this release will bring an increase in the adoption of SQL Server 2008.

Tags:

Database

Introduction to the HttpClient

by Alex Meyer-Gleaves 7 April 2009 - 6:54 PM

Introduction

The WCF REST Starter Kit Preview 2 includes a set of classes designed to simplify interaction with REST web services. The communications are performed using the HttpClient class with the help of other types from the Microsoft.Http namespace. These types can be found in the Microsoft.Http.dll assembly located in the Assemblies folder of the starter kit.

Creating a HttpClient

There are three constructors to choose from. There is a default constructor and two others that allow you to set the base address of the web service you will be working with. The base address can be expressed as a String or Uri value.

HttpClient client1 = new HttpClient();
HttpClient client2 = new HttpClient("http://www.foo.com/");
HttpClient client3 = new HttpClient(new Uri("http://www.foo.com/"));

Sending a Message

The Send method of the HttpClient is used to begin a synchronous request. There are also extension methods available to help you configure your request for the different HTTP methods. We will cover those later as they actually use the Send method under the hood.

HttpClient client = new HttpClient("http://www.foo.com/");
HttpResponseMessage response = client.Send(HttpMethod.GET.ToString(), "RestService");

The Send method returns a HttpResponseMessage instance. You can use the response to check the response status code and retrieve any returned content. If you pass a URI into the Send method it will be added as a relative address to the base address.

Validating the Response

There are extension methods defined in the HttpMessageExtensions class that assist you in checking the returned status code. The HTTP status codes are defined in the HttpStatusCode enumeration in the System.Net namespace, which is part of the framework and not the starter kit.

When using the EnsureStatusIsSuccessful extension method an ArgumentOutOfRangeException is thrown if the status code is not HttpStatusCode.OK.

try
{
    HttpClient client = new HttpClient("http://www.foo.com/");
    HttpResponseMessage response = client.Send(HttpMethod.GET, "RestService");
    response.EnsureStatusIsSuccessful();
}
catch (ArgumentOutOfRangeException exception)
{
    Console.WriteLine(exception);
}

You can also use the EnsureStatusIs method to check for specific status codes. You must provide at least one status code to check. If the check fails an ArgumentOutOfRangeException is thrown just the same as for the EnsureStatusIsSuccessful method.

HttpClient client = new HttpClient("http://www.foo.com/");
HttpResponseMessage response = client.Send(HttpMethod.GET, "RestService");
response.EnsureStatusIs(HttpStatusCode.PartialContent, HttpStatusCode.UnsupportedMediaType);

Send Extension Methods

As mentioned earlier, a number of extensions methods are defined in the HttpMethodExtensions class that help you configure your request with the appropriate HTTP method. There are extension methods for the GET, POST, PUT, DELETE and HEAD methods. The extension methods all end up calling the Send method of the HttpClient being extended with the appropriate HttpMethod value to indicate the HTTP method to use.

HttpClient client = new HttpClient("http://www.foo.com/");
HttpResponseMessage postResponse = client.Post("RestService", HttpContent.CreateEmpty());
HttpResponseMessage putResponse = client.Put("RestService", HttpContent.CreateEmpty());
HttpResponseMessage deleteResponse = client.Delete("RestService");
HttpResponseMessage headResponse = client.Head("RestService");

The Post and Put methods require you to provide the content that is to be sent to the server. Providing a null value for the HttpContent parameter will result in an ArgumentNullException being thrown.

Sending Content

The Send method of the HttpClient class, along with the Post and Put extension methods, except a HttpContent instance to represent the content that should be sent to the web service. You create a new instance of the HttpContent class using its static Create method. An empty HttpContent instance can also be created using the static CreateEmpty method. The Create method can be provided a wide range of parameters including string values, streams, byte arrays and ICreateHttpContent instances.

HttpClient client = new HttpClient("http://www.foo.com/");

HttpContent stringContent = HttpContent.Create("Foo");
HttpResponseMessage response = client.Post("RestService", stringContent);

byte[] bytes = Encoding.UTF8.GetBytes("Foo");
HttpContent byteContent = HttpContent.Create(bytes);
response = client.Post("RestService", byteContent);

using (MemoryStream memoryStream = new MemoryStream(bytes))
{
    HttpContent streamContent = HttpContent.Create(memoryStream);
    response = client.Post("RestService", streamContent);
}

The HttpMultipartMimeForm and HttpUrlEcodedForm classes are examples of classes that implement the ICreateHttpContent interface. These are helper classes that build up the HttpContent instances in more advanced scenarios.

Receiving Content

The content for a response is accessible via the Content property of the HttpResponseMessage. Like the content sent in the request, the property is of type HttpContent. The HttpContent class contains some helper methods for retrieving the data it contains. These include ReadAsByteArray, ReadAsStream and ReadAsString. They return the content in the format that their names suggest.

HttpClient client = new HttpClient("http://www.foo.com/");
HttpResponseMessage response = client.Get("RestService");

byte[] byteArrayContent = response.Content.ReadAsByteArray();
Stream streamContent = response.Content.ReadAsStream();
string stringContent = response.Content.ReadAsString();

Using Headers

HTTP headers play an important role in REST web services. The RequestHeaders class represents the headers that are sent in the request, and the ResponseHeaders class represents the headers returned in the response. Both of these classes derive from the HttpHeaders class. This base class acts like a dictionary for the headers and exposes properties to access the most common header values.

The static Parse method on the RequestHeaders class can take a string of headers and extract the values. It assumes that the headers appear on separate lines.

const string headers = "Content-Type: text/xml\r\nReferer: http://www.referer.com";
RequestHeaders requestHeaders = RequestHeaders.Parse(headers);

You can also use the properties to set the header values individually, and in a more strongly-typed manner.

RequestHeaders requestHeaders = new RequestHeaders
{
    ContentType = "text/xml", Referer = new Uri("http://www.referer.com")
};

The response headers are accessed via the Headers property of the HttpResponseMessage instance returned from the Send method of the HttpClient, or from one of the extension methods found in the HttpMethodExtensions class.

HttpClient client = new HttpClient("http://www.foo.com/");
HttpResponseMessage response = client.Get("RestService");
Console.WriteLine(response.Headers.ContentType);

Sending Asynchronously

There are two ways of sending a request asynchronously via the HttpClient. The first is to call the BeginSend method and provide an AsyncCallback delegate. The second is to call the SendAsync method and subscribe to the SendCompleted event. You must prepare a HttpRequestMessage instance to provide to both of these methods as they do not provide the same overloads as the Send method.

The BeginSend method uses the typical IAsyncResult pattern. This allows you to continue with other work and have the callback delegate invoked, or wait for the result using a wait handle or through polling. The example below continues execution leaving the AsyncCallback delegate to be invoked when the request has completed. The EndSend method on HttpClient is called with the IAsyncResult provided to the callback to get access to the response message.

HttpClient client = new HttpClient("http://www.foo.com/");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.GET.ToString(), "RestService");
AsyncCallback callback = asyncResult =>
     {
         HttpResponseMessage response = client.EndSend(asyncResult);
         Console.WriteLine(response.StatusCode);
     };
client.BeginSend(request, callback, null);

The SendAsync method allows you to send requests asynchronously and be informed when a request has completed via the SendCompleted event. The event argument is of type SendCompletedEventArgs and exposes Request and Response properties. As expected, these properties provide access to the original HttpRequestMessage, and the returned HttpResponseMessage. There is also a UserState property, which is actually defined on the base SendCompletedEventArgs, that can be used to return an object that was passed into the SendAsync method. The user state can be used as a key of sorts to match up returning responses with the requests that initiated them.

HttpClient client = new HttpClient("http://www.foo.com/");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.GET.ToString(), "RestService");
client.SendCompleted += (s, e) => Console.WriteLine(e.UserState);
const string userState = "foo";
client.SendAsync(request, userState);

Summary

The HttpClient provides a simple and clean API for working with REST web services. It removes a great deal of the complexity that is inherent with working so close to the HTTP stack. All the HTTP methods are supported, including the common GET, POST, PUT and DELETE methods. Building content for your requests and extracting content from responses has been greatly simplified. The common HTTP headers for the request and response can be accessed through a strongly-typed interface. You can check the status of responses without having to worry about remembering the numeric HTTP status codes. There are two different patterns available for performing asynchronous requests.

Tags: , ,

Web Development | Web Services

PreCode Plugin for Windows Live Writer v4.0

by Alex Meyer-Gleaves 2 April 2009 - 1:52 PM

I use the PreCode plugin for Windows Live Writer to add the code snippets to my blog posts that are highlighted with SyntaxHighlighter. PreCode adds the HTML markup required for SyntaxHighlighter to identify the snippets that require highlighting. It also ensures that the snippet code is HTML encoded.

The recently released version of PreCode now includes support for the new SyntaxHighlighter v2.0 markup and options. The HTML that SyntaxHighlighter uses to identify code snippets was changed in v2.0 to be more standards compliant and is notably different to the old markup. For this reason, the plugin will not work with older versions of SyntaxHighlighter.

The UI of PreCode has also been given an update and is looking very nice indeed.

 The updated PreCode UI.

Another cool feature Anthony Bouch has added is the ability to run PreCode as a standalone Windows desktop application. Thanks to Anthony for keeping this great plugin alive and kicking.

Tags:

Development Tools

About the author

Alex Meyer-Gleaves I'm a Technical Architect living in Australia (that island like continent in the southern hemisphere). I love Microsoft .NET and C#. I hate early mornings, slow drivers and Lotus Notes.

Twitter

Google Shared

 

Month List

Recent Comments

Comment RSS

Links

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2010