SlideShare a Scribd company logo
BEAUTIFUL REST APIs
in ASP.NET Core
Nate Barbettini
@nbarbettini
recaffeinate.co
.ws
Overview
● Why is API design important?
● HATEOAS (Hypertext As The Engine Of Application State)
● REST APIs in ASP.NET Core
/getAccount?id=17
Bad REST API design
/getAccount?all=1&includePosts=1
/getAllAccounts
/updateAccount?id=17
/createAccount
/findPostsByAccountId?account=17
/accountSearch?lname=Skywalker
/getAccount?id=17&includePosts=1
/getAccount?id=17&format=json
/getAllAccountsJson
/updateAccount?id=17&return=json
/createAccountJson
/accountSearch?lname=Skywalker&xml=1
/findPostsByAccountIdJSON?account=17
/getAllPosts?filter=account&id=17
/countAccounts
/partialUpdateAccount?id=17
/getPostCount?id=17
/deleteUser
HATEOAS, yo!
"A REST API should be entered with no prior knowledge beyond the initial URI (bookmark)
and set of standardized media types that are appropriate for the intended audience (i.e.,
expected to be understood by any client that might use the API). From that point on, all
application state transitions must be driven by client selection of server-provided choices
that are present in the received representations or implied by the user’s manipulation of
those representations." ~ Dr. Fielding
Tl;dr The API responses themselves
should document what you are allowed to
do and where you can go.
If you can get to the root (/), you should be
able to “travel” anywhere else in the API.
Good REST API design should...
● Be discoverable and self-documenting
● Represent resources and collections
● Represent actions using HTTP verbs
● KISS!
Revisiting the API example
/users GET: List all users
POST or PUT: Create a user
/users/17 GET: Retrieve a single user
POST or PUT: Update user details
DELETE: Delete this user
/users/17/posts GET: Get the user’s posts
POST: Create a post
/users?lname=Skywalker
Search
/users/17?include=posts
Include linked data
A specification for REST+JSON APIs
The ION spec: https://github.com/ionwg/ion-doc
Getting a single user
GET /users/17
{
"meta": { "href": "https://example.io/users/17" },
"firstName": "Luke",
"lastName": "Skywalker"
}
Getting a list of users
GET /users
{
"meta": { "href": "https://example.io/users", "rel": ["collection"] },
"items": [{
"meta": { "href": "https://example.io/users/17" },
"firstName": "Luke",
"lastName": "Skywalker"
}, {
"meta": { "href": "https://example.io/users/18" },
"firstName": "Han",
"lastName": "Solo"
}]
}
Discoverable forms
GET /users
{
...
"create": {
"meta": {
"href": "https://example.io/users",
"rel": ["create-form"],
"method": "post"
},
"items": [
{ "name": "firstName" },
{ "name": "lastName" }
]
}
}
Discoverable search
GET /users
{
...
"search": {
"meta": {
"href": "https://example.io/users",
"rel": ["search-form"],
"method": "get"
},
"items": [
{ "name": "fname" },
{ "name": "lname" }
]
}
}
The starting point (API root)
GET /
{
"meta": { "href": "https://example.io/" },
"users": {
"meta": {
"href": "https://example.io/users",
"rel": ["collection"],
}
}
}
● Install the .NET Core SDK - http://dot.net/core
● If you’re using Visual Studio:
○ Install the latest updates (Update 3)
○ Install the .NET Core tooling - https://go.microsoft.com/fwlink/?LinkID=824849
● Or, install Visual Studio Code
● Create a new project from the ASP.NET Core (.NET Core) template
● Pick the API subtemplate
● Ready to run!
Getting started with ASP.NET Core
Getting a single user
GET /users/17
{
"meta": { "href": "https://example.io/users/17" },
"firstName": "Luke",
"lastName": "Skywalker"
}
public class Link
{
public string Href { get; set; }
}
public abstract class Resource
{
[JsonProperty(Order = -2)]
public Link Meta { get; set; }
}
Getting a single user
public class User : Resource
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
Getting a single user
[Route("/users")]
public class UsersController : Controller
{
private readonly BulletinBoardDbContext _context;
private readonly IUrlHelperFactory _urlHelperFactory;
public UsersController(
BulletinBoardDbContext context,
IUrlHelperFactory urlHelperFactory)
{
_context = context;
_urlHelperFactory = urlHelperFactory;
}
Getting a single user
[Route("{id}")]
public async Task<IActionResult> GetUser(string id)
{
var user = await _context.Users.SingleOrDefaultAsync(x => x.Id == id);
if (user == null) return NotFound();
var urlHelper = _urlHelperFactory.GetUrlHelper(ControllerContext);
var url = urlHelper.Link("default", new
{
controller = "users",
id = user.Id
});
var response = new User()
{
Meta = new Link() { Href = url },
FirstName = user.FirstName,
LastName = user.LastName
};
return Ok(response);
}
Getting a single user
Getting a list of users
GET /users
{
"meta": { "href": "https://example.io/users", "rel": ["collection"] },
"items": [{
"meta": { "href": "https://example.io/users/17" },
"firstName": "Luke",
"lastName": "Skywalker"
}, {
"meta": { "href": "https://example.io/users/18" },
"firstName": "Han",
"lastName": "Solo"
}]
}
Getting a list of users
public class Link
{
public string Href { get; set; }
[JsonProperty(PropertyName = "rel", NullValueHandling = NullValueHandling.Ignore)]
public string[] Relations { get; set; }
}
Getting a list of users
public class Collection<T> : Resource
{
public T[] Items { get; set; }
}
Getting a list of users
public async Task<IActionResult> GetAll()
{
var urlHelper = _urlHelperFactory.GetUrlHelper(ControllerContext);
var allUsers = await _context.Users.ToArrayAsync();
var projected = allUsers.Select(x => new User() {
Meta = new Link() {
Href = urlHelper.Link("default", new { controller = "users", id = x.Id })
},
FirstName = x.FirstName,
LastName = x.LastName
});
var response = new Collection<User>()
{
Meta = new Link() {
Href = urlHelper.Link("default", new { controller = "users" }),
Relations = new string[] {"collection"},
},
Items = projected.ToArray()
};
return Ok(response);
}
The starting point (API root)
GET /
{
"meta": { "href": "https://example.io/" },
"users": {
"meta": {
"href": "https://example.io/users",
"rel": ["collection"],
}
}
}
Adding a root route
[Route("/")]
public class RootController : Controller
{
private readonly IUrlHelperFactory _urlHelperFactory;
public RootController(IUrlHelperFactory urlHelperFactory)
{
_urlHelperFactory = urlHelperFactory;
}
public IActionResult Get()
{
var urlHelper = _urlHelperFactory.GetUrlHelper(ControllerContext);
var response = new {
meta = new Link() {
Href = urlHelper.Link("default", new { controller = "root" })
},
users = new Link() {
Href = urlHelper.Link("default", new { controller = "users" }),
Relations = new string[] {"collection"}
}
};
return Ok(response);
}
}
Building and running (anywhere!)
> dotnet build
(...)
Done.
> dotnet run
(...)
Listening on https://localhost:5000
Next Steps
● Full example
https://github.com/nbarbettini/beautiful-rest-api-aspnetcore
● ION draft spec
https://github.com/ionwg/ion-doc
Thank you!
Nate Barbettini
@nbarbettini
recaffeinate.co
.ws

More Related Content

PPTX
Building Beautiful REST APIs in ASP.NET Core
PPTX
Beautiful REST+JSON APIs with Ion
PPTX
Pragmatic REST APIs
PPTX
Creating Truly RESTful APIs
PDF
Overview of GraphQL & Clients
PDF
The never-ending REST API design debate
PDF
Building Beautiful REST APIs with ASP.NET Core
PPTX
Design Beautiful REST + JSON APIs
Building Beautiful REST APIs in ASP.NET Core
Beautiful REST+JSON APIs with Ion
Pragmatic REST APIs
Creating Truly RESTful APIs
Overview of GraphQL & Clients
The never-ending REST API design debate
Building Beautiful REST APIs with ASP.NET Core
Design Beautiful REST + JSON APIs

What's hot (19)

PPTX
Best Practices for Architecting a Pragmatic Web API.
PDF
Designing a beautiful REST json api
PPT
External Data Access with jQuery
PPT
Understanding REST
PPTX
Best practices for RESTful web service design
PPTX
RESTful API Design Best Practices Using ASP.NET Web API
PDF
The never-ending REST API design debate -- Devoxx France 2016
PDF
GraphQL & Relay - 串起前後端世界的橋樑
PDF
RESTful Web Services
PPTX
PPTX
Austin Day of Rest - Introduction
PDF
RESTful Web API and MongoDB go for a pic nic
PDF
Log File Analysis: The most powerful tool in your SEO toolkit
PPTX
Rest presentation
PDF
Representational State Transfer (REST) and HATEOAS
PPTX
Creating 3rd Generation Web APIs with Hydra
PPTX
Building Next-Generation Web APIs with JSON-LD and Hydra
PPTX
Understanding REST APIs in 5 Simple Steps
PDF
Data Exploration with Elasticsearch
Best Practices for Architecting a Pragmatic Web API.
Designing a beautiful REST json api
External Data Access with jQuery
Understanding REST
Best practices for RESTful web service design
RESTful API Design Best Practices Using ASP.NET Web API
The never-ending REST API design debate -- Devoxx France 2016
GraphQL & Relay - 串起前後端世界的橋樑
RESTful Web Services
Austin Day of Rest - Introduction
RESTful Web API and MongoDB go for a pic nic
Log File Analysis: The most powerful tool in your SEO toolkit
Rest presentation
Representational State Transfer (REST) and HATEOAS
Creating 3rd Generation Web APIs with Hydra
Building Next-Generation Web APIs with JSON-LD and Hydra
Understanding REST APIs in 5 Simple Steps
Data Exploration with Elasticsearch
Ad

Viewers also liked (20)

PPTX
Stormpath 101: Spring Boot + Spring Security
PPTX
Secure API Services in Node with Basic Auth and OAuth2
PPTX
Storing User Files with Express, Stormpath, and Amazon S3
PPTX
JWTs for CSRF and Microservices
PDF
Mobile Authentication for iOS Applications - Stormpath 101
PPTX
Token Authentication in ASP.NET Core
PPTX
Custom Data Search with Stormpath
PPTX
Spring Boot Authentication...and More!
PDF
JWTs in Java for CSRF and Microservices
PPTX
Instant Security & Scalable User Management with Spring Boot
PPTX
Multi-Tenancy with Spring Boot
PDF
The Ultimate Guide to Mobile API Security
PPTX
Browser Security 101
PPTX
REST API Security: OAuth 2.0, JWTs, and More!
PPTX
Building Secure User Interfaces With JWTs (JSON Web Tokens)
PDF
Getting Started With Angular
PDF
Securing Web Applications with Token Authentication
PDF
Build a REST API for your Mobile Apps using Node.js
PPTX
Token Authentication for Java Applications
PPTX
So long scrum, hello kanban
Stormpath 101: Spring Boot + Spring Security
Secure API Services in Node with Basic Auth and OAuth2
Storing User Files with Express, Stormpath, and Amazon S3
JWTs for CSRF and Microservices
Mobile Authentication for iOS Applications - Stormpath 101
Token Authentication in ASP.NET Core
Custom Data Search with Stormpath
Spring Boot Authentication...and More!
JWTs in Java for CSRF and Microservices
Instant Security & Scalable User Management with Spring Boot
Multi-Tenancy with Spring Boot
The Ultimate Guide to Mobile API Security
Browser Security 101
REST API Security: OAuth 2.0, JWTs, and More!
Building Secure User Interfaces With JWTs (JSON Web Tokens)
Getting Started With Angular
Securing Web Applications with Token Authentication
Build a REST API for your Mobile Apps using Node.js
Token Authentication for Java Applications
So long scrum, hello kanban
Ad

Similar to Building Beautiful REST APIs in ASP.NET Core (20)

PPTX
The JSON REST API for WordPress
PDF
Making Java REST with JAX-RS 2.0
PDF
AMS, API, RAILS and a developer, a Love Story
PDF
Diseño y Desarrollo de APIs
PDF
GraphQL Los Angeles Meetup Slides
PPTX
Tk2323 lecture 9 api json
PPTX
RESTful API 제대로 만들기
PDF
jSession #4 - Maciej Puchalski - Zaawansowany retrofit
PPTX
Golang slidesaudrey
PDF
Semantic Web & TYPO3
PPTX
Android and REST
PDF
Java Libraries You Can’t Afford to Miss
PDF
David Gómez G. - Hypermedia APIs for headless platforms and Data Integration ...
PDF
Cdm mil-18 - hypermedia ap is for headless platforms and data integration
PPTX
Python Code Camp for Professionals 3/4
PDF
JSON and the APInauts
PDF
Hypermedia APIs and HATEOAS / Wix Engineering
PPT
RESTful JSON web databases
PDF
Specification-Driven Development of REST APIs by Alexander Zinchuk
PPTX
Automatic discovery of Web API Specifications: an example-driven approach
The JSON REST API for WordPress
Making Java REST with JAX-RS 2.0
AMS, API, RAILS and a developer, a Love Story
Diseño y Desarrollo de APIs
GraphQL Los Angeles Meetup Slides
Tk2323 lecture 9 api json
RESTful API 제대로 만들기
jSession #4 - Maciej Puchalski - Zaawansowany retrofit
Golang slidesaudrey
Semantic Web & TYPO3
Android and REST
Java Libraries You Can’t Afford to Miss
David Gómez G. - Hypermedia APIs for headless platforms and Data Integration ...
Cdm mil-18 - hypermedia ap is for headless platforms and data integration
Python Code Camp for Professionals 3/4
JSON and the APInauts
Hypermedia APIs and HATEOAS / Wix Engineering
RESTful JSON web databases
Specification-Driven Development of REST APIs by Alexander Zinchuk
Automatic discovery of Web API Specifications: an example-driven approach

More from Stormpath (7)

PPTX
How to Use Stormpath in angular js
PPTX
Rest API Security
PPTX
Elegant Rest Design Webinar
PPTX
Secure Your REST API (The Right Way)
PPTX
Build a Node.js Client for Your REST+JSON API
PPTX
Build A Killer Client For Your REST+JSON API
PPTX
REST API Design for JAX-RS And Jersey
How to Use Stormpath in angular js
Rest API Security
Elegant Rest Design Webinar
Secure Your REST API (The Right Way)
Build a Node.js Client for Your REST+JSON API
Build A Killer Client For Your REST+JSON API
REST API Design for JAX-RS And Jersey

Recently uploaded (20)

PDF
Encapsulation_ Review paper, used for researhc scholars
PDF
Approach and Philosophy of On baking technology
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
PDF
Machine learning based COVID-19 study performance prediction
PPTX
A Presentation on Artificial Intelligence
DOCX
The AUB Centre for AI in Media Proposal.docx
PPTX
Big Data Technologies - Introduction.pptx
PDF
Electronic commerce courselecture one. Pdf
PPT
“AI and Expert System Decision Support & Business Intelligence Systems”
PDF
Bridging biosciences and deep learning for revolutionary discoveries: a compr...
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PDF
CIFDAQ's Market Insight: SEC Turns Pro Crypto
PDF
Empathic Computing: Creating Shared Understanding
PDF
NewMind AI Monthly Chronicles - July 2025
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PDF
Spectral efficient network and resource selection model in 5G networks
PDF
Chapter 3 Spatial Domain Image Processing.pdf
PDF
cuic standard and advanced reporting.pdf
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
Encapsulation_ Review paper, used for researhc scholars
Approach and Philosophy of On baking technology
Agricultural_Statistics_at_a_Glance_2022_0.pdf
Machine learning based COVID-19 study performance prediction
A Presentation on Artificial Intelligence
The AUB Centre for AI in Media Proposal.docx
Big Data Technologies - Introduction.pptx
Electronic commerce courselecture one. Pdf
“AI and Expert System Decision Support & Business Intelligence Systems”
Bridging biosciences and deep learning for revolutionary discoveries: a compr...
The Rise and Fall of 3GPP – Time for a Sabbatical?
CIFDAQ's Market Insight: SEC Turns Pro Crypto
Empathic Computing: Creating Shared Understanding
NewMind AI Monthly Chronicles - July 2025
Diabetes mellitus diagnosis method based random forest with bat algorithm
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
Spectral efficient network and resource selection model in 5G networks
Chapter 3 Spatial Domain Image Processing.pdf
cuic standard and advanced reporting.pdf
Mobile App Security Testing_ A Comprehensive Guide.pdf

Building Beautiful REST APIs in ASP.NET Core

  • 1. BEAUTIFUL REST APIs in ASP.NET Core Nate Barbettini @nbarbettini recaffeinate.co .ws
  • 2. Overview ● Why is API design important? ● HATEOAS (Hypertext As The Engine Of Application State) ● REST APIs in ASP.NET Core
  • 3. /getAccount?id=17 Bad REST API design /getAccount?all=1&includePosts=1 /getAllAccounts /updateAccount?id=17 /createAccount /findPostsByAccountId?account=17 /accountSearch?lname=Skywalker /getAccount?id=17&includePosts=1 /getAccount?id=17&format=json /getAllAccountsJson /updateAccount?id=17&return=json /createAccountJson /accountSearch?lname=Skywalker&xml=1 /findPostsByAccountIdJSON?account=17 /getAllPosts?filter=account&id=17 /countAccounts /partialUpdateAccount?id=17 /getPostCount?id=17 /deleteUser
  • 4. HATEOAS, yo! "A REST API should be entered with no prior knowledge beyond the initial URI (bookmark) and set of standardized media types that are appropriate for the intended audience (i.e., expected to be understood by any client that might use the API). From that point on, all application state transitions must be driven by client selection of server-provided choices that are present in the received representations or implied by the user’s manipulation of those representations." ~ Dr. Fielding Tl;dr The API responses themselves should document what you are allowed to do and where you can go. If you can get to the root (/), you should be able to “travel” anywhere else in the API.
  • 5. Good REST API design should... ● Be discoverable and self-documenting ● Represent resources and collections ● Represent actions using HTTP verbs ● KISS!
  • 6. Revisiting the API example /users GET: List all users POST or PUT: Create a user /users/17 GET: Retrieve a single user POST or PUT: Update user details DELETE: Delete this user /users/17/posts GET: Get the user’s posts POST: Create a post /users?lname=Skywalker Search /users/17?include=posts Include linked data
  • 7. A specification for REST+JSON APIs The ION spec: https://github.com/ionwg/ion-doc
  • 8. Getting a single user GET /users/17 { "meta": { "href": "https://example.io/users/17" }, "firstName": "Luke", "lastName": "Skywalker" }
  • 9. Getting a list of users GET /users { "meta": { "href": "https://example.io/users", "rel": ["collection"] }, "items": [{ "meta": { "href": "https://example.io/users/17" }, "firstName": "Luke", "lastName": "Skywalker" }, { "meta": { "href": "https://example.io/users/18" }, "firstName": "Han", "lastName": "Solo" }] }
  • 10. Discoverable forms GET /users { ... "create": { "meta": { "href": "https://example.io/users", "rel": ["create-form"], "method": "post" }, "items": [ { "name": "firstName" }, { "name": "lastName" } ] } }
  • 11. Discoverable search GET /users { ... "search": { "meta": { "href": "https://example.io/users", "rel": ["search-form"], "method": "get" }, "items": [ { "name": "fname" }, { "name": "lname" } ] } }
  • 12. The starting point (API root) GET / { "meta": { "href": "https://example.io/" }, "users": { "meta": { "href": "https://example.io/users", "rel": ["collection"], } } }
  • 13. ● Install the .NET Core SDK - http://dot.net/core ● If you’re using Visual Studio: ○ Install the latest updates (Update 3) ○ Install the .NET Core tooling - https://go.microsoft.com/fwlink/?LinkID=824849 ● Or, install Visual Studio Code ● Create a new project from the ASP.NET Core (.NET Core) template ● Pick the API subtemplate ● Ready to run! Getting started with ASP.NET Core
  • 14. Getting a single user GET /users/17 { "meta": { "href": "https://example.io/users/17" }, "firstName": "Luke", "lastName": "Skywalker" }
  • 15. public class Link { public string Href { get; set; } } public abstract class Resource { [JsonProperty(Order = -2)] public Link Meta { get; set; } } Getting a single user
  • 16. public class User : Resource { public string FirstName { get; set; } public string LastName { get; set; } } Getting a single user
  • 17. [Route("/users")] public class UsersController : Controller { private readonly BulletinBoardDbContext _context; private readonly IUrlHelperFactory _urlHelperFactory; public UsersController( BulletinBoardDbContext context, IUrlHelperFactory urlHelperFactory) { _context = context; _urlHelperFactory = urlHelperFactory; } Getting a single user
  • 18. [Route("{id}")] public async Task<IActionResult> GetUser(string id) { var user = await _context.Users.SingleOrDefaultAsync(x => x.Id == id); if (user == null) return NotFound(); var urlHelper = _urlHelperFactory.GetUrlHelper(ControllerContext); var url = urlHelper.Link("default", new { controller = "users", id = user.Id }); var response = new User() { Meta = new Link() { Href = url }, FirstName = user.FirstName, LastName = user.LastName }; return Ok(response); } Getting a single user
  • 19. Getting a list of users GET /users { "meta": { "href": "https://example.io/users", "rel": ["collection"] }, "items": [{ "meta": { "href": "https://example.io/users/17" }, "firstName": "Luke", "lastName": "Skywalker" }, { "meta": { "href": "https://example.io/users/18" }, "firstName": "Han", "lastName": "Solo" }] }
  • 20. Getting a list of users public class Link { public string Href { get; set; } [JsonProperty(PropertyName = "rel", NullValueHandling = NullValueHandling.Ignore)] public string[] Relations { get; set; } }
  • 21. Getting a list of users public class Collection<T> : Resource { public T[] Items { get; set; } }
  • 22. Getting a list of users public async Task<IActionResult> GetAll() { var urlHelper = _urlHelperFactory.GetUrlHelper(ControllerContext); var allUsers = await _context.Users.ToArrayAsync(); var projected = allUsers.Select(x => new User() { Meta = new Link() { Href = urlHelper.Link("default", new { controller = "users", id = x.Id }) }, FirstName = x.FirstName, LastName = x.LastName }); var response = new Collection<User>() { Meta = new Link() { Href = urlHelper.Link("default", new { controller = "users" }), Relations = new string[] {"collection"}, }, Items = projected.ToArray() }; return Ok(response); }
  • 23. The starting point (API root) GET / { "meta": { "href": "https://example.io/" }, "users": { "meta": { "href": "https://example.io/users", "rel": ["collection"], } } }
  • 24. Adding a root route [Route("/")] public class RootController : Controller { private readonly IUrlHelperFactory _urlHelperFactory; public RootController(IUrlHelperFactory urlHelperFactory) { _urlHelperFactory = urlHelperFactory; } public IActionResult Get() { var urlHelper = _urlHelperFactory.GetUrlHelper(ControllerContext); var response = new { meta = new Link() { Href = urlHelper.Link("default", new { controller = "root" }) }, users = new Link() { Href = urlHelper.Link("default", new { controller = "users" }), Relations = new string[] {"collection"} } }; return Ok(response); } }
  • 25. Building and running (anywhere!) > dotnet build (...) Done. > dotnet run (...) Listening on https://localhost:5000
  • 26. Next Steps ● Full example https://github.com/nbarbettini/beautiful-rest-api-aspnetcore ● ION draft spec https://github.com/ionwg/ion-doc