https://github.com/eoin55/honeybear.halclient
A lightweight fluent .NET client for navigating and consuming HAL APIs.
https://github.com/eoin55/honeybear.halclient
api-client api-rest dotnet dotnet-core dotnetcore fluent fluent-chaining-methods fluent-interface hal-api hal-client hateoas hateoas-hal json netstandard rest rest-api rest-client restapi restful restful-api
Last synced: 3 months ago
JSON representation
A lightweight fluent .NET client for navigating and consuming HAL APIs.
- Host: GitHub
- URL: https://github.com/eoin55/honeybear.halclient
- Owner: eoin55
- License: mit
- Created: 2016-01-15T13:04:44.000Z (over 10 years ago)
- Default Branch: master
- Last Pushed: 2022-12-08T00:54:04.000Z (over 3 years ago)
- Last Synced: 2025-04-12T02:48:16.522Z (about 1 year ago)
- Topics: api-client, api-rest, dotnet, dotnet-core, dotnetcore, fluent, fluent-chaining-methods, fluent-interface, hal-api, hal-client, hateoas, hateoas-hal, json, netstandard, rest, rest-api, rest-client, restapi, restful, restful-api
- Language: C#
- Homepage:
- Size: 119 KB
- Stars: 32
- Watchers: 4
- Forks: 9
- Open Issues: 6
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# HoneyBear.HalClient

[](https://coveralls.io/github/eoin55/HoneyBear.HalClient?branch=master)
[](https://www.nuget.org/packages/HoneyBear.HalClient/)
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.
* [HAL Specification](http://stateless.co/hal_specification.html)
* Prezi: [RESTful Design Using HAL](https://prezi.com/4b9fpmopta0g/restful-design-using-hal/)
## What's Nice About this HAL Client
There are already a number of open-source .NET HAL clients [available](https://github.com/mikekelly/hal_specification/wiki/Libraries#c-sharp). *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](http://stackoverflow.com/questions/250001/poco-definition).
* Supports the [Hypertext Cache Pattern](https://tools.ietf.org/html/draft-kelly-json-hal-06#section-8.3); it treats *embedded* resources in the same way as it handles *links*.
* Supports [URI templated](https://tools.ietf.org/html/rfc6570) links. It uses [Tavis.UriTemplates](https://github.com/tavis-software/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](https://github.com/eoin55/HoneyBear.HalClient/issues) or a [pull request](https://github.com/eoin55/HoneyBear.HalClient/pulls).
## Getting Started
### 1) Install the NuGet package
```cs
Install-Package HoneyBear.HalClient
```
### 2) Create an instance of HalClient
`HalClient` has a dependency on `HttpClient`. This can be provided in the constructor:
```cs
var halClent = new HalClient(new HttpClient { BaseAddress = new Uri("https://api.retail.com/") });
```
Or accessed via a public property:
```cs
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:
```cs
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 { 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:
```cs
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 GetAsync(string uri)
=> HttpClient.GetAsync(uri);
public Task PostAsync(string uri, T value)
=> HttpClient.PostAsync(uri, value, _formatter);
public Task PutAsync(string uri, T value)
=> HttpClient.PutAsync(uri, value, _formatter);
public Task DeleteAsync(string uri)
=> HttpClient.DeleteAsync(uri);
}
```
```cs
var jsonClient = new CustomJsonHttpClient(new HttpClient(), new CustomMediaTypeFormatter());
var halClent = new HalClient(jsonClient);
```
or
```cs
var jsonClient = new CustomJsonHttpClient(new HttpClient(), new CustomMediaTypeFormatter());
var formatters = new List { new CustomMediaTypeFormatter() };
var halClent = new HalClient(jsonClient, formatters);
```
## Usage Examples
The following examples are based on the [example JSON](#example-json) below.
### 1) Retrieve a single resource
```cs
IResource order =
client
.Root("/v1/version/1")
.Get("order", new {orderRef = "46AC5C29-B8EB-43E7-932E-19167DA9F5D3"}, "retail")
.Item();
```
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
```cs
Order order =
client
.Root("/v1/version/1")
.Get("order", new {orderRef = "46AC5C29-B8EB-43E7-932E-19167DA9F5D3"}, "retail")
.Item()
.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)
```cs
IEnumerable> orders =
client
.Root("/v1/version/1")
.Get("order-query", new {pageNumber = 0}, "retail")
.Get("order", "retail")
.Items();
```
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
```cs
IEnumerable orders =
client
.Root("/v1/version/1")
.Get("order-query", new {pageNumber = 0}, "retail")
.Get("order", "retail")
.Items()
.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 `Order`s
### 5) Create a resource
```cs
var payload = new { ... };
Order order =
client
.Root("/v1/version/1")
.Post("order-add", payload, "retail")
.Item()
.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
```cs
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()
.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
```cs
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
```cs
IList links =
client
.Root("/v1/version/1")
.Get("order", new {orderRef = "46AC5C29-B8EB-43E7-932E-19167DA9F5D3"}, "retail")
.Item()
.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](http://autofac.org/) might look something like this:
```cs
builder
.RegisterType()
.WithProperty("BaseAddress", new Uri("https://api.retail.com"))
.AsSelf();
builder
.RegisterType()
.As();
```
## Example JSON
Root resource: https://api.retail.com/v1/version/1
```json
{
"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
```json
{
"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
```json
{
"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
}
}
}
]
}
}
```