Giter VIP home page Giter VIP logo

honeybear.halclient's Introduction

HoneyBear.HalClient

Build Status Coverage Status NuGet Version

A lightweight fluent .NET client for navigating and consuming HAL APIs.

What is HAL?

HAL (Hypertext Application Language) is a specification for a lightweight hypermedia type.

What's Nice About this HAL Client

There are already a number of open-source .NET HAL clients available. HoneyBear.HalClient differs because it offers all of the following features:

  • Provides a fluent-like API for navigating a HAL API.
  • No additional attributes or semantics are required on the API contract. Resources can be deserialised into POCOs.
  • Supports the Hypertext Cache Pattern; it treats embedded resources in the same way as it handles links.
  • Supports URI templated links. It uses Tavis.UriTemplates under the hood.

Known Limitations

  • HoneyBear.HalClient only supports the JSON HAL format.

Feedback Welcome

If you have any issues, suggests or comments, please create an issue or a pull request.

Getting Started

1) Install the NuGet package

Install-Package HoneyBear.HalClient

2) Create an instance of HalClient

HalClient has a dependency on HttpClient. This can be provided in the constructor:

var halClent = new HalClient(new HttpClient { BaseAddress = new Uri("https://api.retail.com/") });

Or accessed via a public property:

var halClent = new HalClient();
halClent.HttpClient.BaseAddress = new Uri("https://api.retail.com/");

(Optional) Custom serializer settings

HalClient uses the default JsonMediaTypeFormatter for handling deserialization of responses. If you need to change any of the settings (for handling null values, missing properties, custom date formats and so on), you can build a custom MediaTypeFormatter by subclassing JsonMediaTypeFormatter, and then passing it in to the HalClient constructor:

public class CustomMediaTypeFormatter : JsonMediaTypeFormatter
{
    SerializerSettings.NullValueHandling = NullValueHandling.Ignore;

    SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/hal+json"));
}

var halClent = new HalClient(new HttpClient { BaseAddress = new Uri("https://api.retail.com/") }, new List<MediaTypeFormatter> { new CustomMediaTypeFormatter() });

(Optional) Override default implementation of IJsonHttpClient

By default, HalClient uses a internal implementation of IJsonHttpClient, which uses HttpClient to perform HTTP requests (GET, POST, PUT and DELETE). In some cases, it may be preferable to provide your own implementation of IJsonHttpClient. For example, if you want to specify a different MediaTypeFormatter for serializing POST and PUT requests:

public class CustomJsonHttpClient : IJsonHttpClient
{
    private readonly CustomMediaTypeFormatter _formatter;

    public CustomJsonHttpClient(HttpClient client, CustomMediaTypeFormatter formatter)
    {
        HttpClient = client;
	_formatter = formatter;
        HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/hal+json"));
    }

    public HttpClient HttpClient { get; }

    public Task<HttpResponseMessage> GetAsync(string uri)
        => HttpClient.GetAsync(uri);

    public Task<HttpResponseMessage> PostAsync<T>(string uri, T value)
        => HttpClient.PostAsync(uri, value, _formatter);

    public Task<HttpResponseMessage> PutAsync<T>(string uri, T value)
        => HttpClient.PutAsync(uri, value, _formatter);

    public Task<HttpResponseMessage> DeleteAsync(string uri)
        => HttpClient.DeleteAsync(uri);
}
var jsonClient = new CustomJsonHttpClient(new HttpClient(), new CustomMediaTypeFormatter());
var halClent = new HalClient(jsonClient);

or

var jsonClient = new CustomJsonHttpClient(new HttpClient(), new CustomMediaTypeFormatter());
var formatters = new List<MediaTypeFormatter> { new CustomMediaTypeFormatter() };
var halClent = new HalClient(jsonClient, formatters);

Usage Examples

The following examples are based on the example JSON below.

1) Retrieve a single resource

IResource<Order> order =
    client
        .Root("/v1/version/1")
        .Get("order", new {orderRef = "46AC5C29-B8EB-43E7-932E-19167DA9F5D3"}, "retail")
        .Item<Order>();
  1. GET https://api.retail.com/v1/version/1
  2. GET https://api.retail.com/v1/order/46AC5C29-B8EB-43E7-932E-19167DA9F5D3
  3. Reads Order resource

2) Deserialise that resource into a POCO

Order order =
    client
        .Root("/v1/version/1")
        .Get("order", new {orderRef = "46AC5C29-B8EB-43E7-932E-19167DA9F5D3"}, "retail")
        .Item<Order>()
        .Data;
  1. GET https://api.retail.com/v1/version/1
  2. GET https://api.retail.com/v1/order/46AC5C29-B8EB-43E7-932E-19167DA9F5D3
  3. Reads Order resource
  4. Deserialises resource into Order

3) Retrieve a list of resources (embedded in a paged list resource)

IEnumerable<IResource<Order>> orders =
    client
        .Root("/v1/version/1")
        .Get("order-query", new {pageNumber = 0}, "retail")
        .Get("order", "retail")
        .Items<Order>();
  1. GET https://api.retail.com/v1/version/1
  2. GET https://api.retail.com/v1/order?pagenumber=0
  3. Reads embedded array of Order resources

4) Deserialise the list of resources into POCOs

IEnumerable<Order> orders =
    client
        .Root("/v1/version/1")
        .Get("order-query", new {pageNumber = 0}, "retail")
        .Get("order", "retail")
        .Items<Order>()
        .Data();
  1. GET https://api.retail.com/v1/version/1
  2. GET https://api.retail.com/v1/order?pagenumber=0
  3. Reads embedded array of Order resources
  4. Deserialises resources into a list of Orders

5) Create a resource

var payload = new { ... };

Order order =
    client
        .Root("/v1/version/1")
        .Post("order-add", payload, "retail")
        .Item<Order>()
        .Data;
  1. GET https://api.retail.com/v1/version/1
  2. POST https://api.retail.com/v1/order (with payload)
  3. Reads Order resource from response
  4. Deserialises resource into Order

6) Update a resource

var payload = new { ... };

Order order =
    client
        .Root("/v1/version/1")
        .Get("order", new {orderRef = "46AC5C29-B8EB-43E7-932E-19167DA9F5D3"}, "retail")
        .Put("order-edit", payload, "retail")
        .Item<Order>()
        .Data;
  1. GET https://api.retail.com/v1/version/1
  2. GET https://api.retail.com/v1/order/46AC5C29-B8EB-43E7-932E-19167DA9F5D3
  3. PUT https://api.retail.com/v1/order/46AC5C29-B8EB-43E7-932E-19167DA9F5D3 (with payload)
  4. Reads Order resource from response
  5. Deserialises resource into Order

7) Delete a resource

client
    .Root("/v1/version/1")
    .Get("order", new {orderRef = "46AC5C29-B8EB-43E7-932E-19167DA9F5D3"}, "retail")
    .Delete("order-delete", "retail");
  1. GET https://api.retail.com/v1/version/1
  2. GET https://api.retail.com/v1/order/46AC5C29-B8EB-43E7-932E-19167DA9F5D3
  3. DELETE https://api.retail.com/v1/order/46AC5C29-B8EB-43E7-932E-19167DA9F5D3

8) Retrieve a resource's links

IList<ILink> links =
    client
        .Root("/v1/version/1")
        .Get("order", new {orderRef = "46AC5C29-B8EB-43E7-932E-19167DA9F5D3"}, "retail")
        .Item<Order>()
        .Links;
  1. GET https://api.retail.com/v1/version/1
  2. GET https://api.retail.com/v1/order/46AC5C29-B8EB-43E7-932E-19167DA9F5D3
  3. Reads Order resource
  4. Returns the Order resource's links, e.g. self, retail:order-edit, retail:order-delete.

Dependency Injection

HalClient implements interface IHalClient. Registering it with Autofac might look something like this:

builder
    .RegisterType<HttpClient>()
    .WithProperty("BaseAddress", new Uri("https://api.retail.com"))
    .AsSelf();

builder
    .RegisterType<HalClient>()
    .As<IHalClient>();

Example JSON

Root resource: https://api.retail.com/v1/version/1

{
  "versionNumber": 1,
  "_links": {
    "curies": [
      {
        "href": "https://api.retail.com/v1/docs/{rel}",
        "name": "retail",
        "templated": true
      }
    ],
    "self": {
      "href": "/v1/version/1"
    },
    "retail:order-query": {
      "href": "/v1/order?pageNumber={pageNumber}&pageSize={pageSize}",
      "templated": true
    },
    "retail:order": {
      "href": "/v1/order/{orderRef}",
      "templated": true
    },
    "retail:order-add": {
      "href": "/v1/order"
    },
    "retail:order-queryby-user": {
      "href": "/v1/order?userRef={userRef}",
      "templated": true
    }
  }
}

Order resource: https://api.retail.com/v1/order/46AC5C29-B8EB-43E7-932E-19167DA9F5D3

{
  "orderRef": "46ac5c29-b8eb-43e7-932e-19167da9f5d3",
  "orderNumber": "123456",
  "status": "AwaitingPayment",
  "total": {
    "amount": 100.0,
    "currency": "USD"
  },
  "_links": {
    "curies": [
      {
        "href": "https://api.retail.com/v1/docs/{rel}",
        "name": "retail",
        "templated": true
      }
    ],
    "self": {
      "href": "/v1/order/46ac5c29-b8eb-43e7-932e-19167da9f5d3"
    },
    "retail:order-edit": {
      "href": "/v1/order/46ac5c29-b8eb-43e7-932e-19167da9f5d3"
    },
    "retail:order-delete": {
      "href": "/v1/order/46ac5c29-b8eb-43e7-932e-19167da9f5d3"
    },
    "retail:orderitem": {
      "href": "/v1/orderitem"
    }
  },
  "_embedded": {
    "retail:orderitem": [
      {
        "orderItemRef": "d7161f76-ed17-4156-a627-bc13b43345ab",
        "status": "AwaitingPayment",
        "total": {
          "amount": 20.0,
          "currency": "USD"
        },
        "quantity": 1,
        "_links": {
          "self": {
            "href": "/v1/orderitem"
          },
          "retail:product": {
            "href": "/v1/product/637ade4e-e927-4d4a-a628-32055ae5a12b"
          }
        }
      },
      {
        "orderItemRef": "25d61931-181b-4b09-b883-c6fb374d5f4a",
        "status": "AwaitingPayment",
        "total": {
          "amount": 30.0,
          "currency": "USD"
        },
        "quantity": 2,
        "_links": {
          "self": {
            "href": "/v1/orderitem"
          },
          "retail:product": {
            "href": "/v1/product/fdc0d414-23a1-4208-a20a-9eeab0351f76"
          }
        }
      }
    ]
  }
}

Paged list of Orders resource: https://api.retail.com/v1/order?pageNumber=0

{
  "pageNumber": 0,
  "pageSize": 10,
  "knownPagesAvailable": 1,
  "totalItemsCount": 1,
  "_links": {
    "curies": [
      {
        "href": "https://api.retail.com/v1/docs/{rel}",
        "name": "retail",
        "templated": true
      }
    ],
    "self": {
      "href": "/v1/order?pageNumber=0&pageSize=10"
    },
    "retail:order": {
      "href": "/v1/order/{orderRef}",
      "templated": true
    }
  },
  "_embedded": {
    "retail:order": [
      {
        "orderRef": "e897113c-4c56-404b-8e83-7e7f705046b3",
        "orderNumber": "789456",
        "status": "AwaitingPayment",
        "total": {
          "amount": 100.0,
          "currency": "USD"
        },
        "_links": {
          "self": {
            "href": "/v1/order/e897113c-4c56-404b-8e83-7e7f705046b3"
          },
          "retail:order-edit": {
            "href": "/v1/order/e897113c-4c56-404b-8e83-7e7f705046b3"
          },
          "retail:order-delete": {
            "href": "/v1/order/e897113c-4c56-404b-8e83-7e7f705046b3"
          },
          "retail:orderitem-queryby-order": {
            "href": "/v1/orderitem?pageNumber={pageNumber}&pageSize={pageSize}&orderRef=e897113c-4c56-404b-8e83-7e7f705046b3",
            "templated": true
          }
        }
      }
    ]
  }
}

honeybear.halclient's People

Contributors

eoin55 avatar berhir avatar bobhonores avatar dmunch avatar christianz avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.