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