In our previous post we setup a basic JSON:API compliant API with the 4.0 release of the JsonApiDotNetCore framework. You can find the code we wrote under the part-1 branch on Github.

In this post I want to introduce some of the more powerful capabilities of the framework and to do that we are going to introduce relationships between models.

Relationships

Let's use the canonical todo example and create the ability to assign a todo to a single person. We'll start by creating a new TodoItem.cs file and add the following to it:

using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;

namespace MyApi
{
    public class TodoItem : Identifiable
    {
        [Attr]
        public string Todo { get; set; }

				[Attr]
        public uint Priority { get; set; }
				
				[HasOne]
        public Person Owner { get; set; }
    }
}

In the code above, we state that a TodoItem has a single Person as its owner. Let's also add the reverse relationship, where we state that a Person can have many todo's. Open up your Person.cs and add the following:

using System.Collections.Generic;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;

namespace MyApi
{
    public class Person : Identifiable
    {
        [Attr]
        public string Name { get; set; }
        
        [HasMany]
        public ICollection<TodoItem> TodoItems { get; set; }
    }
}

We're also going to add our new TodoItem to our DbContext to release the power of Entity Framework Core:

using Microsoft.EntityFrameworkCore;

namespace MyApi
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options)
            : base(options) { }

        public DbSet<Person> People { get; set; }
        public DbSet<TodoItem> TodoItems { get; set; }
    }
}

To expose the new endpoint, let's create a controller, TodoItemsController.cs with the following boilerplate:

using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Services;
using Microsoft.Extensions.Logging;

namespace MyApi.Controllers
{
    public class TodoItemsController : JsonApiController<Person>
    {
        public TodoItemsController(
                IJsonApiOptions options,
                ILoggerFactory loggerFactory,
                IResourceService<TodoItem> resourceService)
            : base(options, loggerFactory, resourceService)
        { }
    }
}

Before we run our API, make sure to delete the my-api.db file in our project folder for it to pick up the new schema.

We can now run our API and go to https://localhost:5001/todoItems and we will see the following:

{
  "links": {
    "self": "<https://localhost:5001/todoItems>",
    "first": "<https://localhost:5001/todoItems>"
  },
  "data": []
}

It works, but it's a bit boring. Let's add some more data in our Startup.cs file:

if (!context.People.Any())
{
		var person = context.People.Add(new Person
    {
		    Name = "John Doe",
        TodoItems = new List<TodoItem>
        {
		        new TodoItem { Todo = "Make pizza", Priority = 1},
						new TodoItem { Todo = "Clean room", Priority = 2}
        }
     });
     context.SaveChanges();
}

Another query will show us the following, if we go to /people we will see:

{
  "links": {
    "self": "<https://localhost:5001/people>",
    "first": "<https://localhost:5001/people>"
  },
  "data": [
    {
      "type": "people",
      "id": "1",
      "attributes": {
        "name": "John Doe"
      },
      "relationships": {
        "todoItems": {
          "links": {
            "self": "<https://localhost:5001/people/1/relationships/todoItems>",
            "related": "<https://localhost:5001/people/1/todoItems>"
          }
        }
      },
      "links": {
        "self": "<https://localhost:5001/people/1>"
      }
    }
  ]
}

And if we query /todoItems we see:

{
  "links": {
    "self": "<https://localhost:5001/todoItems>",
    "first": "<https://localhost:5001/todoItems>"
  },
  "data": [
    {
      "type": "todoItems",
      "id": "1",
      "attributes": {
        "todo": "Make pizza",
        "priority": 1
      },
      "relationships": {
        "owner": {
          "links": {
            "self": "<https://localhost:5001/todoItems/1/relationships/owner>",
            "related": "<https://localhost:5001/todoItems/1/owner>"
          }
        }
      },
      "links": {
        "self": "<https://localhost:5001/todoItems/1>"
      }
    },
    {
      "type": "todoItems",
      "id": "2",
      "attributes": {
        "todo": "Clean room",
        "priority": 2
      },
      "relationships": {
        "owner": {
          "links": {
            "self": "<https://localhost:5001/todoItems/2/relationships/owner>",
            "related": "<https://localhost:5001/todoItems/2/owner>"
          }
        }
      },
      "links": {
        "self": "<https://localhost:5001/todoItems/2>"
      }
    }
  ]
}