Json:API Spezifikation mit .NET implementieren

Alexander W. | Softwareentwickler
03/2020

Effiziente JSON:API Implementierung mit JsonApiDotNetCore

Die JSON:API Spezifikation definiert, wie ein Client eine Ressource anfragen oder ändern kann und wie der Server auf diese Anfrage zu antworten hat. JSON:API wurde entwickelt, um die Anzahl der Anfragen und die Menge der übertragenen Daten zu minimieren. JSON:API benötigt den Media Type „application/vnd.api+json“, um Daten auszutauschen. Alle Details zu den Spezifikationen sind unter https://jsonapi.org zu finden.

Durchführung

Wer die Spezifikation nicht „from scratch“ in sein Projekt implementieren möchte, kann auf bereits bestehende Implementierungen für die verschiedensten Umgebungen zurückgreifen. Eine Auswahl ist unter https://jsonapi.org/implementations/ zu finden. Im Detail gehe ich hier auf das „JsonApiDotNetCore“-Package ein, welches ich per NuGet in meine .NET Core API eingebunden habe.

Nach der Installation des NuGet-Pakets muss die Ressource definiert werden. Um die Ressource für „JsonApiDotNetCore“ verfügbar zu machen, muss sie von „Identifiable“ erben. Um den Namen der Eigenschaft innerhalb des JSON-Objektes zu definieren, muss diese per DataAnnotation über der Eigenschaft angelegt werden. Die Ressourcenklasse könnte dann so aussehen:

1public class MitarbeiterDTO : Identifiable 
2{ 
3    [Attr("Id")] 
4    public override int Id { get; set; } 
5
6    [Attr("Vorname")] 
7    public string Vorname { get; set; } 
8}

In der Startup.cs Configure-Methode muss „JsonApiDotNetCore“ als Middleware registriert werden:

1public void Configure(IApplicationBuilder app) 
2{ 
3    app.UseJsonApi(); 
4}

app.UseMvc() braucht dann nicht explizit aufgerufen zu werden, da es in app.UseJsonApi() bereits enthalten ist.

Mit DbContext

Anschließend wird ein DbContext angelegt, welcher die Ressource implementiert:

1public class BeispielContext : DbContext 
2{ 
3    public BeispielContext(DbContextOptions<BeispielContext> options) : base(options) { } 
4    public DbSet<MitarbeiterDTO> Mitarbeiter { get; set; } 
5}

Dieser wird dann in der Startup.cs inklusive der globalen Optionen registriert:

1services.AddJsonApi<BeispielContext>(options => 
2{ 
3    options.Namespace = "jsonapi"; 
4    options.IncludeTotalRecordCount = true; 
5});

Soll die Ressource durch eine Custom-Funktion bereitgestellt oder geändert werden, ist es nötig, eine Klasse anzulegen, welche von EntityResourceService erbt:

1public class MitarbeiterERS : EntityResourceService<MitarbeiterDTO>

Um die API-üblichen Funktionen wie GET, GET(id), PATCH, POST, DELETE zu überschreiben, muss diese ebenfalls in der Startup.cs registriert werden:

1services.AddScoped<IResourceService<MitarbeiterDTO>, MitarbeiterERS>();

Ohne DbContext

Alternativ kann auch ohne DbContext gearbeitet werden, dann müssen die einzelnen Ressourcen allerdings über den BuildResourceGraph angemeldet und zusätzlich eine Klasse angelegt werden, welche IResourceService implementiert. In dieser können dann die API-spezifischen Funktionen implementiert werden, welche für die Ressource benötigt werden.

1var mvcBuilder = services.AddMvcCore(); 
2
3services.AddJsonApi(options => 
4{ 
5    options.Namespace = "jsonapi"; 
6    options.IncludeTotalRecordCount = true; 
7    options.BuildResourceGraph((builder) => 
8    { 
9        builder.AddResource<MitarbeiterDTO>("mitarbeiter"); 
10    }); 
11});

1public class MitarbeiterResourceService : IResourceService<MitarbeiterDTO>

Um Funktionen wie Filter oder Sorting ohne DbContext nutzen zu können, wird eine weitere Klasse benötigt, die von ResourceDefinition erbt:

1public class MitarbeiterResourceDefinition : ResourceDefinition<MitarbeiterDTO>

Diese muss ebenfalls in der Startup.cs registriert werden:

1services.AddScoped<ResourceDefinition<MitarbeiterDTO>, MitarbeiterResourceDefinition>();

Der JsonApiController

Nun kann ein API-Controller erstellt werden, welcher von JsonApiController erbt. Dieser Controller stellt bereits die API-üblichen Funktionen wie GET, GET(id), PATCH, POST, DELETE bereit. Die Aufrufe werden dann, abhängig davon, ob DbContext genutzt wird und ob ein ResourceService implementiert wurde, an die jeweilige Stelle delegiert.

1public class MitarbeiterController : JsonApiController<MitarbeiterDTO> 
2{ 
3    public MitarbeiterController(IJsonApiContext jsonApiContext,  
4                                 IResourceService<MitarbeiterDTO> resourceService,  
5                                 ILoggerFactory loggerFactory)  
6        : base(jsonApiContext, resourceService, loggerFactory) 
7    {} 
8}

Die API-Funktionen können im Controller überschrieben und somit für den individuellen Einsatz angepasst werden.

Der BaseJsonApiController

Alternativ kann auch von BaseJsonApiController geerbt werden:

1public class MitarbeiterController : BaseJsonApiController<MitarbeiterDTO>

Dann müssen die API-Funktionen allerdings überschrieben werden, da ansonsten ein 404-Fehler zurückgegeben wird. Optional hat man hier die Möglichkeit, mit DataAnnotations die Fähigkeiten des Controllers einzuschränken.

HttpReadOnly ermöglicht es beispielsweise, ohne ausdrückliches Überschreiben der Funktionen alle „lesenden“ Anfragen zu nutzen. Für alle „nicht-lesenden“ Anfragen wird dann ein „405 Method Not Allowed“ zurückgegeben.

1[HttpReadOnly] 
2public class MitarbeiterController : BaseJsonApiController<MitarbeiterDTO>

Dies ist nur ein Teil der umfangreichen Konfigurationsmöglichkeiten, die dieses Paket zur Verfügung stellt.

Die Einarbeitung in die JSON:API Spezifikation und in das „JsonApiDotNetCore“-Package hat einige Zeit in Anspruch genommen. Nach heutigem Stand kann ich jedoch nicht behaupten, alles gesehen oder verstanden zu haben. Wer jedoch JSON:API Spezifikationen in einem MVC-Projekt umsetzen und nicht Monate mit der Implementierung verbringen möchte, hat zurzeit nur wenige Alternativen zu diesem Paket.

Der Einsatz von JSON:API ist grundsätzlich vom Umfang des Projektes und der benötigten Ressourcen abhängig und sollte vor der Entscheidung individuell geprüft werden.

Alexander W. | Softwareentwickler
Zurück zur Übersicht

Gemeinsam Großes schaffen

Wir freuen uns auf ein kostenloses Erstgespräch mit Ihnen!
Unser Geschäftsführer Tibor Csizmadia und unser Kundenbetreuer Jens Walter stehen Ihnen persönlich zur Verfügung. Profitieren Sie von unserer langjährigen Erfahrung und erhalten Sie eine kompetente Erstberatung in einem unverbindlichen Austausch.
Foto von Tibor

Tibor Csizmadia

Geschäftsführer
Foto von Jens

Jens Walter

Projektmanager
Devware GmbH verpflichtet sich, Ihre Privatsphäre zu schützen. Wir benötigen Ihre Kontaktinformationen, um Sie bezüglich unserer Produkte und Dienstleistungen zu kontaktieren. Mit Klick auf Absenden geben Sie sich damit einverstanden. Weitere Informationen finden Sie unter Datenschutz.
Vielen Dank für Ihre Nachricht!

Wir haben Ihre Anfrage erhalten und melden uns in Kürze bei Ihnen.

Falls Sie in der Zwischenzeit Fragen haben, können Sie uns jederzeit unter Kontaktanfrage@devware.de erreichen.

Wir freuen uns auf die Zusammenarbeit!
Oops! Something went wrong while submitting the form.
KontaktImpressumDatenschutz