Skip to content

Latest commit

 

History

History
322 lines (236 loc) · 12.9 KB

openapi-client.md

File metadata and controls

322 lines (236 loc) · 12.9 KB

OpenAPI clients

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# 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 PATCH/POST 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.

To add it to your project, run the following command:

dotnet add package JsonApiDotNetCore.OpenApi.Client.Kiota

Getting started

To generate your C# client, follow the steps below.

Visual Studio

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.

  1. In Solution Explorer, right-click your client project, select Add > Service Reference and choose OpenAPI.

  2. On the next page, specify the OpenAPI URL to your JSON:API server, for example: http://localhost:14140/swagger/v1/swagger.json. Specify ExampleApiClient 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.

  3. Run package update now, which fixes incompatibilities and bugs from older versions.

  4. Add our client package to your project:

    dotnet add package JsonApiDotNetCore.OpenApi.Client.NSwag
    
  5. Add the following glue code to connect our package with your generated code.

    [!NOTE] The class name must be the same as specified in step 2. If you also specified a namespace, put this class in the same namespace. For example, add namespace GeneratedCode; below the using lines.

    using JsonApiDotNetCore.OpenApi.Client.NSwag;
    using Newtonsoft.Json;
    
    partial class ExampleApiClient : JsonApiClient
    {
        partial void Initialize()
        {
            _instanceSettings = new JsonSerializerSettings(_settings.Value);
            SetSerializerSettingsForJsonApi(_instanceSettings);
        }
    }
  6. 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}");
    }
  7. 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",
            Attributes = new AttributesInUpdatePersonRequest
            {
                LastName = "Doe"
            }
        }
    };
    
    // This line results in sending "firstName: null" instead of omitting it.
    using (apiClient.WithPartialAttributeSerialization<UpdatePersonRequestDocument, AttributesInUpdatePersonRequest>(
        updatePersonRequest, person => person.FirstName))
    {
        // Workaround for https://github.com/RicoSuter/NSwag/issues/2499.
        await ApiResponse.TranslateAsync(() =>
            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.

Other IDEs

When using the command line, you can try the Microsoft.dotnet-openapi Global Tool.

Alternatively, 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>
    <CodeGenerator>NSwagCSharp</CodeGenerator>
    <ClassName>ExampleApiClient</ClassName>
    <OutputPath>ExampleApiClient.cs</OutputPath>
  </OpenApiReference>
</ItemGroup>

From here, continue from step 3 in the list of steps for Visual Studio.

To generate your C# client, install the Kiota tool by following the steps at https://learn.microsoft.com/en-us/openapi/kiota/install#install-as-net-tool.

Next, generate client code by running the command line tool. For example:

dotnet kiota generate --language CSharp --class-name ExampleApiClient --output ./GeneratedCode --backing-store --exclude-backward-compatible --clean-output --clear-cache --openapi http://localhost:14140/swagger/v1/swagger.json

Caution

The --backing-store switch is needed for JSON:API partial PATCH/POST 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.


Configuration

Various switches enable you to tweak the client generation to your needs. See the section below for an overview.

The OpenApiReference can be customized using various NSwag-specific MSBuild properties. See the source code for their meaning.

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 and generates an interface (handy when writing tests):

<OpenApiReference Include="swagger.json">
  <Namespace>ExampleProject.GeneratedCode</Namespace>
  <ClassName>SalesApiClient</ClassName>
  <CodeGenerator>NSwagCSharp</CodeGenerator>
  <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="RemoveKiotaGeneratedCode" BeforeTargets="BeforeCompile;CoreCompile" Condition="$(DesignTimeBuild) != true And $(BuildingProject) == true">
  <ItemGroup>
    <Compile Remove="**\GeneratedCode\**\*.cs" />
  </ItemGroup>
</Target>

<Target Name="KiotaRunTool" BeforeTargets="BeforeCompile;CoreCompile" AfterTargets="RemoveKiotaGeneratedCode"
  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>

Headers and caching

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

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
  • 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
  • Create a new todo-item, tagged with the new tag, and owned by the new person
  • Assign the todo-item to the created person