My group has been doing a lot of REST API consumption lately, and our favorite tool to use for that is the terrific RestSharp library. Our latest project is very large, and uses lots of different APIs, and so we needed a way to create many different client classes that could consume many different APIs but still use common features like caching and error logging.

And so, I present to you, the Ultimate RestSharp Client!

In this post, we'll build a RestSharp client which can serve as a base class to many other clients which needs to consume RESTful APIs. Said base class will need to implement common functionality (e.g. the aforementioned caching and error logging) and be able to deserialize known objects which the APIs return. Come along with me as I condense a week's worth of research and coding into one 1700-word blog post and build the Ultimate RestSharp Client!

Requirements

The ultimate RestSharp client that we're going to build needs to support the following scenarios:

  1. Deserialize objects using an injected serializer.
  2. Store into and retrieve from a cache.
  3. Check for timeouts.
  4. Use custom injected error logging.

Prerequisites

Before we build our ultimate RestSharp client, let's first define the three items which need to be injected into said client.

Cache Service

The cache service we used in an earlier post will serve us well now. Here's our (slightly modified) simple cache service interface and implementation:

using System;
using System.Runtime.Caching;

public interface ICacheService
{
    T Get<T>(string cacheKey) where T : class;
    void Set(string cacheKey, object item, int minutes);
}

public class InMemoryCache : ICacheService
{
    public T Get<T>(string cacheKey) where T : class
    {
        return MemoryCache.Default.Get(cacheKey) as T;
    }
    public void Set(string cacheKey, object item)
    {
        if (item != null)
        {
            MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(30));
        }
    }
}

Deserializer

As of recently, RestSharp no longer requires JSON.NET as its default serializer (see this readme). So, we must inject a serializer into our ultimate client, which will use JSON.NET anyway because that's what I like. I stole the following class from this fantastic blog post and it's worked pretty well for us.

using Newtonsoft.Json;
using RestSharp.Deserializers;
using RestSharp.Serializers;
using System.IO;

public class JsonSerializer : ISerializer, IDeserializer
{
    private readonly Newtonsoft.Json.JsonSerializer _serializer;

    public JsonSerializer()
    {
        ContentType = "application/json";
        _serializer = new Newtonsoft.Json.JsonSerializer
        {
            MissingMemberHandling = MissingMemberHandling.Ignore,
            NullValueHandling = NullValueHandling.Include,
            DefaultValueHandling = DefaultValueHandling.Include,
            DateFormatHandling=DateFormatHandling.IsoDateFormat,
            DateTimeZoneHandling=DateTimeZoneHandling.Unspecified
        };
    }

    public JsonSerializer(Newtonsoft.Json.JsonSerializer serializer)
    {
        ContentType = "application/json";
        _serializer = serializer;
    }

    public string Serialize(object obj)
    {
        using (var stringWriter = new StringWriter())
        {
            using (var jsonTextWriter = new JsonTextWriter(stringWriter))
            {
                jsonTextWriter.Formatting = Formatting.Indented;
                jsonTextWriter.QuoteChar = '"';

                _serializer.Serialize(jsonTextWriter, obj);

                var result = stringWriter.ToString();
                return result;
            }
        }
    }

    public string DateFormat { get; set; }
    public string RootElement { get; set; }
    public string Namespace { get; set; }
    public string ContentType { get; set; }

    public T Deserialize<T>(RestSharp.IRestResponse response)
    {
        var content = response.Content;

        using (var stringReader = new StringReader(content))
        {
            using (var jsonTextReader = new JsonTextReader(stringReader))
            {
                return _serializer.Deserialize<T>(jsonTextReader);
            }
        }
    }
}

Error Logger

This is the simplest of the three injection prerequisites. You will want to replace this implementation with one of your own design. Here's a very basic error logger interface and class:

public interface IErrorLogger
{
    void LogError(Exception ex, string infoMessage);
}

public class ErrorLogger : IErrorLogger
{
    public void LogError(Exception ex, string infoMessage)
    {
        //Log the error to your error database
    }
}

With all three prerequisite items in place, we can now start to code our ultimate RestSharp client!

Class and Constructor

In order to build our ultimate RestSharp client, we'll first need a class that inherits from RestSharp's RestClient class:

using RestSharp;
using RestSharp.Deserializers;
using System;
using System.Linq;

public class BaseClient : RestSharp.RestClient { }

We also need a constructor to inject our three prerequisite classes into this client, as well as a base URL for which each child client will be using for their APIs. Such a constructor looks like this:

public class BaseClient : RestSharp.RestClient
{
    protected ICacheService _cache;
    protected IErrorLogger _errorLogger;
    public BaseClient(ICacheService cache, 
                      IDeserializer serializer, 
                      IErrorLogger errorLogger, 
                      string baseUrl)
    {
        _cache = cache;
        _errorLogger = errorLogger;
        AddHandler("application/json", serializer);
        AddHandler("text/json", serializer);
        AddHandler("text/x-json", serializer);
        BaseUrl = new Uri(baseUrl);
    }
}

Note that we are using JSON for this ultimate client. You can use other formats (e.g. XML), but I prefer JSON for this kind of client so that's what we'll use here.

Once we've got the constructor build, it's time to start coding the guts of our ultimate client, starting with...

Error Logging

We have two different kinds of error logging in this system: the generic error logging and the more specific Timeout checks. We implemented timeout checks in an earlier post, go check that out if you want more details. For now, here's some code to accomplish each of these.

private void LogError(Uri BaseUrl, IRestRequest request, IRestResponse response)
{
    //Get the values of the parameters passed to the API
    string parameters = string.Join(", ", request.Parameters.Select(x => x.Name.ToString() + "=" + ((x.Value == null) ? "NULL" : x.Value)).ToArray());

    //Set up the information message with the URL, 
    //the status code, and the parameters.
    string info = "Request to " + BaseUrl.AbsoluteUri 
                   + request.Resource + " failed with status code " 
                   + response.StatusCode + ", parameters: "
                   + parameters + ", and content: " + response.Content;

    //Acquire the actual exception
    Exception ex;
    if (response != null && response.ErrorException != null)
    {
        ex = response.ErrorException;
    }
    else
    {
        ex = new Exception(info);
        info = string.Empty;
    }

    //Log the exception and info message
    _errorLogger.LogError(ex,info);
}

private void TimeoutCheck(IRestRequest request, IRestResponse response)
{
    if (response.StatusCode == 0)
    {
        LogError(BaseUrl, request, response);
    }
}

Get and Execute

Once we've got the error logging in place, we can now discuss the kinds of operations we want this client to be able to perform.

In our client, there are three possible actions that the client can perform against the API:

  • A basic EXECUTE operation, which will return an IRestResponse, which is the generic Response interface defined by RestSharp.
  • A GET operation, which will call the EXECUTE operations and return a deserialized class.
  • A GET FROM CACHE operation, which will check the cache service for a matching object, return that object if it exists, or call the EXECUTE operations if it doesn't.

EXECUTE

Because the GET operations will call the EXECUTE operations and will return deserialized objects, we need both generic and non-generic versions of the EXECUTE operations. Here's those EXECUTE operations, which override the underlying RestSharp methods and call the TimeoutCheck() method from earlier:

public override IRestResponse Execute(IRestRequest request)
{
    var response = base.Execute(request);
    TimeoutCheck(request, response);
    return response;
}
public override IRestResponse<T> Execute<T>(IRestRequest request)
{
    var response = base.Execute<T>(request);
    TimeoutCheck(request, response);
    return response;
}

GET

The GET operation is just a tiny bit more complex. It must check the status code of the response, and if that code is 200 OK it will deserialize an object; otherwise it must log an error and return a default instance of the class it is trying to deserialize. We can do this via a generic method, like so:

public T Get<T>(IRestRequest request) where T : new()
{
    var response = Execute<T>(request);
    if (response.StatusCode == System.Net.HttpStatusCode.OK)
    {
        return response.Data;
    }
    else
    {
        LogError(BaseUrl,request,response);
        return default(T);
    }
}

GET FROM CACHE

The GET FROM CACHE methods are more complex still, though they really just add an extra step onto the GET operations:

public T GetFromCache<T>(IRestRequest request, string cacheKey) 
  where T : class, new()
{
    var item = _cache.Get<T>(cacheKey);
    if (item == null) //If the cache doesn't have the item
    {
        var response = Execute<T>(request); //Get the item from the API call
        if (response.StatusCode == System.Net.HttpStatusCode.OK)
        {
            _cache.Set(cacheKey, response.Data); //Set that item into the cache 
                                                 //so we can get it next time
            item = response.Data;
        }
        else
        {
            LogError(BaseUrl, request, response);
            return default(T);
        }
    }
    return item;
}

The Complete Client

For posterity's sake, here's the complete code for the ultimate RestSharp client:

using RestSharp;
using RestSharp.Deserializers;
using System;
using System.Linq;

public class BaseClient : RestSharp.RestClient
{
    protected ICacheService _cache;
    protected IErrorLogger _errorLogger;
    public BaseClient(ICacheService cache, 
                      IDeserializer serializer, 
                      IErrorLogger errorLogger, 
                      string baseUrl)
    {
        _cache = cache;
        _errorLogger = errorLogger;
        AddHandler("application/json", serializer);
        AddHandler("text/json", serializer);
        AddHandler("text/x-json", serializer);
        BaseUrl = new Uri(baseUrl);
    }

    private void TimeoutCheck(IRestRequest request, IRestResponse response)
    {
        if (response.StatusCode == 0)
        {
            LogError(BaseUrl, request, response);
        }
    }

    public override IRestResponse Execute(IRestRequest request)
    {
        var response = base.Execute(request);
        TimeoutCheck(request, response);
        return response;
    }
    public override IRestResponse<T> Execute<T>(IRestRequest request)
    {
        var response = base.Execute<T>(request);
        TimeoutCheck(request, response);
        return response;
    }

    public T Get<T>(IRestRequest request) where T : new()
    {
        var response = Execute<T>(request);
        if (response.StatusCode == System.Net.HttpStatusCode.OK)
        {
            return response.Data;
        }
        else
        {
            LogError(BaseUrl,request,response);
            return default(T);
        }
    }

    public T GetFromCache<T>(IRestRequest request, string cacheKey) 
      where T : class, new()
    {
        var item = _cache.Get<T>(cacheKey);
        if (item == null)
        {
            var response = Execute<T>(request);
            if (response.StatusCode == System.Net.HttpStatusCode.OK)
            {
                _cache.Set(cacheKey, response.Data);
                item = response.Data;
            }
            else
            {
                LogError(BaseUrl, request, response);
                return default(T);
            }
        }
        return item;
    }

    private void LogError(Uri BaseUrl, 
                          IRestRequest request, 
                          IRestResponse response)
    {
        //Get the values of the parameters passed to the API
        string parameters = string.Join(", ", request.Parameters.Select(x => x.Name.ToString() + "=" + ((x.Value == null) ? "NULL" : x.Value)).ToArray());

        //Set up the information message with the URL, 
        //the status code, and the parameters.
        string info = "Request to " + BaseUrl.AbsoluteUri 
                      + request.Resource + " failed with status code " 
                      + response.StatusCode + ", parameters: "
                      + parameters + ", and content: " + response.Content;

        //Acquire the actual exception
        Exception ex;
        if (response != null && response.ErrorException != null)
        {
            ex = response.ErrorException;
        }
        else
        {
            ex = new Exception(info);
            info = string.Empty;
        }

        //Log the exception and info message
        _errorLogger.LogError(ex,info);
    }
}

Ta da! We've now written our Ultimate RestSharp Client! But one thing remains: how do we use it?

Using the Ultimate RestSharp Client

To use this client, we treat it as a base class for other clients. Let's imagine that we have the following object which will be returned by our API:

public class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int UserID { get; set; }
    public DateTime DateOfBirth { get; set; }
}

We also have APIs at the following URLs which will return this object or a collection of this object:

/users/{id}

/users/all

We can build a client for this situation very easily with our Ultimate RestSharp Client. First, we need to inherit from our BaseClient class and implement the constructor:

public class UsersClient : BaseClient
{
    public UsersClient (ICacheService cache, 
                        IDeserializer serializer, 
                        IErrorLogger errorLogger)
        : base(cache, serializer, errorLogger, "http://baseUrl.com") { }
}

Then we need create two methods, one which will get the users by ID and one which will get all the users. Let's add a further requirement and say that the method which will get the users by ID will have its results be cached. The complete UsersClient now looks like this:

public class UsersClient : BaseClient
{
    public UsersClient (ICacheService cache, 
                        IDeserializer serializer, 
                        IErrorLogger errorLogger)
        : base(cache, serializer, errorLogger, "http://yourBaseUrl.com") { }

    public Contact GetByID(int id)
    {
        RestRequest request = new RestRequest("users/{id}", Method.GET);
        request.AddUrlSegment("id", id.ToString());
        return GetFromCache<User>(request, "User" + id.ToString());
    }

    public List<Contact> GetByID(int id)
    {
        RestRequest request = new RestRequest("users/all", Method.GET);
        return Get<List<User>>(request);
    }
}

That's all there is to it! Now if you have many client classes, they can all share the same base class and easily implement error logging and caching!

Summary

This Ultimate RestSharp client is already in use in one of our biggest projects, and it's saved us so much time and effort once we got it together. I hope it helps you all out as well, dear reader!

As always, I welcome suggestions on how to improve the code in this post. Let me know if you've got any ideas in the comments!

Happy Coding!