Invisual.Libraries.Rest.Json

Make rest requests with strongly typed requests and responses. Includes preset and configurable retry policies.


Keywords
Invisual, Rest
License
MIT
Install
Install-Package Invisual.Libraries.Rest.Json -Version 2.1.2

Documentation

InvisualRest

NuGet License

Make rest requests with strongly typed requests and responses. Includes preset and configurable retry policies.

Features

  • Supports GET, POST, PATCH, PUT, DELETE
  • Fully Asynchronous
  • Basic HTTP Authentication
  • Customisation of HTTP headers
  • Uses Json.NET to deserialise to strongly typed response objects
  • Built-in customisable retry logic
  • Many configuration options allowing compatibility with most REST APIs.

Installation

To install InvisualRest, run the following command in the Package Manager Console

PM> Install-Package Invisual.Libraries.Rest.Json

Examples

The examples here aren't just made up code. They all use the excellent free demo REST api at https://jsonplaceholder.typicode.com and will actually work.

Simple GET

var client = new JsonRestClient("https://jsonplaceholder.typicode.com");

Post post = await client.GetAsync<Post>("posts/1");

public class Post
{
    public string UserId { get; set; }
    public int Id { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }
}

Simple GET - Resource List

var client = new JsonRestClient("https://jsonplaceholder.typicode.com");

List<Post> post = await client.GetAsync<List<Post>>("posts");

public class Post
{
    public string UserId { get; set; }
    public int Id { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }
}

GET with strongly typed request

Pass an object to a call to GetAsync() to have it automatically serialised and added as a querystring. Parameters will obey any serialisation options, such as PropertyStyle.CamelCase, or NullValues.Ignore, as well as Json.NET property annotations.

var client = new JsonRestClient(
    "https://jsonplaceholder.typicode.com",
    new JsonRestClientOptions
    {
        NullValueHandling = NullValues.Ignore,
        PropertyStyle = PropertyStyle.CamelCase,
    });

List<Post> post = await client.GetAsync<List<Post>>("posts", new PostRequest { UserId = "1", Id = 1 });

// -> https://jsonplaceholder.typicode.com/posts?userId=1&id=1

public class PostRequest
{
    public string Title { get; set; }
    public string UserId { get; set; }

    [JsonProperty("id")]
    public int? Id { get; set; }
}

Simple POST

var client = new JsonRestClient("https://jsonplaceholder.typicode.com");

Post newBlogPost = await client.PostAsync<Post>(
    "posts",
    new Post
    {
        Title = "foo",
        UserId = "1",
        Body = "bar"
    });

Basic HTTP Authentication

var client = new JsonRestClient(
    "https://jsonplaceholder.typicode.com",
    new JsonRestClientOptions
    {
        AuthenticationInfo = new AuthenticationInfo("username", "password")
    });

Custom Headers

var client = new JsonRestClient("https://jsonplaceholder.typicode.com");

client.RequestHeaders.Add("Authorization", "Bearer NDQ3OGViN2FjMTM4YTEzNjg1MmJhYmQ4NjE5NTZjMTk6M2U1YTZlZGVjNzFlYWIwMzk0MjJjNjQ0NGQwMjY1OWQ=");

Retry Logic

By default, requests will not be retried. Simply pass a RetryPolicy instance into the JsonRestClient constructor to enable retry logic. There are a number of convenience extension methods to help setup advanced retry logic without too much faff.

Retry every 1 second
var client = new JsonRestClient(
    "https://jsonplaceholder.typicode.com",
    new JsonRestClientOptions
    {
        RetryPolicy = Retry.Every(1000)
    });
Exponential Backoff

Progressively lengthen the time between retries.

var client = new JsonRestClient(
    "https://jsonplaceholder.typicode.com",
    new JsonRestClientOptions
    {
        RetryPolicy = Retry.WithExponentialBackoff()
    });
Limit Number of Attempts

Stop retrying after n attempts.

var client = new JsonRestClient(
    "https://jsonplaceholder.typicode.com",
    new JsonRestClientOptions
    {
        RetryPolicy = Retry.Every(1000).StopAfter(5)
    });
Retry on Specified HTTP Status codes
var client = new JsonRestClient(
    "https://jsonplaceholder.typicode.com",
    new JsonRestClientOptions
    {
        RetryPolicy = Retry.Every(1000).On(429, 500)
    });
Retry On Exceptions
var client = new JsonRestClient(
    "https://jsonplaceholder.typicode.com",
    new JsonRestClientOptions
    {
        RetryPolicy = Retry
            .Every(1000)
            .OnException()
            .StopAfter(5)
    });

It is recommended to use OnException() carefully. Exceptions may be caused by non-transient errors, such as a bug or long term outage. It is recommended practice to chain a call to OnException with StopAfter() and/or WithExponentialBackoff().

Status Code Handling

REST APIs around the web differ in their error state handling. Some will return a 404 status code for a missing resource, whereas others may return a 200 status code, but include error information in the response. Others still may do a combination of these. By default InvisualRest will attempt to deserialise every response, regardless of status. Sometimes, this behaviour may not be desired, so it can be disabled. This is best shown with examples.

The https://jsonplaceholder.typicode.com API returns a 404 status code for a missing resource, with the following JSON.

{}

The default InvisualRest options will deserialise this to a default version of your supplied type. E.g.

Blank Resource

In a lot of cases, this is not desirable. Is this a resource with null values, or a missing resource? Since the response does not contain any information about a valid resource, it would be better to not deserialise the response, and handle this state unambiguously, throwing an exception for all non-200 statuses.

var client = new JsonRestClient(
    "https://jsonplaceholder.typicode.com",
    new JsonRestClientOptions
    {
        NonHttpSuccessCodeHandling = NonHttpSuccessCodes.ThrowException
    });

Now, all unsuccessful requests will throw a RestException. This exception contains an HttpStatusCode and a RawResponse property in case you need more specific information about the response.

If an API provider returns error information with each response, and with a non-200 status. E.g.

{
    "errorCode": "4b",
    "errorMessage": "Resource not found" 
}

You can handle this in two ways. Configure InvisualRest to throw an exception as above, and use the HttpStatusCode property to control your logic. You can use the RawResponse property of the exception to access the reponse if you need more info. Alternatively, you could include the error properties on all of your response types, and allow InvisualRest to deserialise the response, regardless of status code. Here is an example of one way to implement such a response class.

public abstract class Response
{
    public string ErrorCode { get; set; }
    public string ErrorMessage { get; set; }

    public bool HasError => ErrorCode != null;
}

public class Post : Response
{
    public string UserId { get; set; }
    public int Id { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }
}

All Configuration Options

As seen above with the retry logic, you can pass a JsonRestClientOptions object into the JsonRestClient constructor. Here are all available options.

Property Type Default Value Available Values Notes
AuthenticationInfo AuthenticationInfo null - To provide credentials for Basic HTTP Authentication. See Basic HTTP Authentication
NonHttpSuccessCodeHandling enum (NonHttpSuccessCodes) Continue Continue, ThrowException Choose to throw an exception when a non-200 HTTP status code is received. See Status Code Handling.
NullValueHandling enum (NullValues) Include Include, Ignore Choose whether or not to include properties with null values in the serialised request, or omit them altogether.
PropertyStyle enum (PropertyStyle) Unmodified Unmodified, CamelCase Serialise/Deserialise property names as-is, or convert to/from camel case. Property-level Json.NET attributes will be respected.
RetryPolicy RetryPolicy null - See Retry Logic