Skip to content

Commit 65430a8

Browse files
author
Bart Koelman
committed
Updated example client project using Visual Studio defaults and updated documentation
1 parent 1baed2d commit 65430a8

File tree

5 files changed

+129
-148
lines changed

5 files changed

+129
-148
lines changed

docs/usage/openapi-client.md

Lines changed: 97 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,137 +1,123 @@
11
# OpenAPI Client
22

3-
You can generate a client in various programming languages from the OpenAPI specification file that JsonApiDotNetCore APIs provide. For C# .NET clients generated using NSwag, we provide an additional package that introduces support for partial PATCH/POST requests. The issue here is that a property on a generated C# class being `null` could mean "set the value to `null` in the request" or "this is `null` because I never touched it".
3+
You can generate a JSON:API client in various programming languages from the OpenAPI specification file that JsonApiDotNetCore APIs provide. For C# .NET clients generated using NSwag, we provide an additional package that introduces support for partial PATCH/POST requests. The issue here is that a property on a generated C# class being `null` could mean "set the value to `null` in the request" or "this is `null` because I never touched it".
44

5-
## Installation
6-
7-
You are required to install the following NuGet packages:
8-
9-
- `JsonApiDotNetCore.OpenApi.Client`
10-
- `NSwag.ApiDescription.Client`
11-
- `Microsoft.Extensions.ApiDescription.Cient`
12-
- `NSwag.ApiDescription.Client`
13-
14-
The following examples demonstrate how to install the `JsonApiDotNetCore.OpenApi.Client` package.
15-
16-
### CLI
17-
18-
```
19-
dotnet add package JsonApiDotNetCore.OpenApi.Client
20-
```
5+
## Getting started
216

227
### Visual Studio
238

24-
```powershell
25-
Install-Package JsonApiDotNetCore.OpenApi.Client
26-
```
27-
28-
### *.csproj
29-
30-
```xml
31-
<ItemGroup>
32-
<!-- Be sure to check NuGet for the latest version # -->
33-
<PackageReference Include="JsonApiDotNetCore.OpenApi.Client" Version="4.0.0" />
34-
</ItemGroup>
35-
```
9+
The easiest way to get started is by using the built-in capabilities of Visual Studio. The next steps describe how to generate a JSON:API client library and use our package.
3610

11+
1. In **Solution Explorer**, right-click your client project, select **Add** > **Service Reference** and choose **OpenAPI**.
12+
2. On the next page, specify the OpenAPI URL to your JSON:API server, for example: `http://localhost:14140/swagger/v1/swagger.json`.
13+
Optionally provide a class name and namespace and click **Finish**.
14+
Visual Studio now downloads your swagger.json and updates your project file. This results in a pre-build step that generates the client code.
15+
Tip: To later re-download swagger.json and regenerate the client code, right-click **Dependencies** > **Manage Connected Services** and click the **Refresh** icon.
16+
3. Although not strictly required, we recommend to run package update now, which fixes some issues and removes the `Stream` parameter from generated calls.
17+
4. Add some demo code that calls one of your JSON:API endpoints. For example:
18+
```c#
19+
using var httpClient = new HttpClient();
20+
var apiClient = new ExampleApiClient("http://localhost:14140", httpClient);
3721

38-
## Adding an OpenApiReference
22+
PersonCollectionResponseDocument getResponse = await apiClient.GetPersonCollectionAsync();
3923

40-
Add a reference to your OpenAPI specification in your project file as demonstrated below.
41-
42-
```xml
43-
<ItemGroup>
44-
<OpenApiReference Include="swagger.json">
45-
<Namespace>JsonApiDotNetCoreExampleClient.GeneratedCode</Namespace>
46-
<ClassName>ExampleApiClient</ClassName>
47-
<CodeGenerator>NSwagCSharp</CodeGenerator>
48-
<Options>/UseBaseUrl:false /GenerateClientInterfaces:true</Options>
49-
</OpenApiReference>
50-
</ItemGroup>
51-
```
52-
53-
54-
## Usage
55-
56-
The NSwag tooling generates the OpenAPI client during a prebuild step. Once your application is built,
57-
you can instantiate it using the class name as indicated in the project file.
58-
59-
```c#
60-
namespace JsonApiDotNetCoreExampleClient
61-
{
62-
class Program
24+
foreach (PersonDataInResponse person in getResponse.Data)
6325
{
64-
static void Main(string[] args)
65-
{
66-
using (HttpClient httpClient = new HttpClient())
67-
{
68-
ExampleApiClient exampleApiClient = new ExampleApiClient(httpClient);
69-
70-
// IntelliSense is now available on `exampleApiClient`!
71-
}
72-
}
26+
Console.WriteLine($"Found user {person.Id} named '{person.Attributes.FirstName} {person.Attributes.LastName}'.");
7327
}
74-
}
75-
```
76-
77-
Support for partial write requests can be enabled by leveraging the extensibility points of the generated client.
78-
79-
```c#
80-
namespace JsonApiDotNetCoreExampleClient.GeneratedCode
81-
{
82-
// Note that this class should be in the same namespace as the ExampleApiClient generated by NSwag.
83-
public partial class ExampleApiClient : JsonApiClient
28+
```
29+
5. Add our client package to your project:
30+
```
31+
dotnet add package JsonApiDotNetCore.OpenApi.Client
32+
```
33+
6. Add the following glue code to connect our package with your generated code. The code below assumes you specified `ExampleApiClient` as class name in step 2.
34+
```c#
35+
using JsonApiDotNetCore.OpenApi.Client;
36+
using Newtonsoft.Json;
37+
38+
partial class ExampleApiClient : JsonApiClient
8439
{
8540
partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings)
8641
{
8742
SetSerializerSettingsForJsonApi(settings);
8843
}
8944
}
90-
}
91-
```
92-
93-
You can now perform a write request by calling the `RegisterAttributesForRequest` method. Calling this method treats all attributes that contain their default value (<c>null</c> for reference types, <c>0</c> for integers, <c>false</c> for booleans, etc) as omitted unless explicitly listed to include them using the `alwaysIncludedAttributeSelectors` parameter.
94-
95-
```c#
96-
// Program.cs
97-
static void Main(string[] args)
98-
{
99-
using (HttpClient httpClient = new HttpClient())
45+
```
46+
7. Extend your demo code to send a partial PATCH request with the help of our package:
47+
```c#
48+
var patchRequest = new PersonPatchRequestDocument
10049
{
101-
ExampleApiClient exampleApiClient = new ExampleApiClient(httpClient);
102-
103-
var requestDocument = new PersonPatchRequestDocument
50+
Data = new PersonDataInPatchRequest
10451
{
105-
Data = new PersonDataInPatchRequest
52+
Id = "1",
53+
Attributes = new PersonAttributesInPatchRequest
10654
{
107-
Id = "546",
108-
Type = PersonResourceType.People,
109-
Attributes = new PersonAttributesInPatchRequest
110-
{
111-
FirstName = "Jack"
112-
}
55+
FirstName = "Jack"
11356
}
114-
};
115-
116-
using (apiClient.RegisterAttributesForRequestDocument<PersonPatchRequestDocument, PersonDataInPatchRequest>(requestDocument, person => person.LastName)
117-
{
118-
await exampleApiClient.PatchPersonAsync(543, requestDocument));
119-
120-
// The request will look like this:
121-
//
122-
// {
123-
// "data": {
124-
// "type": "people",
125-
// "id": "543",
126-
// "attributes": {
127-
// "firstName": "Jack",
128-
// "lastName": null,
129-
// }
130-
// }
131-
// }
13257
}
58+
};
13359
60+
// This line results in sending "lastName: null" instead of omitting it.
61+
using (apiClient.RegisterAttributesForRequestDocument<PersonPatchRequestDocument, PersonAttributesInPatchRequest>(
62+
patchRequest, person => person.LastName))
63+
{
64+
PersonPrimaryResponseDocument patchResponse = await apiClient.PatchPersonAsync("1", patchRequest);
65+
66+
// The sent request looks like this:
67+
// {
68+
// "data": {
69+
// "type": "people",
70+
// "id": "1",
71+
// "attributes": {
72+
// "firstName": "Jack",
73+
// "lastName": null
74+
// }
75+
// }
76+
// }
13477
}
135-
}
78+
```
79+
80+
### Other IDEs
81+
82+
When using the command-line, you can try the [Microsoft.dotnet-openapi Global Tool](https://docs.microsoft.com/en-us/aspnet/core/web-api/microsoft.dotnet-openapi?view=aspnetcore-5.0).
83+
84+
Alternatively, the next section shows what to add to your client project file directly:
85+
86+
```xml
87+
<ItemGroup>
88+
<PackageReference Include="Microsoft.Extensions.ApiDescription.Client" Version="3.0.0">
89+
<PrivateAssets>all</PrivateAssets>
90+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
91+
</PackageReference>
92+
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
93+
<PackageReference Include="NSwag.ApiDescription.Client" Version="13.0.5">
94+
<PrivateAssets>all</PrivateAssets>
95+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
96+
</PackageReference>
97+
</ItemGroup>
98+
99+
<ItemGroup>
100+
<OpenApiReference Include="OpenAPIs\swagger.json" CodeGenerator="NSwagCSharp" ClassName="ExampleApiClient">
101+
<SourceUri>http://localhost:14140/swagger/v1/swagger.json</SourceUri>
102+
</OpenApiReference>
103+
</ItemGroup>
136104
```
137105

106+
From here, continue from step 3 in the list of steps for Visual Studio.
107+
108+
## Customization
109+
110+
### NSwga
111+
112+
The `OpenApiReference` element in the project file accepts an `Options` element to pass additional settings to the client generator,
113+
which are listed at https://github.com/RicoSuter/NSwag/blob/master/src/NSwag.Commands/Commands/CodeGeneration/OpenApiToCSharpClientCommand.cs.
114+
115+
For example, the next section puts the generated code in a namespace, removes the `baseUrl` parameter and generates an interface (which is handy for dependency injection):
116+
```xml
117+
<OpenApiReference Include="swagger.json">
118+
<Namespace>ExampleProject.ServiceAccess.GeneratedCode</Namespace>
119+
<ClassName>SalesApiClient</ClassName>
120+
<CodeGenerator>NSwagCSharp</CodeGenerator>
121+
<Options>/UseBaseUrl:false /GenerateClientInterfaces:true</Options>
122+
</OpenApiReference>
123+
```

src/Examples/JsonApiDotNetCoreExampleClient/GeneratedCode/ExampleApiClient.cs renamed to src/Examples/JsonApiDotNetCoreExampleClient/ExampleApiClient.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
using JsonApiDotNetCore.OpenApi.Client;
22
using Newtonsoft.Json;
33

4-
namespace JsonApiDotNetCoreExampleClient.GeneratedCode
4+
namespace JsonApiDotNetCoreExampleClient
55
{
6-
internal partial class ExampleApiClient : JsonApiClient
6+
public partial class ExampleApiClient : JsonApiClient
77
{
88
partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings)
99
{
Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,28 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
2-
<PropertyGroup>
3-
<OutputType>Exe</OutputType>
4-
<TargetFramework>$(NetCoreAppVersion)</TargetFramework>
5-
</PropertyGroup>
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
<TargetFramework>$(NetCoreAppVersion)</TargetFramework>
5+
</PropertyGroup>
66

7-
<ItemGroup>
8-
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
9-
<PackageReference Include="Microsoft.Extensions.ApiDescription.Client" Version="5.0.9">
10-
<PrivateAssets>all</PrivateAssets>
11-
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
12-
</PackageReference>
13-
<PackageReference Include="NSwag.ApiDescription.Client" Version="13.13.2">
14-
<PrivateAssets>all</PrivateAssets>
15-
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
16-
</PackageReference>
17-
</ItemGroup>
18-
19-
<ItemGroup>
20-
<OpenApiReference Include="swagger.json">
21-
<ClassName>ExampleApiClient</ClassName>
22-
<CodeGenerator>NSwagCSharp</CodeGenerator>
23-
<Namespace>JsonApiDotNetCoreExampleClient.GeneratedCode</Namespace>
24-
<Options>/UseBaseUrl:false /ClientClassAccessModifier:internal</Options>
25-
</OpenApiReference>
26-
</ItemGroup>
7+
<ItemGroup>
8+
<ProjectReference Include="..\..\JsonApiDotNetCore.OpenApi.Client\JsonApiDotNetCore.OpenApi.Client.csproj" />
9+
</ItemGroup>
2710

28-
<ItemGroup>
29-
<ProjectReference Include="..\..\JsonApiDotNetCore.OpenApi.Client\JsonApiDotNetCore.OpenApi.Client.csproj" />
30-
</ItemGroup>
11+
<ItemGroup>
12+
<PackageReference Include="Microsoft.Extensions.ApiDescription.Client" Version="5.0.10">
13+
<PrivateAssets>all</PrivateAssets>
14+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
15+
</PackageReference>
16+
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
17+
<PackageReference Include="NSwag.ApiDescription.Client" Version="13.13.2">
18+
<PrivateAssets>all</PrivateAssets>
19+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
20+
</PackageReference>
21+
</ItemGroup>
22+
23+
<ItemGroup>
24+
<OpenApiReference Include="OpenAPIs\swagger.json" CodeGenerator="NSwagCSharp" ClassName="ExampleApiClient">
25+
<SourceUri>http://localhost:14140/swagger/v1/swagger.json</SourceUri>
26+
</OpenApiReference>
27+
</ItemGroup>
3128
</Project>

src/Examples/JsonApiDotNetCoreExampleClient/Program.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
1-
using System;
1+
using System;
22
using System.Net.Http;
33
using System.Threading.Tasks;
4-
using JsonApiDotNetCoreExampleClient.GeneratedCode;
54

65
namespace JsonApiDotNetCoreExampleClient
76
{
87
internal static class Program
98
{
9+
private const string BaseUrl = "http://localhost:14140";
10+
1011
private static async Task Main()
1112
{
12-
using var httpClient = new HttpClient
13-
{
14-
BaseAddress = new Uri("http://localhost:14140")
15-
};
13+
using var httpClient = new HttpClient();
1614

17-
ExampleApiClient exampleApiClient = new(httpClient);
15+
ExampleApiClient exampleApiClient = new(BaseUrl, httpClient);
1816

1917
try
2018
{

0 commit comments

Comments
 (0)