scott-mcdonald / JsonApiFramework

JsonApiFramework is a fast, extensible, and portable .NET framework for the reading and writing of JSON API documents. Currently working on ApiFramework 1.0 which is a new framework that supports the many enhancements documented in the 2.0 milestone of this project while being media type agnostic but will support media types like {json:api} and GraphQL for serialization/deserialization purposes.
Other
96 stars 11 forks source link
dotnet-core dotnet-standard framework json-api portable rest

JsonApiFramework

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 the IResource interface when defining your service model.

Overview

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.

Benefits and Features

Extreme high code quality with 2000+ unit tests. Production ready.

For further details, please check out the Wiki and .NET Core Samples

Usage examples

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; }
}

Server-side document building example #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:

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"
    }
  }
}

Server-side document building example #2:

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"
      }
    }
  ]
}

Server-side document building example #3:

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"
      }
    }
}

Server-side document building example #4 - Sparse Fieldsets:

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"
    }
  }
}

Client-side document building for POST example:

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" }
      }
    }
  }
}

Client-side document building for PATCH example:

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 example:

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();
        }
    }
}

Solution description

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

Projects

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.

Installation

There are 2 options for installation of JsonApiFramework depending on the goal of the project:

Option 1: From NuGet (easy peasy)

Client-Side Document Reading/Building/Writing

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

Server-Side Document Reading/Building/Writing

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

Shared Service Model Only

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

Option 2: From source

Development setup

JsonApiFramework is a C#/.NET standard library developed and built with Visual Studio 2019.

Prerequisites

The only thing needed is Visual Studio 2019 or higher installed on your development machine. JsonApiFramework has dependencies on nuget packages, more specifically:

Running the tests

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 the
  • ITestOutputHelper 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.

Contributing

  1. Fork it!
  2. Checkout master branch: git checkout master
  3. Create your feature branch from master branch: git checkout -b my-new-feature
  4. Add your changes: git add --all
  5. Sign-off and commit your changes with a message: git commit -sm 'commit message'
  6. Push to the branch: git push origin my-new-feature
  7. Submit a pull request :D

Release history

Support

Please use the following support channels:

Authors

Scott McDonald (scott@quantumsoft.com) created JsonApiFramework and these fine people have contributed.

License

Licensed under the Apache License, Version 2.0. See LICENSE.md in the project root for license information.