JsonApiFramework is a fast, extensible, and portable .NET framework for the reading and writing of client-side and server-side JSON API documents based on the domain model of the hypermedia API resources.
Note Version 2.0 Breaking Change
Version 2.0 is a breaking change from version 1.X by deprecating the use of
IResource
. To fix remove all usages of theIResource
interface when defining your service model.
JSON API is an excellent specification for building hypermedia (Level 3 REST API) APIs in JSON. Using this standard solves the problem of how to format your hypermedia API responses (server) as well as requests for creating, updating, and deleting resources (client) with JSON. Adopting this standard promotes standardized communication protocols between client applications and hypermedia API servers making development and consumption of the hypermedia API effortless.
JsonApiFramework implements the JSON API 1.0 version of the specification that enables .NET developers to work with JSON API documents at a high level using .NET objects. Therefore JsonApiFramework helps .NET developers focus on core application functionality rather than on protocol implementation.
JsonApiFramework is a framework where developers define a service model that represents the domain model of the resources produced and consumed by a hypermedia API server or client application either through explicit configuration and/or implicit conventions. With a service model developers can use a document context that represents a session with a JSON API document for reading and/or writing of various JSON API concepts such as resources, relationships, links, meta information, error objects, and JSON API version information encapsulated as high level CLR objects.
Extreme high code quality with 2000+ unit tests. Production ready.
For further details, please check out the Wiki and .NET Core Samples
The following are some brief but concise usage examples to get an overall feel for how JsonApiFramework works both client-side and server-side of things. More usage examples will be found in the wiki and samples.
Assume the following for the usage examples:
// JsonApiFramework CLR Types
// --------------------------
// Document Represents a JSON API document.
// DocumentContext Represents a session with a JSON API document.
//
// Blogging CLR Types
// ------------------
// BloggingDocumentContext Specialization of DocumentContext for blogging resource types.
//
// Internally contains the service model (think metadata) of the
// blogging resource types with optional naming conventions to apply
// when converting between JSON API and .NET CLR resources.
//
// Blogging Relationships
// ----------------------
// Blog has "to-many" relationship to Article named "articles"
//
// Article has "to-one" relationship to Person named "author"
// Article has "to-many" relationship to Comment named "comments"
//
// Comment has "to-one" relationship to Person named "author"
public class Blog
{
public long BlogId { get; set; }
public string Name { get; set; }
}
public class Article
{
public long ArticleId { get; set; }
public string Title { get; set; }
public string Text { get; set; }
}
public class Comment
{
public long CommentId { get; set; }
public string Body { get; set; }
}
public class Person
{
public long PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Twitter { get; set; }
}
This example shows how a server-side Web API Controller could construct and return a JSON API document for the following GET
request for an individual article:
GET http://example.com/articles/1
public class ArticlesController : ApiController
{
[Route("articles/{id}")]
public async Task<IHttpActionResult> GetAsync(string id)
{
Contract.Requires(String.IsNullOrWhitespace(id) == false);
// Get article /////////////////////////////////////////////////////
var article = await GetArticle(id);
// Build and return JSON API document //////////////////////////////
var currentRequestUrl = HttpContext.Current.Request.Url;
using (var documentContext = new BloggingDocumentContext())
{
// Build new document.
var document = documentContext
.NewDocument(currentRequestUrl)
// Document links
.Links()
.AddUpLink()
.AddSelfLink()
.LinksEnd()
// Resource document (convert CLR Article resource to JSON API resource)
.Resource(article)
// Article relationships
.Relationships()
// article -> author
.Relationship("author")
.Links()
.AddSelfLink()
.AddRelatedLink()
.LinksEnd()
.RelationshipEnd()
// article -> comments
.Relationship("comments")
.Links()
.AddSelfLink()
.AddRelatedLink()
.LinksEnd()
.RelationshipEnd()
.RelationshipsEnd()
// Article links
.Links()
.AddSelfLink()
.LinksEnd()
.ResourceEnd()
.WriteDocument();
// Return 200 OK
// Note: WebApi JsonMediaTypeFormatter serializes the JSON API document into JSON.
return this.Ok(document);
}
}
}
will create the following example JSON
{
"links": {
"up": "http://example.com/articles",
"self": "http://example.com/articles/1"
},
"data": {
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON API paints my bikeshed!",
"text": "If you’ve ever argued with your team about the way your JSON responses should be
formatted, JSON API can be your anti-bikeshedding tool."
},
"relationships": {
"author": {
"links": {
"self": "http://example.com/articles/1/relationships/author",
"related": "http://example.com/articles/1/author"
}
},
"comments": {
"links": {
"self": "http://example.com/articles/1/relationships/comments",
"related": "http://example.com/articles/1/comments"
}
}
},
"links": {
"self": "http://example.com/articles/1"
}
}
}
This example shows how a server-side Web API Controller could construct and return a JSON API document for the following GET
request for an individual article and server-side include of the article's related author and comments resources:
GET http://example.com/articles/1
public class ArticlesController : ApiController
{
[Route("articles/{id}")]
public async Task<IHttpActionResult> GetAsync(string id)
{
Contract.Requires(String.IsNullOrWhitespace(id) == false);
// Get article and related author and comments /////////////////////
var article = await GetArticle();
var author = await GetArticleAuthor(article);
var comments = await GetArticleComments(article);
// Build and return JSON API document //////////////////////////////
var currentRequestUrl = HttpContext.Current.Request.Url;
using (var documentContext = new BloggingDocumentContext())
{
// Build new document.
var document = documentContext
.NewDocument(currentRequestUrl)
// Document links
.Links()
.AddLink("up")
.AddLink("self")
.LinksEnd()
// Resource document (convert CLR Article resource to JSON API resource)
.Resource(article)
// Article relationships
.Relationships()
// article -> author
.AddRelationship("author", new [] { "self", "related" })
// article -> comments
.AddRelationship("comments", new [] { "self", "related" })
.RelationshipsEnd()
// Article links
.Links()
.AddLink("self")
.LinksEnd()
.ResourceEnd()
// With included resources
.Included()
// Convert related "to-one" CLR Person resource to JSON API resource
// Automatically generate "to-one" resource linkage in article to related author
.Include(ToOneIncludedResource.Create(article, "author", author))
// Author(Person) relationships
.Relationships()
// author -> comments
.AddRelationship("comments", new [] { "self", "related" })
.RelationshipsEnd()
// Author(Person) links
.Links()
.AddLink("self")
.LinksEnd()
.IncludeEnd()
// Convert related "to-many" CLR Comment resources to JSON API resources
// Automatically generate "to-many" resource linkage in article to related comments
.Include(ToManyIncludedResources.Create(article, "comments", comments))
// Comments relationships
.Relationships()
// comments -> author
.AddRelationship("author", new [] { "self", "related" })
.RelationshipsEnd()
// Comments links
.Links()
.AddLink("self")
.LinksEnd()
.IncludeEnd()
.IncludedEnd()
.WriteDocument();
// Return 200 OK
// Note: WebApi JsonMediaTypeFormatter serializes the JSON API document into JSON.
return this.Ok(document);
}
}
}
will create the following example JSON
{
"links": {
"up": "http://example.com/articles",
"self": "http://example.com/articles/1"
},
"data": {
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON API paints my bikeshed!",
"text": "If you’ve ever argued with your team about the way your JSON responses should be
formatted, JSON API can be your anti-bikeshedding tool."
},
"relationships": {
"author": {
"links": {
"self": "http://example.com/articles/1/relationships/author",
"related": "http://example.com/articles/1/author"
},
"data": { "type": "people", "id": "9" }
},
"comments": {
"links": {
"self": "http://example.com/articles/1/relationships/comments",
"related": "http://example.com/articles/1/comments"
},
"data": [
{ "type": "comments", "id": "5" },
{ "type": "comments", "id": "12" }
]
}
},
"links": {
"self": "http://example.com/articles/1"
}
},
"included": [
{
"type": "people",
"id": "9",
"attributes": {
"first-name": "Dan",
"last-name": "Gebhardt",
"twitter": "dgeb"
},
"relationships": {
"comments": {
"links": {
"self": "http://example.com/people/9/relationships/comments",
"related": "http://example.com/people/9/comments"
}
}
},
"links": {
"self": "http://example.com/people/9"
}
},
{
"type": "comments",
"id": "5",
"attributes": {
"body": "First!"
},
"relationships": {
"author": {
"links": {
"self": "http://example.com/comments/5/relationships/author",
"related": "http://example.com/comments/5/author"
}
}
},
"links": {
"self": "http://example.com/comments/5"
}
},
{
"type": "comments",
"id": "12",
"attributes": {
"body": "I like XML better"
},
"relationships": {
"author": {
"links": {
"self": "http://example.com/comments/12/relationships/author",
"related": "http://example.com/comments/12/author"
}
}
},
"links": {
"self": "http://example.com/comments/12"
}
}
]
}
This example shows how a server-side Web API Controller could construct and return a JSON API document for the following GET
request for an individual article and server-side include of the article's resource linkage to related author and comments resources without including the related author and comments resources in the included section:
GET http://example.com/articles/1
public class ArticlesController : ApiController
{
[Route("articles/{id}")]
public async Task<IHttpActionResult> GetAsync(string id)
{
Contract.Requires(String.IsNullOrWhitespace(id) == false);
// Get article and related author and comments /////////////////////
var article = await GetArticle();
var author = await GetArticleAuthor(article);
var comments = await GetArticleComments(article);
// Build and return JSON API document //////////////////////////////
var currentRequestUrl = HttpContext.Current.Request.Url;
using (var documentContext = new BloggingDocumentContext())
{
// Build new document.
var document = documentContext
.NewDocument(currentRequestUrl)
// Document links
.Links()
.AddLink("up")
.AddLink("self")
.LinksEnd()
// Resource document (convert CLR Article resource to JSON API resource)
.Resource(article)
// Article relationships
.Relationships()
// article -> author
.Relationship("author")
.Links()
.AddSelfLink()
.AddRelatedLink()
.LinksEnd()
.SetData(ToOneResourceLinkage.Create(9))
.RelationshipEnd()
// article -> comments
.Relationship("comments")
.Links()
.AddSelfLink()
.AddRelatedLink()
.LinksEnd()
.SetData(ToManyResourceLinkage.Create(new [] {5, 12}))
.RelationshipEnd()
.RelationshipsEnd()
// Article links
.Links()
.AddLink("self")
.LinksEnd()
.ResourceEnd()
.WriteDocument();
// Return 200 OK
// Note: WebApi JsonMediaTypeFormatter serializes the JSON API document into JSON.
return this.Ok(document);
}
}
}
will create the following example JSON
{
"links": {
"up": "http://example.com/articles",
"self": "http://example.com/articles/1"
},
"data": {
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON API paints my bikeshed!",
"text": "If you’ve ever argued with your team about the way your JSON responses should be
formatted, JSON API can be your anti-bikeshedding tool."
},
"relationships": {
"author": {
"links": {
"self": "http://example.com/articles/1/relationships/author",
"related": "http://example.com/articles/1/author"
},
"data": { "type": "people", "id": "9" }
},
"comments": {
"links": {
"self": "http://example.com/articles/1/relationships/comments",
"related": "http://example.com/articles/1/comments"
},
"data": [
{ "type": "comments", "id": "5" },
{ "type": "comments", "id": "12" }
]
}
},
"links": {
"self": "http://example.com/articles/1"
}
}
}
This example shows how a server-side Web API Controller could construct and return a JSON API document for the following GET
request for an individual article with client requested sparse fieldsets.
GET http://example.com/articles/1?fields[articles]=title,author
public class ArticlesController : ApiController
{
[Route("articles/{id}")]
public async Task<IHttpActionResult> GetAsync(string id)
{
Contract.Requires(String.IsNullOrWhitespace(id) == false);
// Get article /////////////////////////////////////////////////////
var article = await GetArticle(id);
// Build and return JSON API document //////////////////////////////
var currentRequestUrl = HttpContext.Current.Request.Url;
using (var documentContext = new BloggingDocumentContext())
{
// Build new document.
var document = documentContext
.NewDocument(currentRequestUrl)
// Document links
.Links()
.AddUpLink()
.AddSelfLink()
.LinksEnd()
// Resource document (convert CLR Article resource to JSON API resource)
.Resource(article)
// Article relationships
.Relationships()
// article -> author
.Relationship("author")
.Links()
.AddRelatedLink()
.LinksEnd()
.RelationshipEnd()
// article -> comments
.Relationship("comments")
.Links()
.AddRelatedLink()
.LinksEnd()
.RelationshipEnd()
.RelationshipsEnd()
// Article links
.Links()
.AddSelfLink()
.LinksEnd()
.ResourceEnd()
.WriteDocument();
// Return 200 OK
// Note: WebApi JsonMediaTypeFormatter serializes the JSON API document into JSON.
return this.Ok(document);
}
}
}
will create the following example JSON
{
"links": {
"up": "http://example.com/articles",
"self": "http://example.com/articles/1?fields[articles]=title,author"
},
"data": {
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON API paints my bikeshed!"
},
"relationships": {
"author": {
"links": {
"related": "http://example.com/articles/1/author"
}
}
},
"links": {
"self": "http://example.com/articles/1"
}
}
}
This example shows how a client-side ViewModel could construct and send a POST
request with a JSON API document for creating resource purposes:
POST http://example.com/articles
public class ArticlesViewModel : ViewModel
{
public async Task<bool> CreateNewArticleAsync(string title, string text, long authorId)
{
// Create new article for respective author.
var article = new Article { Title = title, Text = text };
// Build and POST JSON API document to create new article.
using (var documentContext = new BloggingDocumentContext())
{
// Build document
var document = documentContext
.NewDocument()
// Resource document (convert CLR Article resource to JSON API resource)
.Resource(article)
// Link new article to an existing author.
.Relationships()
.AddRelationship("author", ToOneResourceLinkage.Create(authorId))
.RelationshipsEnd()
.ResourceEnd()
.WriteDocument();
// POST document
var documentJson = document.ToJson();
var content = new StringContent(documentJson, Encoding.UTF8, "application/json")
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await httpClient.PostAsync("http://example.com/articles", content);
response.EnsureSuccessStatusCode();
return true;
}
}
}
will create the following example JSON
{
"data": {
"type": "articles",
"attributes": {
"title": "JSON API paints my house!",
"text": "If you’ve ever argued with your team about the way your JSON responses should be
formatted, JSON API can be your anti-bikeshedding tool."
},
"relationships": {
"author": {
"data": { "type": "people", "id": "9" }
}
}
}
}
This example shows how a client-side ViewModel could construct and send a PATCH
request with a JSON API document for updating resource purposes:
PATCH http://example.com/articles/2
public class ArticlesViewModel : ViewModel
{
public async Task<bool> UpdateExistingArticleTitleAsync(long articleId, string newTitle)
{
// Build and PATCH JSON API document to update an existing article's title.
using (var documentContext = new BloggingDocumentContext())
{
// Build document.
var document = documentContext
.NewDocument()
// Resource document (manually build an Article JSON API resource)
.Resource<Article>()
// Set primary key
.SetId(Id.Create(articleId))
// Update title attribute
.Attributes()
.AddAttribute(article => article.Title, newTitle)
.AttributesEnd()
.ResourceEnd()
.WriteDocument();
var documentJson = document.ToJson();
// PATCH document.
var content = new StringContent(documentJson, Encoding.UTF8, "application/json")
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await httpClient.PatchAsync("http://example.com/articles/2", content);
response.EnsureSuccessStatusCode();
return true;
}
}
}
will create the following example JSON
{
"data": {
"type": "articles",
"id": "2",
"attributes": {
"title": "To TDD or Not"
}
}
}
Document reading is the same for either client-side or server-side.
This example shows how a client application or server hypermedia API server could receive and read a JSON API resource document for an individual article with included author and comments:
public class ArticleReader
{
public void ReadJsonApiDocument(string json)
{
// Parse and read JSON API document.
var document = JsonObject.Parse<Document>(json);
using (var documentContext = new BloggingDocumentContext(document))
{
// Read JSON API protocol version //////////////////////////////////
var jsonApiVersion = documentContext.GetJsonApiVersion();
// Read Document-Level things //////////////////////////////////////
var documentType = documentContext.GetDocumentType();
Assume(documenType == DocumentType.ResourceDocument);
var documentMeta = documentContext.GetDocumentMeta();
var documentLinks = documentContext.GetDocumentLinks();
Assume(documentLinks.ContainsLink("self") == true);
var documentSelfLink = documentLinks ["self"];
// Read Resource-Level things //////////////////////////////////////
// Articles
var article = documentContext.GetResource<Article>();
var articleLinks = documentContext.GetResourceLinks(article);
var articleSelfLink = articleLinks ["self"];
var articleRelationships = documentContext.GetResourceRelationships(article);
Assume(articleRelationships.ContainsRelationship("author") == true);
Assume(articleRelationships.ContainsRelationship("comments") == true);
// Related Author
var authorRelationship = articleRelationships ["author"];
var authorRelationshipLinks = authorRelationship.Links;
var authorRelationshipSelfLink = authorRelationshipLinks ["self"];
var authorRelationshipRelatedLink = authorRelationshipLinks ["related"];
var authorRelationshipMeta = authorRelationship.Meta
Assume(authorRelationship.IsResourceLinkageNullOrEmpty() == false);
var author = documentContext.GetRelatedResource<Person>(authorRelationship);
// Related Comments
var commentsRelationship = articleRelationships ["comments"];
var commentsRelationshipLinks = commentsRelationship.Links;
var commentsRelationshipSelfLink = commentsRelationshipLinks ["self"];
var commentsRelationshipRelatedLink = commentsRelationshipLinks ["related"];
var commentsRelationshipMeta = commentsRelationship.Meta
Assume(commentsRelationship.IsResourceLinkageNullOrEmpty() == false);
var comments = documentContext
.GetRelatedResourceCollection<Comment>(commentsRelationship)
.ToList();
}
}
}
The JSON API specification fundamentally standardizes the HTTP communication protocol for CRUD (Create Retrieve Update Delete) of resources between client and server. This is in the context of hypermedia API JSON-based HTTP requests and responses containing JSON API documents which in turn are composed of resources.
From a JSON API document reading and writing standpoint, the reading of JSON API documents is the same for both client and server but the writing of JSON API documents is different. For the server, the writing or generation of a JSON API documents will be based on a client request and contain hypermedia throughout the document. For the client, the writing or generation of a JSON API document will be essentially for the creating or updating of resources on the server and contain no hypermedia at all. With this understanding, the JsonApiFramework solution is composed of the following
Project | Assembly * | Summary |
---|---|---|
JsonApiFramework.Core | JsonApiFramework.Core.dll | Portable core-level .NET class library for serializing and deserializing between raw JSON API documents and CLR resources. Portable core-level .NET framework for ServiceModel and Conventions. |
JsonApiFramework.Infrastructure | JsonApiFramework.Infrastructure.dll | Portable client-side and server-side .NET framework for JSON API document reading and writing. Depends on the JsonApiFramework.Core project. |
JsonApiFramework.Client | JsonApiFramework.Client.dll | Portable client-side .NET framework for JSON API document building. Depends on the JsonApiFramework.Core and JsonApiFramework.Infrastructure projects. |
JsonApiFramework.Server | JsonApiFramework.Server.dll | Portable server-side .NET framework for JSON API document building. Depends on the JsonApiFramework.Core and JsonApiFramework.Infrastructure projects. |
* All assemblies are .NET Standard class libraries to support cross-platform development.
There are 2 options for installation of JsonApiFramework depending on the goal of the project:
Id | Name | Latest Version |
---|---|---|
JsonApiFramework.Client | JsonApiFramework [Client] | 2.12.0 |
To install the JsonApiFramework [Client] NuGet package, run the following command in the Package Manager Console
PM> Install-Package JsonApiFramework.Client
Id | Name | Latest Version |
---|---|---|
JsonApiFramework.Server | JsonApiFramework [Server] | 2.12.0 |
To install the JsonApiFramework [Server] NuGet package, run the following command in the Package Manager Console
PM> Install-Package JsonApiFramework.Server
Special case of creating an assembly containing just the service model where the assembly is intended to be shared by both client-side and server-side projects.
Id | Name | Latest Version |
---|---|---|
JsonApiFramework.Core | JsonApiFramework [Core] | 2.12.0 |
To install the JsonApiFramework [Core] NuGet package, run the following command in the Package Manager Console
PM> Install-Package JsonApiFramework.Core
JsonApiFramework is a C#/.NET standard library developed and built with Visual Studio 2019.
The only thing needed is Visual Studio 2019 or higher installed on your development machine. JsonApiFramework has dependencies on nuget packages, more specifically:
JsonApiFramework has over 2000+ unit tests and growing. This ensures extreme high code quality and allows for new development with a safety net that any new development has not broken any of the existing code base.
JsonApiFramework unit tests were developed with the excellent xUnit 2.0 unit testing framework. In order to run the unit tests, you will need a xUnit test runner so please see the xUnit Documentation page to setup an appropriate test runner for your development machine.
The JsonApiFramework unit tests rely heavily on the following xUnit features:
[Theory]
data driven unit tests, and theITestOutputHelper
interface used to capture useful unit test output for diagnostic and visualization purposes. For example, the JSON being read or written for a particular unit test. Another example, the internal DOM (Document Object Model) tree representing the JSON API document in memory.Recommendation: If you have Resharper, we highly recommend using Resharper with the xUnit.net Test Support extension as your test runner. This allows the running or debugging of unit tests in the Resharper unit tests window which also captures all the output from the
ITestOutputHelper
interface described above.
git checkout master
git checkout -b my-new-feature
git add --all
git commit -sm 'commit message'
git push origin my-new-feature
Please use the following support channels:
scott@quantumsoft.com
.Scott McDonald (scott@quantumsoft.com
) created JsonApiFramework and these fine people have contributed.
Licensed under the Apache License, Version 2.0. See LICENSE.md
in the project root for license information.