niedziela, 5 października 2014

Asynchronous WebApi2 HTTP client example

Today I want to cover a very common scenario about how to create a HTTP client for a WebAPI 2 service. To present my implementation I will be using one of a Task<T> extension method that I described recently on my blog.

Let`s start from defining an API service. In this example it udes a REST based WebApi service with the following implementation.

   [AllowAnonymous]
   public class CityController
   {
       /// <summary>
       /// The city repository.
       /// </summary>
       private readonly ICityRepository cityRepository;
 
       /// <summary>
       /// Initializes a new instance of the <see cref="CityController" /> class.
       /// </summary>
       /// <param name="cityRepository">The city repository.</param>
       public CityController(ICityRepository cityRepository)
       {
           this.cityRepository = cityRepository;
       }
 
       /// <summary>
       /// Return a list of cities.
       /// </summary>
       /// <param name="query">The query.</param>
       /// <returns>
       /// A list of <see cref="Users" />.
       /// </returns>
       [HttpGet]
       [Route("api/" + ApiVersion.CurrentVersion + "/cities")]
       public async Task<HttpResponseMessage> Get(string query)
       {
           return await this.cityRepository.AutocompleteCityAsync(query)
               .Continue<IQueryable<City>, HttpResponseMessage>(entities =>
               {
                   var cities = entities.MapCollection<TCity>();
                   return this.CreateListResponse<TCity>(cities);
               });
       }
    }

As you can see there is no any big deal there. I created a CityController class which is decorated with an attribute AllowAnonymous. This will tell a WebApi engine that all functions inside this controller may be accessed by anonymous user - not logged in. Next, the controller class initialization accepts an one parameter of ICityRepository which is nothing else than a simple implementation of a Repository Pattern for Entity Framework. A fact that I passing an instance of this repository as a constructor parameter means that I used in my solution an Inversion of Control (IoC) and Dependency Injection (I recommend here using  Unity as it`s the simplest one as far as I check) - this post doesn`t cover an implementation details for both patterns however I will try to create post about it soon and link it here. Now, coming back to CityController... it has one public function which is a WebApi service call for a GET (decorated by the attribute HttpGet) request for the URL GET/ http://{url}/api/v1/cities?query={query} (Route attribute). This function is also an asynchronous one, which means that it`s returning a Task<T> type which in this case is a Task<HttpResponseMessage>. The body of it uses a task chaining implementation (with my custom Continue<T,R> extension method) to call the AutocompleteCityAsync repository method and map results returned by it to a TCity data transfer object by using the AutoMapper.

using BookingWorld.Common.Interfaces.Entities;
using System.Runtime.Serialization;
 
[DataContract]
public class TCity
{
    public TCity()
    { }
 
    [DataMember]
    public string Code { getset; }
 
    [DataMember]
    public decimal Latitude { getset; }
 
    [DataMember]
    public decimal Longitude { getset; }
 
    [DataMember]
    public decimal? Scoring { getset; }
 
    [DataMember]
    public bool CapitalCity { getset; }
 
    [DataMember]
    public int QueryCount { getset; }
 
    [DataMember]
    public string Name { getset; }
 
    [DataMember]
    public int Id { getset; }
 
    [DataMember]
    public bool Active { getset; }
}


By nature the WebApi2 service serializes all responses objects to JSON format. This is a very important part that developer has to know to implement working client. In this example my GET request will return a serialized collection on TCiti objects. Now I described a service logic so it`s time to start writing more about a client itself.

There is no strict rules how to consume a WebApi service. As it`s build at the top of standard HTTP request methods and uses a JSON format of data it`s completely platform agnostic. It`s means that you can send request to it from non-.NET platform like iOS or Android mobile device and as long as you`re using the same standards you will get an response.  However in this example the client of this API is a MVC5 based website (this really doesn`t matter what is the version, the only this which is important is that it`s based on .NET Framework 4.5.1). First of all let`s introduce a service proxy base class - this is the code of the implementation and later calling a specific API function is just a matter of changing an input parameters.

public class Proxy
    {
        public IDictionary<stringobject> Parameters { getset; }
 
        /// <summary>
        /// The <see cref="HttpClient"/> used for communicating with the API.
        /// </summary>
        private HttpClient httpClient;
 
        public Uri ServiceUrl
        {
            get;
            private set;
        }
 
        public string ControllerName
        {
            get;
            private set;
        }
 
        protected Proxy(string serviceBase, string controllerName)
        {
            this.ServiceUrl = new Uri(serviceBase);
            this.httpClient = new HttpClient();
            this.ControllerName = controllerName;
            this.Parameters = new Dictionary<stringobject>();
        }
 
        /// <summary>
        /// Sends a GET request to the API.
        /// </summary>
        /// <typeparam name="TReturnType">The type of the return type.</typeparam>
        /// <param name="getUri">The GET URI.</param>
        /// <returns>A task which results in a {TReturnType} object.</returns>
        protected async Task<TReturnType> GetAsync<TReturnType>(Uri getUri)
        where TReturnType : classnew()
        {
            return await this.httpClient.GetAsync(getUri)
                .Continue<HttpResponseMessagestring>(
                httpResponseMessage =>
                {
 
                    if (httpResponseMessage.StatusCode.IsServerErrorCode())
                    {
                        throw new Exception(httpResponseMessage.ReasonPhrase);
                    }
 
                    if (httpResponseMessage.StatusCode.IsSuccessCode())
                    {
                        if (typeof(TReturnType) == typeof(string))
                        {
                            return this.httpClient.GetStringAsync(getUri) as Task<string>;
                        }
 
                        return httpResponseMessage.Content.ReadAsStringAsync();
                    }
 
                    return Task.FromResult(string.Empty);
 
                }).Continue<string, TReturnType>(resultJson =>
                {
                    var result = default(TReturnType);
 
                    if (!string.IsNullOrWhiteSpace(resultJson))
                    {
                        result = Newtonsoft.Json.JsonConvert
                                .DeserializeObject<TReturnType>(resultJson);
                    }
 
                    return result;
                });
        }



As you can see my proxy is not a very sophisticated implementation. It contains three very import parts:

1) It uses a HttpClient.GetAsync function which is a .NET native code which will start first asynchronous task.

2) Continue<string,TReturnType> - this is my own implementation of a wrapper around ContinueWith native .NET function which simplify accessing a request results. In that case this function has two generic types: a string (JSON string returned from the API) and TReturnType  type that JSON will be deserialized to.

3)  Object deserialization - this is crucial step when a JSON will be transformed into a TReturnType.

Presented class in a very generic one. It`s not related to any API as it`s configured by the parameters of derived classes (please note that it`s has a protected constructor). Now when we have it implemented it`s a piece of cake to implement a client for a City API.

However before I present an API specific proxy class I want to mention one more thing. In my option it`s obvious thing that most of an API calls passes some parameters to it. In the case the GET request these parameters are part of the query string (part of URL). To centralized implementation of constructing a request URL for a particular API call in the Proxy base class I implemented a function presented below which build a request URL based on the parameters stored in the base class Parameters property of dictionary type.

protected Uri BuildRequestUri()
        {
            var stringBuilder = new StringBuilder(this.ServiceUrl.ToString());
            stringBuilder.Append(this.ControllerName);
 
            if (this.Parameters != null && this.Parameters.Any())
            {
                stringBuilder.Append("?");
                foreach (var parameter in this.Parameters)
                {
                    stringBuilder.AppendFormat("{0}={1}", parameter.Key, parameter.Value);
 
                    if (parameter.Key != this.Parameters.Last().Key)
                    {
                        stringBuilder.Append("&");
                    }
                }
            }
 
            this.Parameters.Clear();
            return new Uri(stringBuilder.ToString());
        }

Finally it`s time to present a API specific version of the client. It`s very tiny, don`t you think? Yes, it`s is because most of the logic I put in the base class already and this derived class is only responsible for passing proper set of parameters to it parent. In that case CityProxy  accepts an API top level URL i.e. http://www.api.com/api/v1/. Also in the constructor it passes a controller specific name - in that case it`s 'cities' as I specified in the Route attribute in service definition. Now, it the GetCities function I can pass query to look-up for a city and next it will be converted to the query string parameter by the BuildRequestUri function from base. Lastly I await a GetAsync function and pass a type of response (List<TCity>)that response JSON will be deserialized to.

   using BookingWorld.Common.Proxy;
   using BookingWorld.Common.Types;
   using System.Collections.Generic;
   using System.Collections.ObjectModel;
   using System.Threading.Tasks;
 
   public class CityProxy : Proxy
   {
       public CityProxy(string baseUrl)
           : base(baseUrl, "cities")
       {
       }
 
       public async Task<List<TCity>> GetCities(string query)
       {
           this.Parameters.Add("query", query);
           var requestUri = this.BuildRequestUri();
 
           return await this.GetAsync<List<TCity>>(requestUri);
       }
   }

For those who are looking for more, I also implemented a POST.

       /// <summary>
       /// Performs an asynchronous POST of the supplied object to the given URI.
       /// </summary>
       /// <typeparam name="TPostObjectType">Type of the object to POST.</typeparam>
       /// <typeparam name="TReturnType">Type of the object to read from the returned POST operation.</typeparam>
       /// <param name="postUri">The POST uri.</param>
       /// <param name="objectToPost">The actual object to POST.</param>
       /// <returns>
       /// A <see cref="Task{TReturnType}" /> from posting the object.
       /// </returns>
       /// <exception cref="System.ArgumentNullException">postUri</exception>
       /// <remarks>
       /// Use this version of POST if the type of object returned in the
       /// POST response is different from the object that was posted.
       /// </remarks>
       protected async Task<TReturnType> PostAsync<TPostObjectType, TReturnType>(
           Uri postUri,
           TPostObjectType objectToPost)
           where TReturnType : class
       {
           if (postUri == null)
           {
               throw new ArgumentNullException("postUri");
           }
 
           var jsonToPost = Newtonsoft.Json.JsonConvert.SerializeObject(objectToPost);
           var content = new StringContent(jsonToPost);
 
           return await this.httpClient.PostAsync(postUri.AbsoluteUri, content)
                .Continue<HttpResponseMessagestring>(
                httpResponseMessage =>
                {
                    if (httpResponseMessage.StatusCode.IsServerErrorCode())
                    {
                        throw new Exception(httpResponseMessage.ReasonPhrase);
                    }
 
 
                    if (httpResponseMessage.StatusCode.IsSuccessCode())
                    {
                        return httpResponseMessage.Content.ReadAsStringAsync();
                    }
 
                    return TaskCreator.Completed<string>(string.Empty);
                })
               .Continue<string, TReturnType>(resultJson =>
                {
                    var result = default(TReturnType);
 
                    if (!string.IsNullOrWhiteSpace(resultJson))
                    {
                        result = Newtonsoft.Json.JsonConvert
                       .DeserializeObject<TReturnType>(resultJson);
                    }
 
                    return result;
                });
       }