Written on 4/18/2014 in Web development

First phase - Consuming the REST API


The vorpal story REST Service First phase

var greeting = _greetingsApi.Get(“World”);

In the last few posts, we’ve written an elephant that can lift a mouse. The Vorpal blog is growing up and it’s going to be a beautiful monster! The bottom layers are finished and I can provide the top layers with some data. We’re almost done here I guess. WRONG. I’m always spending more time trying to make it pretty so you’ve still got that to look forward to! (:p) The data is provided to our top layers using a RESTful api I discussed passionately in the previous blog post. This API still has to be consumed and to do this I recycled some generic code from a previous project of mine.

While you can and should1 use third party packages to consume the web api, I strongly believe that the best way to understand how these things work, is to write it yourself first. Once you understand the concept, you can use the third party tool to its best potential. My generic code is quite nifty, if I say so myself, it contains some base models for easy usage of the web API’s http methods. It implements a form of Repository pattern where I have a base6 class that can be implemented to consume default api behaviour2. When I say it’s nifty its because I wrote this a while ago and I did not had reuse in mind3. It was a private project for trying out some stuff4. So I was really surprised that it was actually working ‘out of its box‘.

The api client generally works like this: The url is built up using a base url. Depending on the resource I need, I can provide a suffix or addition. Url parameters can be added using ‘url bricks’ where every url brick is a url parameter with a key and value. One problem that might arise here is for accessing a REST action or method. This action does not use a url parameter but an extra suffix. A way around this is to extend the base api client behaviour. Specifically, how the url is built. The URL is built using an ‘ApiConnection’ object. This object is set on api client initialisation and holds the base url and the general suffix. When we extend the base api client behaviour, we can provide the method name to the ‘ApiConnection]’ ‘BuildUrl’ method.

http://www.baseurl.com/suffix/action?extraparam=true
-----------------------++++++%%%%%%%================
        base url       suffix action    parameters

- base url: set on HttpClient
+ suffix (addition): ApiClient specific, set in ApiConnection
% action: parameter on building url (string)
= parameters: parameter on building url (UrlBricks)

Some code for the hungry coders:

The ‘ApiClient’ class is a wrapper for the HttpClient. It’s possible to set a Token for authentication against the basic web api 2 authentication.

public class ApiClient : IApiClient
{
    /// <summary>
    /// The http client
    /// </summary>
    private readonly HttpClient _client;

    /// <summary>
    /// Constructor
    /// </summary>
    public ApiClient()
    {
        // Initialize the httpclient (api address is defined in the web.config)
        _client = new HttpClient { BaseAddress = new Uri(ConfigFileInteraction.GetValueFromConfig("ApiLocation")) };

        // Add an Accept header for JSON format.
        _client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));
    }

    /// <summary>
    /// Open the Client for direct use
    /// </summary>
    public HttpClient Client
    {
        get
        {
            return _client;
        }
    }

    /// <summary>
    /// Set the token when authorized
    /// </summary>
    public string Token
    {
        set { _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", value); }
    }

    public void Dispose()
    {
        _client.Dispose();
    }
}

The ‘ApiClientBase’ class acts like a base repository that allows for base functionality5.

public class ApiClientBase<T> : IApiClientContract<T>
    where T : class
{
    public readonly IApiClient Client;
    public ApiConnection ApiParameters;

    public ApiClientBase(IApiClient client, string addition)
    {
        Client = client;
        ApiParameters = new ApiConnection { Addition = addition };
    }

    // ListResponse is a wrapper that wraps the list together with current page, amount of pages and other parameters for easier paging
    public async Task<IApiClientResponseGet<ListResponse<T>>> Get()
    {
        IApiClientResponseGet<ListResponse<T>> result = new ResponseGet<ListResponse<T>>();

        // List all products.
        var response = await Client.Client.GetAsync(ApiParameters.BuildUrl());
        if (response.IsSuccessStatusCode)
        {
            // Parse the response body. Blocking!
            var projects = await response.Content.ReadAsAsync<ListResponse<T>>();
            result.Success = true;
            result.Object = projects;
        }
        else
        {
            result.StatusCode = (int)response.StatusCode;
            result.ReasonPhrase = response.ReasonPhrase;
            result.Success = false;
        }

        return result;
    }

    public async Task<IApiClientResponseGet<ListResponse<T>>> GetPage(int page)
    {
        IApiClientResponseGet<ListResponse<T>> result = new ResponseGet<ListResponse<T>>();

        IUrlBricks parameters = new UrlBricks();
        parameters.Parameters.Add("page", page);

        // List all products.
        var response = await Client.Client.GetAsync(ApiParameters.BuildUrl(parameters));
        if (response.IsSuccessStatusCode)
        {
            // Parse the response body. Blocking!
            var products = await response.Content.ReadAsAsync<ListResponse<T>>();
            result.Success = true;
            result.Object = products;
        }
        else
        {
            result.StatusCode = (int)response.StatusCode;
            result.ReasonPhrase = response.ReasonPhrase;
            result.Success = false;
        }

        return result;
    }

    public async Task<IApiClientResponseGet<ListResponse<T>>> Get(IUrlBricks bricks)
    {
        IApiClientResponseGet<ListResponse<T>> result = new ResponseGet<ListResponse<T>>();

        // List all products.
        var response = await Client.Client.GetAsync(ApiParameters.BuildUrl(bricks));
        if (response.IsSuccessStatusCode)
        {
            // Parse the response body. Blocking!
            var products = await response.Content.ReadAsAsync<ListResponse<T>>();
            result.Success = true;
            result.Object = products;
        }
        else
        {
            result.StatusCode = (int)response.StatusCode;
            result.ReasonPhrase = response.ReasonPhrase;
            result.Success = false;
        }

        return result;
    }

    public async Task<IApiClientResponseGet<T>> GetById(int id)
    {
        IApiClientResponseGet<T> result = new ResponseGet<T>();

        IUrlBricks parameters = new UrlBricks();
        parameters.Parameters.Add("id", id);

        // List all products.
        var response = await Client.Client.GetAsync(ApiParameters.BuildUrl(parameters));
        if (response.IsSuccessStatusCode)
        {
            if (result.Object == null)
            {
                result.Success = false;
                result.StatusCode = 404;
                result.ReasonPhrase = "Object not found";
            }
            // Parse the response body. Blocking!
            var project = await response.Content.ReadAsAsync<T>();
            result.Success = true;
            result.Object = project;
        }
        else
        {
            result.StatusCode = (int)response.StatusCode;
            result.ReasonPhrase = response.ReasonPhrase;
            result.Success = false;
        }
        return result;
    }

    public async Task<IApiClientResponseBase> Post(T model)
    {
        IApiClientResponseBase result = new Response();

        var response = await Client.Client.PostAsJsonAsync(ApiParameters.BuildUrl(), model);
        if (response.IsSuccessStatusCode)
        {
            result.Success = true;
        }
        else
        {
            result.StatusCode = (int)response.StatusCode;
            result.ReasonPhrase = response.ReasonPhrase;
            result.Success = false;
        }
        return result;
    }

    public async Task<IApiClientResponseBase> Post(T model, IUrlBricks bricks)
    {
        IApiClientResponseBase result = new Response();

        var response = await Client.Client.PostAsJsonAsync(ApiParameters.BuildUrl(bricks), model);
        if (response.IsSuccessStatusCode)
        {
            result.Success = true;
        }
        else
        {
            result.StatusCode = (int)response.StatusCode;
            result.ReasonPhrase = response.ReasonPhrase;
            result.Success = false;
        }
        return result;
    }

    public async Task<IApiClientResponseBase> Put(int id, T model)
    {
        IApiClientResponseBase result = new Response();

        IUrlBricks bricks = new UrlBricks();
        bricks.Parameters.Add("id", id);

        var response = await Client.Client.PutAsJsonAsync(ApiParameters.BuildUrl(bricks), model);
        if (response.IsSuccessStatusCode)
        {
            result.Success = true;
        }
        else
        {
            result.StatusCode = (int)response.StatusCode;
            result.ReasonPhrase = response.ReasonPhrase;
            result.Success = false;
        }
        return result;
    }

    public async Task<IApiClientResponseBase> Put(int id, T model, IUrlBricks bricks)
    {
        IApiClientResponseBase result = new Response();

        bricks.Parameters.Add("id", id);

        var response = await Client.Client.PutAsJsonAsync(ApiParameters.BuildUrl(bricks), model);
        if (response.IsSuccessStatusCode)
        {
            result.Success = true;
        }
        else
        {
            result.StatusCode = (int)response.StatusCode;
            result.ReasonPhrase = response.ReasonPhrase;
            result.Success = false;
        }
        return result;
    }

    public async Task<IApiClientResponseBase> Delete(int id)
    {
        IApiClientResponseBase result = new Response();

        IUrlBricks bricks = new UrlBricks();
        bricks.Parameters.Add("id", id);

        var response = await Client.Client.DeleteAsync(ApiParameters.BuildUrl(bricks));
        if (response.IsSuccessStatusCode)
        {
            result.Success = true;
        }
        else
        {
            result.StatusCode = (int)response.StatusCode;
            result.ReasonPhrase = response.ReasonPhrase;
            result.Success = false;
        }
        return result;
    }


    public async Task<IApiClientResponseGet<TC>> Get<TC>(IUrlBricks bricks)
    {
        IApiClientResponseGet<TC> result = new ResponseGet<TC>();

        // List all products.
        var response = await Client.Client.GetAsync(ApiParameters.BuildUrl(bricks));
        if (response.IsSuccessStatusCode)
        {
            // Parse the response body. Blocking!
            var products = await response.Content.ReadAsAsync<TC>();
            result.Success = true;
            result.Object = products;
        }
        else
        {
            result.StatusCode = (int)response.StatusCode;
            result.ReasonPhrase = response.ReasonPhrase;
            result.Success = false;
        }

        return result;
    }
}

The api client uses several objects for building the URL’s, ‘the ApiConnection’ class allows for an api client specific addition (‘Tag’ for accessing the ‘Tag’ controller, appends ‘Tag’ to the base url). The Bricks object can be used to set url parameters that have to be set on every api call from that api client:

public class ApiConnection : IApiConnectionBase
{
    // Suffix
    public string Addition { get; set; }

    // Basically a dictionary with a wrapper
    public IUrlBricks Bricks { get; set; }
}

Last, but not least, the URL builder, it does not build the entire URL, the base url is set on the HttpClient. But it can build almost every addition using a combination of ApiConnection, action and bricks. Its a collection of extension methods for ApiConnection objects:

public static class UrlBuilder
{
    public static string BuildUrl(this ApiConnection apiConnection, IUrlBricks bricks)
    {
        return BuildUrl(BuildBaseUrl(apiConnection.Addition), BuildParameters(AppendParameters(apiConnection.Bricks, bricks)));
    }

    public static string BuildUrl(this ApiConnection apiConnection)
    {
        return BuildUrl(BuildBaseUrl(apiConnection.Addition), BuildParameters(apiConnection.Bricks));
    }

    public static string BuildUrl(this ApiConnection apiConnection, string action)
    {
        return BuildUrl(BuildBaseUrl(apiConnection.Addition, action), BuildParameters(apiConnection.Bricks));
    }

    public static string BuildUrl(this ApiConnection apiConnection, string action, IUrlBricks bricks)
    {
        return BuildUrl(BuildBaseUrl(apiConnection.Addition, action),
            BuildParameters(AppendParameters(apiConnection.Bricks, bricks)));
    }

    private static string BuildUrl(string baseUrl, string parameters)
    {
        return string.Format("{0}{1}", baseUrl, parameters);
    }

    private static IUrlBricks AppendParameters(params IUrlBricks[] urlBricks)
    {
        var allBricks = new UrlBricks();
        foreach (var param in urlBricks.Where(m => m != null).SelectMany(urlBrick => urlBrick.Parameters))
        {
            allBricks.Parameters.Add(param.Key, param.Value);
        }
        return allBricks;
    }

    private static string BuildParameters(IUrlBricks bricks)
    {
        var parameters = new StringBuilder();
        if (bricks != null && bricks.Parameters.Any())
        {
            parameters.Append("?");

            var last = bricks.Parameters.LastOrDefault().Key;

            foreach (var param in bricks.Parameters)
            {
                parameters.Append(BuildParameter(param.Key, param.Value, last));
            }
        }
        return parameters.ToString();
    }

    private static string BuildParameter(string key, object value, string last)
    {
        string encodedValue = value == null ? "" : HttpUtility.UrlEncode(value.ToString());
        return string.Format(last.Equals(key) ? "{0}={1}" : "{0}={1}&", key, encodedValue);
    }

    private static string BuildBaseUrl(string addition)
    {
        return addition;
    }

    private static string BuildBaseUrl(string addition, string action)
    {
        return string.Format("{0}/{1}", addition, action);
    }
}

Implementation of the ‘TagApiClient’ that can be extended or overridden when necessary:

// Use: await new TagApiClient().Get();
public class TagApiClient : ApiClientBase<TagDto>, ITagApiClient
{
    public TagApiClient(IApiClient client)
        : base(client, "api/Tag")
    {
    }
}

I use dependency injection in the mvc layer of the website to insert the ApiClients into the controllers but that’s for the next page. That’s all there is to it. Not very complicated and I think we can thank the inventor of RESTfull api’s here. It’s that way of thinking that makes it very easy to handle resources when you’re using a service.

As a last addition, this is the api client that allows for authentication against the default ‘individual user accounts’ set-up when creating a web api 2 project:

public class UserApiClient : IUserApiClient
{
    private readonly IApiClient _client;

    /// <summary>
    /// Api parameters ( url from base api client + addition + url parameters )
    /// </summary>
    private readonly ApiConnection _apiParameters;

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="client">Injected by ninject: Base api client object</param>
    public UserApiClient(IApiClient client)
    {
        _client = client;

        // The addition is specific for an apiclient, in this case it's account
        _apiParameters = new ApiConnection { Addition = "api/Account" };
    }

    public async Task<IApiClientResponseGet<TokenDto>> RegisterAndAuthorize(RegisterBindingModel registerModel)
    {
        IApiClientResponseGet<TokenDto> result = new ResponseGet<TokenDto>();

        // POST the register model, use an action ("Register") full URL: http://...com/api/Account/Register
        HttpResponseMessage response = await _client.Client.PostAsJsonAsync(_apiParameters.BuildUrl("Register"), registerModel);
        if (response.IsSuccessStatusCode) // If registration is successful, login the new user
        {
            result = await AuthorizeAndGetToken(registerModel.UserName, registerModel.Password);
        }
        else // Return the correct status code / Throw error?
        {
            result.StatusCode = (int)response.StatusCode;
            if (result.StatusCode == 400)
            {
                var modelErrors = await response.Content.ReadAsAsync<ErrorResponse>();
                result.ReasonPhrase = modelErrors.Message;
                result.Modelstate = modelErrors.Modelstate;
            }
            else
            {
                result.ReasonPhrase = response.ReasonPhrase;
            }
            result.Success = false;
        }

        return result;
    }

    public async Task<IApiClientResponseGet<TokenDto>> AuthorizeAndGetToken(string username, string password)
    {
        IApiClientResponseGet<TokenDto> result = new ResponseGet<TokenDto>();

        // Build the HTTP content string for authentication
        HttpContent content = new StringContent(string.Format("grant_type=password&username={0}&password={1}", username, password));

        // Execute a request directly on the httpClient (Token has a special url: http://...com/Token)
        HttpResponseMessage response = await _client.Client.PostAsync("Token", content);
        if (response.IsSuccessStatusCode)
        {
            // Parse the response body.
            var token = await response.Content.ReadAsAsync<TokenDto>();
            result.Success = true;
            result.Object = token;
        }
        else
        {
            result.StatusCode = (int)response.StatusCode;
            if (result.StatusCode == 400)
            {
                var modelErrors = await response.Content.ReadAsAsync<ErrorResponse>();
                result.ReasonPhrase = modelErrors.Message;
                result.Modelstate = modelErrors.Modelstate;
            }
            else
            {
                result.ReasonPhrase = response.ReasonPhrase;
            }
            result.Success = false;
        }

        return result;
    }

    public void Dispose()
    {
        _client.Dispose();
    }
}

See you next post!

Newer Older Top
blog comments powered by Disqus