After enabling OpenAPI, you can generate a typed JSON:API client for your API in various programming languages.
Note
If you prefer a generic JSON:API client instead of a typed one, choose from the existing client libraries.
The following code generators are supported, though you may try others as well:
- NSwag (v14.1 or higher): Produces clients for C# (requires
Newtonsoft.Json
) and TypeScript - Kiota: Produces clients for C#, Go, Java, PHP, Python, Ruby, Swift and TypeScript
For C# clients, we provide an additional package that provides workarounds for bugs in NSwag and enables using partial POST/PATCH requests.
To add it to your project, run the following command:
dotnet add package JsonApiDotNetCore.OpenApi.Client.NSwag
For C# clients, we provide an additional package that provides workarounds for bugs in Kiota, as well as MSBuild integration.
To add it to your project, run the following command:
dotnet add package JsonApiDotNetCore.OpenApi.Client.Kiota
To generate your C# client, follow the steps below.
The easiest way to get started is by using the built-in capabilities of Visual Studio. The following steps describe how to generate and use a JSON:API client in C#, combined with our NuGet package.
-
In Solution Explorer, right-click your client project, select Add > Service Reference and choose OpenAPI.
-
On the next page, specify the OpenAPI URL to your JSON:API server, for example:
http://localhost:14140/swagger/v1/swagger.json
. SpecifyExampleApiClient
as the class name, optionally provide a namespace and click Finish. Visual Studio now downloads your swagger.json and updates your project file. This adds a pre-build step that generates the client code.[!TIP] To later re-download swagger.json and regenerate the client code, right-click Dependencies > Manage Connected Services and click the Refresh icon.
-
Run package update now, which fixes incompatibilities and bugs from older versions.
-
Add our client package to your project:
dotnet add package JsonApiDotNetCore.OpenApi.Client.NSwag
-
Add code that calls one of your JSON:API endpoints.
using var httpClient = new HttpClient(); var apiClient = new ExampleApiClient(httpClient); var getResponse = await apiClient.GetPersonCollectionAsync(new Dictionary<string, string?> { ["filter"] = "has(assignedTodoItems)", ["sort"] = "-lastName", ["page[size]"] = "5" }); foreach (var person in getResponse.Data) { Console.WriteLine($"Found person {person.Id}: {person.Attributes!.DisplayName}"); }
-
Extend the demo code to send a partial PATCH request with the help of our package:
var updatePersonRequest = new UpdatePersonRequestDocument { Data = new DataInUpdatePersonRequest { Id = "1", // Using TrackChangesFor to send "firstName: null" instead of omitting it. Attributes = new TrackChangesFor<AttributesInUpdatePersonRequest>(_apiClient) { Initializer = { FirstName = null, LastName = "Doe" } }.Initializer } }; await ApiResponse.TranslateAsync(async () => await _apiClient.PatchPersonAsync(updatePersonRequest.Data.Id, updatePersonRequest)); // The sent request looks like this: // { // "data": { // "type": "people", // "id": "1", // "attributes": { // "firstName": null, // "lastName": "Doe" // } // } // }
Tip
The example project contains an enhanced version
that uses IHttpClientFactory
for scalability and
resiliency and logs the HTTP requests and responses.
Additionally, the example shows how to write the swagger.json file to disk when building the server, which is imported from the client project.
This keeps the server and client automatically in sync, which is handy when both are in the same solution.
The following section shows what to add to your client project file directly:
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.ApiDescription.Client" Version="8.0.*" PrivateAssets="all" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.*" />
<PackageReference Include="NSwag.ApiDescription.Client" Version="14.1.*" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<OpenApiReference Include="OpenAPIs\swagger.json">
<SourceUri>http://localhost:14140/swagger/v1/swagger.json</SourceUri>
<ClassName>ExampleApiClient</ClassName>
<OutputPath>%(ClassName).cs</OutputPath>
</OpenApiReference>
</ItemGroup>
From here, continue from step 3 in the list of steps for Visual Studio.
To generate your C# client, first add the Kiota tool to your solution:
dotnet tool install microsoft.openapi.kiota
After adding the JsonApiDotNetCore.OpenApi.Client.Kiota
package to your project, add a KiotaReference
element
to your project file to import your OpenAPI file. For example:
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<KiotaReference Include="path/to/openapi.json">
<NamespaceName>$(MSBuildProjectName).GeneratedCode</NamespaceName>
<ClassName>ExampleApiClient</ClassName>
<OutputPath>./GeneratedCode</OutputPath>
<ExtraArguments>$(JsonApiExtraArguments)</ExtraArguments>
</KiotaReference>
</ItemGroup>
</Project>
Note
The ExtraArguments
parameter is required for compatibility with JSON:API.
Next, build your project. It runs the kiota command-line tool, which generates files in the GeneratedCode
subdirectory.
Caution
If you're not using <KiotaReference>
, at least make sure you're passing the --backing-store
switch to the command-line tool,
which is needed for JSON:API partial POST/PATCH requests to work correctly.
Kiota is pretty young and therefore still rough around the edges. At the time of writing, there are various bugs, for which we have workarounds in place. For a full example, see the example project.
Various switches enable you to tweak the client generation to your needs. See the section below for an overview.
The OpenApiReference
element can be customized using various NSwag-specific MSBuild properties.
See the source code for their meaning.
The JsonApiDotNetCore.OpenApi.Client.NSwag
package sets various of these for optimal JSON:API support.
Note
Earlier versions of NSwag required the use of <Options>
to specify command-line switches directly.
This is no longer recommended and may conflict with the new MSBuild properties.
For example, the following section puts the generated code in a namespace, makes the client class internal and generates an interface (handy when writing tests):
<OpenApiReference Include="swagger.json">
<Namespace>ExampleProject.GeneratedCode</Namespace>
<NSwagClientClassAccessModifier>internal</NSwagClientClassAccessModifier>
<NSwagGenerateClientInterfaces>true</NSwagGenerateClientInterfaces>
</OpenApiReference>
The available command-line switches for Kiota are described here.
At the time of writing, Kiota provides no official integration with MSBuild. Our example project takes a stab at it, which seems to work. If you're an MSBuild expert, please help out!
<Target Name="ExcludeKiotaGeneratedCode" BeforeTargets="BeforeCompile;CoreCompile" Condition="$(DesignTimeBuild) != true And $(BuildingProject) == true">
<ItemGroup>
<Compile Remove="**\GeneratedCode\**\*.cs" />
</ItemGroup>
</Target>
<Target Name="KiotaRunTool" BeforeTargets="BeforeCompile;CoreCompile" AfterTargets="ExcludeKiotaGeneratedCode"
Condition="$(DesignTimeBuild) != true And $(BuildingProject) == true">
<Exec
Command="dotnet kiota generate --language CSharp --class-name ExampleApiClient --namespace-name OpenApiKiotaClientExample.GeneratedCode --output ./GeneratedCode --backing-store --exclude-backward-compatible --clean-output --clear-cache --log-level Error --openapi ../JsonApiDotNetCoreExample/GeneratedSwagger/JsonApiDotNetCoreExample.json" />
</Target>
<Target Name="IncludeKiotaGeneratedCode" BeforeTargets="BeforeCompile;CoreCompile" AfterTargets="KiotaRunTool"
Condition="$(DesignTimeBuild) != true And $(BuildingProject) == true">
<ItemGroup>
<Compile Include="**\GeneratedCode\**\*.cs" />
</ItemGroup>
</Target>
The use of HTTP headers varies per client generator. To use ETags for caching, see the notes below.
To gain access to HTTP response headers, add the following in a PropertyGroup
or directly in the OpenApiReference
:
<NSwagWrapResponses>true</NSwagWrapResponses>
This enables the following code, which is explained below:
var getResponse = await ApiResponse.TranslateAsync(() => apiClient.GetPersonCollectionAsync());
string eTag = getResponse.Headers["ETag"].Single();
Console.WriteLine($"Retrieved {getResponse.Result?.Data.Count ?? 0} people.");
// wait some time...
getResponse = await ApiResponse.TranslateAsync(() => apiClient.GetPersonCollectionAsync(if_None_Match: eTag));
if (getResponse is { StatusCode: (int)HttpStatusCode.NotModified, Result: null })
{
Console.WriteLine("The HTTP response hasn't changed, so no response body was returned.");
}
The response of the first API call contains both data and an ETag header, which is a fingerprint of the response. That ETag gets passed to the second API call. This enables the server to detect if something changed, which optimizes network usage: no data is sent back, unless is has changed. If you only want to ask whether data has changed without fetching it, use a HEAD request instead.
Use HeadersInspectionHandlerOption
to gain access to HTTP response headers. For example:
var headerInspector = new HeadersInspectionHandlerOption
{
InspectResponseHeaders = true
};
var responseDocument = await apiClient.Api.People.GetAsync(configuration => configuration.Options.Add(headerInspector));
string eTag = headerInspector.ResponseHeaders["ETag"].Single();
Due to a bug in Kiota, a try/catch block is needed additionally to make this work.
For a full example, see the example project.
Atomic operations are fully supported. The example project demonstrates how to use them. It uses local IDs to:
- Create a new tag
- Create a new person
- Update the person to clear an attribute (using
TrackChangesFor
) - Create a new todo-item, tagged with the new tag, and owned by the new person
- Assign the todo-item to the created person
Atomic operations are fully supported. See the example project demonstrates how to use them. It uses local IDs to:
- Create a new tag
- Create a new person
- Update the person to clear an attribute (using built-in backing-store)
- Create a new todo-item, tagged with the new tag, and owned by the new person
- Assign the todo-item to the created person