|
1 | 1 | # OpenAPI Client
|
2 | 2 |
|
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](https://swagger.io/specification/) file that JsonApiDotNetCore APIs provide. |
4 | 4 |
|
5 |
| -## Installation |
| 5 | +For C# .NET clients generated using [NSwag](https://github.com/RicoSuter/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". |
6 | 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 |
| -``` |
| 7 | +## Getting started |
21 | 8 |
|
22 | 9 | ### Visual Studio
|
23 | 10 |
|
24 |
| -```powershell |
25 |
| -Install-Package JsonApiDotNetCore.OpenApi.Client |
26 |
| -``` |
| 11 | +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. |
27 | 12 |
|
28 |
| -### *.csproj |
| 13 | +1. In **Solution Explorer**, right-click your client project, select **Add** > **Service Reference** and choose **OpenAPI**. |
29 | 14 |
|
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 |
| -``` |
| 15 | +2. On the next page, specify the OpenAPI URL to your JSON:API server, for example: `http://localhost:14140/swagger/v1/swagger.json`. |
| 16 | + Optionally provide a class name and namespace and click **Finish**. |
| 17 | + Visual Studio now downloads your swagger.json and updates your project file. This results in a pre-build step that generates the client code. |
36 | 18 |
|
| 19 | + Tip: To later re-download swagger.json and regenerate the client code, right-click **Dependencies** > **Manage Connected Services** and click the **Refresh** icon. |
| 20 | +3. Although not strictly required, we recommend to run package update now, which fixes some issues and removes the `Stream` parameter from generated calls. |
37 | 21 |
|
38 |
| -## Adding an OpenApiReference |
| 22 | +4. Add some demo code that calls one of your JSON:API endpoints. For example: |
39 | 23 |
|
40 |
| -Add a reference to your OpenAPI specification in your project file as demonstrated below. |
| 24 | + ```c# |
| 25 | + using var httpClient = new HttpClient(); |
| 26 | + var apiClient = new ExampleApiClient("http://localhost:14140", httpClient); |
41 | 27 |
|
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 |
| -``` |
| 28 | + PersonCollectionResponseDocument getResponse = |
| 29 | + await apiClient.GetPersonCollectionAsync(); |
52 | 30 |
|
| 31 | + foreach (PersonDataInResponse person in getResponse.Data) |
| 32 | + { |
| 33 | + Console.WriteLine($"Found user {person.Id} named " + |
| 34 | + $"'{person.Attributes.FirstName} {person.Attributes.LastName}'."); |
| 35 | + } |
| 36 | + ``` |
53 | 37 |
|
54 |
| -## Usage |
| 38 | +5. Add our client package to your project: |
55 | 39 |
|
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. |
| 40 | + ``` |
| 41 | + dotnet add package JsonApiDotNetCore.OpenApi.Client |
| 42 | + ``` |
58 | 43 |
|
59 |
| -```c# |
60 |
| -namespace JsonApiDotNetCoreExampleClient |
61 |
| -{ |
62 |
| - class Program |
63 |
| - { |
64 |
| - static void Main(string[] args) |
65 |
| - { |
66 |
| - using (HttpClient httpClient = new HttpClient()) |
67 |
| - { |
68 |
| - ExampleApiClient exampleApiClient = new ExampleApiClient(httpClient); |
| 44 | +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. |
69 | 45 |
|
70 |
| - // IntelliSense is now available on `exampleApiClient`! |
71 |
| - } |
72 |
| - } |
73 |
| - } |
74 |
| -} |
75 |
| -``` |
| 46 | + ```c# |
| 47 | + using JsonApiDotNetCore.OpenApi.Client; |
| 48 | + using Newtonsoft.Json; |
76 | 49 |
|
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 |
| 50 | + partial class ExampleApiClient : JsonApiClient |
84 | 51 | {
|
85 | 52 | partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings)
|
86 | 53 | {
|
87 | 54 | SetSerializerSettingsForJsonApi(settings);
|
88 | 55 | }
|
89 | 56 | }
|
90 |
| -} |
91 |
| -``` |
| 57 | + ``` |
92 | 58 |
|
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. |
| 59 | +7. Extend your demo code to send a partial PATCH request with the help of our package: |
94 | 60 |
|
95 |
| -```c# |
96 |
| -// Program.cs |
97 |
| -static void Main(string[] args) |
98 |
| -{ |
99 |
| - using (HttpClient httpClient = new HttpClient()) |
| 61 | + ```c# |
| 62 | + var patchRequest = new PersonPatchRequestDocument |
100 | 63 | {
|
101 |
| - ExampleApiClient exampleApiClient = new ExampleApiClient(httpClient); |
102 |
| - |
103 |
| - var requestDocument = new PersonPatchRequestDocument |
| 64 | + Data = new PersonDataInPatchRequest |
104 | 65 | {
|
105 |
| - Data = new PersonDataInPatchRequest |
| 66 | + Id = "1", |
| 67 | + Attributes = new PersonAttributesInPatchRequest |
106 | 68 | {
|
107 |
| - Id = "546", |
108 |
| - Type = PersonResourceType.People, |
109 |
| - Attributes = new PersonAttributesInPatchRequest |
110 |
| - { |
111 |
| - FirstName = "Jack" |
112 |
| - } |
| 69 | + FirstName = "Jack" |
113 | 70 | }
|
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 |
| - // } |
132 | 71 | }
|
| 72 | + }; |
133 | 73 |
|
| 74 | + // This line results in sending "lastName: null" instead of omitting it. |
| 75 | + using (apiClient.RegisterAttributesForRequestDocument<PersonPatchRequestDocument, |
| 76 | + PersonAttributesInPatchRequest>(patchRequest, person => person.LastName)) |
| 77 | + { |
| 78 | + PersonPrimaryResponseDocument patchResponse = |
| 79 | + await apiClient.PatchPersonAsync("1", patchRequest); |
| 80 | +
|
| 81 | + // The sent request looks like this: |
| 82 | + // { |
| 83 | + // "data": { |
| 84 | + // "type": "people", |
| 85 | + // "id": "1", |
| 86 | + // "attributes": { |
| 87 | + // "firstName": "Jack", |
| 88 | + // "lastName": null |
| 89 | + // } |
| 90 | + // } |
| 91 | + // } |
134 | 92 | }
|
135 |
| -} |
| 93 | + ``` |
| 94 | +
|
| 95 | +### Other IDEs |
| 96 | +
|
| 97 | +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). |
| 98 | +
|
| 99 | +Alternatively, the next section shows what to add to your client project file directly: |
| 100 | +
|
| 101 | +```xml |
| 102 | +<ItemGroup> |
| 103 | + <PackageReference Include="Microsoft.Extensions.ApiDescription.Client" Version="3.0.0"> |
| 104 | + <PrivateAssets>all</PrivateAssets> |
| 105 | + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
| 106 | + </PackageReference> |
| 107 | + <PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> |
| 108 | + <PackageReference Include="NSwag.ApiDescription.Client" Version="13.0.5"> |
| 109 | + <PrivateAssets>all</PrivateAssets> |
| 110 | + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
| 111 | + </PackageReference> |
| 112 | +</ItemGroup> |
| 113 | +
|
| 114 | +<ItemGroup> |
| 115 | + <OpenApiReference Include="OpenAPIs\swagger.json" CodeGenerator="NSwagCSharp" ClassName="ExampleApiClient"> |
| 116 | + <SourceUri>http://localhost:14140/swagger/v1/swagger.json</SourceUri> |
| 117 | + </OpenApiReference> |
| 118 | +</ItemGroup> |
136 | 119 | ```
|
137 | 120 |
|
| 121 | +From here, continue from step 3 in the list of steps for Visual Studio. |
| 122 | + |
| 123 | +## Configuration |
| 124 | + |
| 125 | +### NSwag |
| 126 | + |
| 127 | +The `OpenApiReference` element in the project file accepts an `Options` element to pass additional settings to the client generator, |
| 128 | +which are listed [here](https://github.com/RicoSuter/NSwag/blob/master/src/NSwag.Commands/Commands/CodeGeneration/OpenApiToCSharpClientCommand.cs). |
| 129 | + |
| 130 | +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): |
| 131 | + |
| 132 | +```xml |
| 133 | +<OpenApiReference Include="swagger.json"> |
| 134 | + <Namespace>ExampleProject.GeneratedCode</Namespace> |
| 135 | + <ClassName>SalesApiClient</ClassName> |
| 136 | + <CodeGenerator>NSwagCSharp</CodeGenerator> |
| 137 | + <Options>/UseBaseUrl:false /GenerateClientInterfaces:true</Options> |
| 138 | +</OpenApiReference> |
| 139 | +``` |
0 commit comments