Skip to main content

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;
                });
       }

Popular posts from this blog

Full-Text Search with PDF in Microsoft SQL Server

Last week I get interesting task to develop. The task was to search input text in PDF file stored in database as FileStream. The task implementation took me some time so I decided to share it with other developers. Here we are going to use SQL Server 2008 R2 (x64 Developers Edition), external driver from Adobe, Full-Text Search technology and FileStream technology.Because this sems a little bit comlicated let`s make this topic clear and do it step by step. 1) Enable FileStream - this part is pretty easy, just check wheter You already have enabled filestream on Your SQL Server instance - if no simply enable it as in the picture below. Picture 1. Enable filestream in SQL Server instance. 2) Create SQL table to store files  - mainly ther will be PDF file stored but some others is also be allright. Out table DocumentFile will be created in dbo schema and contain one column primary key with default value as sequential GUID. Important this is out table contains FileSt...

Persisting Enum in database with Entity Framework

Problem statement We all want to write clean code and follow best coding practices. This all engineers 'North Star' goal which in many cases can not be easily achievable because of many potential difficulties with converting our ideas/good practices into working solutions.  One of an example I recently came across was about using ASP.NET Core and Entity Framework 5 to store Enum values in a relational database (like Azure SQL). Why is this a problem you might ask... and my answer here is that you want to work with Enum types in your code but persist an integer in your databases. You can think about in that way. Why we use data types at all when everything could be just a string which is getting converted into a desirable type when needed. This 'all-string' approach is of course a huge anti-pattern and a bad practice for many reasons with few being: degraded performance, increased storage space, increased code duplication.  Pre-requirements 1. Status enum type definition...

Using Newtonsoft serializer in CosmosDB client

Problem In some scenarios engineers might want to use a custom JSON serializer for documents stored in CosmosDB.  Solution In CosmosDBV3 .NET Core API, when creating an instance of  CosmosClient one of optional setting in  CosmosClientOptions is to specify an instance of a Serializer . This serializer must be JSON based and be of  CosmosSerializer type. This means that if a custom serializer is needed this should inherit from CosmosSerializer abstract class and override its two methods for serializing and deserializing of an object. The challenge is that both methods from  CosmosSerializer are stream based and therefore might be not as easy to implement as engineers used to assume - still not super complex.  For demonstration purpose as or my custom serializer I'm going to use Netwonsoft.JSON library. Firstly a new type is needed and this must inherit from  CosmosSerializer.  using  Microsoft.Azure.Cosmos; using  Newtonsoft.Json; usin...