MeyerCorp.HateoasBuilder
1.8.1
dotnet add package MeyerCorp.HateoasBuilder --version 1.8.1
NuGet\Install-Package MeyerCorp.HateoasBuilder -Version 1.8.1
<PackageReference Include="MeyerCorp.HateoasBuilder" Version="1.8.1" />
paket add MeyerCorp.HateoasBuilder --version 1.8.1
#r "nuget: MeyerCorp.HateoasBuilder, 1.8.1"
// Install MeyerCorp.HateoasBuilder as a Cake Addin #addin nuget:?package=MeyerCorp.HateoasBuilder&version=1.8.1 // Install MeyerCorp.HateoasBuilder as a Cake Tool #tool nuget:?package=MeyerCorp.HateoasBuilder&version=1.8.1
MeyerCorp.HateoasBuilder
A .NET Standard Library allowing convenient creation of HATEOAS for REST API models created by developers at Meyer Corporation.
Background
HATEOAS or Hypermedia as the Engine of Application State is a helpful feature of REST and one many consider necessary to implement REST propertly. It can be often overlooked by backend developers as not critical but is very helpful to SPA development as API responses will contain predictable object schemas with URLs that can be used in pages of the SPA and linked pages ad infinitum.
In the following example, data is returned as an array of items. Each item has a description as well as an array of links. In this case, only one link is necessary to inform the consumer where the details for each item can be found. There is an array of links describing how to retrieve the previous page or data, the next page, and this page which is helpful for paginated data.
{
"data":[
{
"description":"first item",
"links":[
{
"href":"https://foo.bar/api/item/1",
"rel":"self",
"type":"GET"
}
]
},
{
"description":"second item",
"links":[
{
"href":"https://foo.bar/api/item/2",
"rel":"self",
"type":"GET"
}
]
},
{
"description":"third item",
"links":[
{
"href":"https://foo.bar/api/item/3",
"rel":"self",
"type":"GET"
}
]
},
],
"links":[
{
"href":"https://foo.bar/api/main?page=2",
"rel":"self",
"type":"GET"
},
{
"href":"https://foo.bar/api/main?page=1",
"rel":"previous",
"type":"GET"
},
{
"href":"https://foo.bar/api/main?page=3",
"rel":"next",
"type":"GET"
}
]
}
I am not trying to convince anyone that they need to use HATEOAS, but if you do, and you are creating APIs in .NET, this can make it far more convenient.
Getting Started
Add the nuget package to your .NET web application.
When returning data as a model in a method of a Web API controller, start by using one of the extension methods to create a link array.
// Some controller method
[HttpGet("all")]
public object GetAll()
{
// The relative URL of the link target
var relativeUrl1 = "employees?page=1"
var relativeUrl2 = "employees?page=2"
// Create a link array that has links for pagination to that relative URL
var links = HttpContext
.AddLink("previous", relativeUrl1)
.AddLink("next", relativeUrl2)
.Build();
return new
{
Links = links,
Results = Enumerable.Range(1, 6).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)],
// Here we use a format string to create our link
// http://base.url/WeatherForecast/1
Links = HttpContext.AddFormattedLink("self", "WeatherForecast/{0}", index)
.Build(),
})
.ToArray()
};
}
By using the extension method for the HttpContext
, the link builder is able to determine the base URL and append the employees route. This allow you to not worry what the base URL is as the HttpContext
knows this and links can be created dynamically.
Sample API
Included in this repository is a minimal .NET Web API application that references the library and demonstrates how to use some methods. Feel free to use this Postman collection to make calls to the API: MmeyerCorporation/Hateoasbuilder.
Methods
Various methods allow convenient creation of links. They are all methods of the LinkBuilder class as well as complimentary extension methods that allow initializing the LinkBuilder object starting with a base URL string or the HttpContext
property of a Web API controller.
AddLink
AddLink
allows you to add a raw relative URL to the base URL which is either extracted from the HttpContext
or a string.
"https://foo.bar".AddLink("label", "relativeLinkWithRoutesAndQueries");
// or
this.HttpContext
.AddLink("previous", "data?page=1") //https://foobar/data?page=1
.AddLink("self", "data?page=2") //https://foobar/data?page=2
.AddLink("next", "data?page=3"); //https://foobar/data?page=3
AddRouteLink
AddRouteLink
allows you to add a relative URL to the base URL which is either extracted from the HttpContext
or a string and add as many route items as you like appended to that.
Example
"https://foo.bar".AddRouteLink("label", "relativeUrl", "route", 1, "subroute", 2);
// or
this.HttpContext
.AddRouteLink("employees", "id", 1, "dateOfHire") //https://foobar/employees/id/1/dateOfHire
.AddRouteLink("locations", "id", 2, "address") //https://foobar/employees/id/2/address
.AddRouteLink("products", "id", 3, "price"); //https://foobar/employees/id/3/price
AddQueryLink
AddRouteLink
allows you to add a relative URL to the base URL which is either extracted from the HttpContext
or a string and add as many query parameters as you like appended to that.
AddQueryLink Example
"https://foo.bar".AddQueryLink("label", "relativeUrl", "route", 1, "subroute", 2);
// or
this.HttpContext
.AddQueryLink("employees", "id", 1, "dateOfHire", "wednesday") //https://foobar/employees?id=1&dateOfHire=wednesday
.AddQueryLink("locations", "id", 2, "address", "95687") //https://foobar/locations?id=2&address=95687
.AddQueryLink("products", "id", 3, "price", "100") //https://foobar/products?id=3&price=100
.AddQueryLink("products", "id", null, "price", "100"); //https://foobar/products?id=&price=100
AddFormattedLink
AddFormattedLink
allows you to add a relative URL to the base URL which is either extracted from the HttpContext
or a string and format your URL as you like as if you were using String.Format()
.
AddFormattedLink Example
this.HttpContext
.AddFormattedLink("{0}/{1}/{2}?{3}={4}"employees", "id", 1, "dateOfHire", "wednesday") //https://foobar/employees/id/1?dateOfHire=wednesday
.AddFormattedLink("locations", "id", 2, "address") //https://foobar/locations/id/2/address
.AddFormattedLink("products", "id", 3, "price"); //https://foobar/products/id/3/price
Build | BuildEncoded
Build
or BuildEncoded
is always the final call in the chain and returns the the links/name pairs as a collection which can be added to your returned data object. The Link
objects will serialize to JSON automatically. XML is not officially supported at this time. BuildEncoded
will encode your URLs which is a good practice but also crucial if your parameters contain special characters such as spaces, question marks, equal signs, etc.
Build Example
this.HttpContext
.AddRouteLink("employees", "id", 1, "dateOfHire") //https://foobar/employees/id/1/dateOfHire
.Build(); //[ https://foobar/employees/id/3/price ... ]
AddParameters
AddParameters
allows you to add any number of query parameters to the end of the last link that it is run from. The parameters are added as pairs of parameters in the .NET method with each pair representing a parameter name, then value ("name", "value", "name1", "value1")
which yields ?name=value&name1=value1
. The AddParameters
method will only work after the Add**Link
methods adding the parameters to that link it follows. Do not chain the AddParameters
method.
AddParameters Example
this.HttpContext
.AddRouteLink("employees", "id", 1, "dateOfHire") //https://foobar/employees/id/1/dateOfHire
.AddParameters("location", "1", "address",2 ) //?location=1&address=2
.Build(); //Yields: https://foobar/employees/id/1/dateOfHire?location=1&address=2
Best Practices
- Always use the extension methods to create the link builder.
- Do not try to create a link builder as a member of a class. Only create an instance as a local variable in a method.
- Do not reuse an instance of the link builder.
- Most methods rely on a
params
attributed array parameter so you can just use an array instead of an explicit list of values.
Which Method to Use
If your URL is simple and not dynamically generated, or you want to format your URL using inline syntax, then use AddLink
:
var relativeUrl = "employees";
var route1 = "id";
var routeValue = "1234";
var query = "dateOfHire";
var queryValue = "20231225";
var query1 = "location";
var queryValue1 = "Vallejo";
var href = $"{relativeUrl}/{route1}/{routeValue}?{query}={queryValue}&{query1}={queryValue1}";
HttpContext.AddLink(href);
// https://foo.bar/employees/id/1234?dateOfHire=20231225&location=Vallejo
If your link relies on the route (w/o parameters) , then use AddRouteLink
:
var relativeUrl = "employees";
var route1 = "id";
var routeValue1 = "1234";
var route2 = "dateOfHire";
var routeValue2 = "20231225";
var route3 = "location";
var routeValue3 = "Vallejo";
HttpContext.AddRouteLink(relativeUrl, route1, reouteValue1, route2, routeValue2, route3, routeValue3);
// https://foo.bar/employees/id/1234/dateOfHire/20231225/location/Vallejo
If your link relies on only parameters, then use AddQueryLink
:
var relativeUrl = "employees";
var param1 = "id";
var paramValue1 = "1234";
var param2 = "dateOfHire";
var paramValue2 = "20231225";
var param3 = "location";
var paramValue3 = "Vallejo";
HttpContext.AddQueryLink(relativeUrl, param1, paramValue1, param2, paramValue2, param3, paramValue3);
// https://foo.bar/employees/?id=1234&dateOfHire=20231225&location=Vallejo
If your link relies on a route along with parameters, then use AddQueryLink
and AddParameters
:
var relativeUrl = "employees";
var route1 = "id";
var routeValue1 = "1234";
var param2 = "dateOfHire";
var paramValue2 = "20231225";
var param3 = "location";
var paramValue3 = "Vallejo";
HttpContext.AddRouteLink(relativeUrl, route1, routeValue1).AddParameters(param2, paramValue2, param3, paramValue3);
// https://foo.bar/employees/id/1234/?dateOfHire=20231225&location=Vallejo
If you prefer using a format string or your format is dynamically generated, then use AddFormattedLink
:
var format = "{0}/{1}/{2}/?{3}={4}&{5}={6}";
var relativeUrl = "employees";
var route1 = "id";
var routeValue1 = "1234";
var param2 = "dateOfHire";
var paramValue2 = "20231225";
var param3 = "location";
var paramValue3 = "Vallejo";
HttpContext.AddFormattedLink(format, relativeUrl, route1, routeValue1, param2, paramValue2, param3, paramValue3);
// https://foo.bar/employees/id/1234/?dateOfHire=20231225&location=Vallejo
Glossary
- REST: REpresentational State Transfer
- SPA: A Single Page Application is a SaaS application where the application goes back to the server for only data from an API. THe webpages are created by the code in the application typically by manipulation of the HTML DOM.
References
- HATEOAS: HATEOAS Driven REST APIs
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.1 is compatible. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.Azure.Functions.Worker (>= 1.10.0)
- Newtonsoft.Json (>= 13.0.2)
- System.ComponentModel.Annotations (>= 5.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
1.8.1 | 360 | 1/8/2023 |
1.8.0 | 302 | 1/7/2023 |
1.7.0-preview-9 | 148 | 12/30/2022 |
1.6.0-preview-8 | 165 | 12/29/2022 |
1.5.0-preview-7 | 145 | 12/28/2022 |
1.4.0-preview-6 | 145 | 12/27/2022 |
1.3.0-preview-5 | 151 | 12/27/2022 |
1.1.0-preview-3 | 149 | 12/23/2022 |
1.1.0-preview-1 | 131 | 12/22/2022 |
1.0.0-preview-2 | 135 | 12/22/2022 |
1.0.0-preview-1 | 142 | 12/22/2022 |
Fix bug:
<a href="https://github.com/MeyerCorporation/HateoasBuilder/issues/16">#16</a>