Skip to content

Commit

Permalink
Move existing integration to JADNC (#1076)
Browse files Browse the repository at this point in the history
* Copied integration and fixed compilation errors

* Add models and adjusted models in OpenAPI document tests such that i reflect features that were covered in the private repository

* Add FlightAttendantController to used controllers

* Added client library with tests to project; made adjustmets to the models to make the client test pass without introducing any structural adjustments to the test codes

* Adjusted build script to create OpenApiClient artifact, added documentation, minor model adjustments

* Adjusted docs and minor refactors

* Replace resource name formatter with internal one

* Removed JsonApiInputFormatter workaround

* Moved full document test to ExistingOpenApiIntegration test folder, which is to be removed at the very end once have dedicated test suites for all components

* Disabled OpenAPI for nonjson and operations controller, as these are not yet supported and will cause the integration to crash

* review feedback on docs

* Add a very minimal example of a generated OpenAPI client for JsonApiDotNetCoreExample project. The only call that currently doesn't crash is the one associated to deleting a primary resource

* Fixed inspect and cleanup code issues that do not appear when executed through Rider

* rename existing to legacy in ExistingOpenApiIntegration folder and namespace

* moved OpenApiClient to OpenApi.Client

* Configure NSwag to generate a client with access modifier set to internal. Simplified ExampleApiClient to not use a generated interface

* Move OpenAPI client tests to dedicated test project, cleared up some unrequired internalsVisibleTo usage

* Some adjustments to stay closer to 1-on-1 model mapping from the private repository of the legacy integration. Also processed some of the review feedback

* review feedback

* Process inspectcode issues that do not appear when run locally

* Update package reference in build script

* Changed models to stay closer 1-to-1 mapping. Changed 'OpenApiClient' to 'OpenApi.Client'. Removed unneeded config from legacy integration startup.

* process review feedback

* rename ClassName in client test project

* Updated example client project using Visual Studio defaults and updated documentation

* Layout tweaks and fixes

* fixed failed automatic rename 'openApiOpenApi'

* reduced changed wrt private version of this test

* Changed ID type from  to  for Flight

* ReserveCabinPersonnel -> Purser, CabinPersonnel -> CabinCrewMembers

* fixed failed automatic rename 'openApiOpenApi'

* Flight: replaced OperatingAirplane with Purser, added to many relationship to new model Passenger

* Review feedback

* Fix client lifetime test

* Feedback

Co-authored-by: Bart Koelman <bart@degreed.com>
  • Loading branch information
maurei and Bart Koelman authored Sep 17, 2021
1 parent 9209642 commit 362bf5f
Show file tree
Hide file tree
Showing 109 changed files with 11,189 additions and 1,078 deletions.
2 changes: 2 additions & 0 deletions Build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,12 @@ function CreateNuGetPackage {
if ([string]::IsNullOrWhitespace($versionSuffix)) {
dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts
dotnet pack .\src\JsonApiDotNetCore.OpenApi -c Release -o .\artifacts
dotnet pack .\src\JsonApiDotNetCore.OpenApi.Client -c Release -o .\artifacts
}
else {
dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=$versionSuffix
dotnet pack .\src\JsonApiDotNetCore.OpenApi -c Release -o .\artifacts --version-suffix=$versionSuffix
dotnet pack .\src\JsonApiDotNetCore.OpenApi.Client -c Release -o .\artifacts --version-suffix=$versionSuffix
}

CheckLastExitCode
Expand Down
45 changes: 45 additions & 0 deletions JsonApiDotNetCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonApiDotNetCore.OpenApi",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenApiTests", "test\OpenApiTests\OpenApiTests.csproj", "{B693DE14-BB28-496F-AB39-B4E674ABCA80}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonApiDotNetCore.OpenApi.Client", "src\JsonApiDotNetCore.OpenApi.Client\JsonApiDotNetCore.OpenApi.Client.csproj", "{5ADAA902-5A75-4ECB-B4B4-03291D63CE9C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonApiDotNetCoreExampleClient", "src\Examples\JsonApiDotNetCoreExampleClient\JsonApiDotNetCoreExampleClient.csproj", "{7FC5DFA3-6F66-4FD8-820D-81E93856F252}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenApiClientTests", "test\OpenApiClientTests\OpenApiClientTests.csproj", "{77F98215-3085-422E-B99D-4C404C2114CF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -238,6 +244,42 @@ Global
{B693DE14-BB28-496F-AB39-B4E674ABCA80}.Release|x64.Build.0 = Release|Any CPU
{B693DE14-BB28-496F-AB39-B4E674ABCA80}.Release|x86.ActiveCfg = Release|Any CPU
{B693DE14-BB28-496F-AB39-B4E674ABCA80}.Release|x86.Build.0 = Release|Any CPU
{5ADAA902-5A75-4ECB-B4B4-03291D63CE9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5ADAA902-5A75-4ECB-B4B4-03291D63CE9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5ADAA902-5A75-4ECB-B4B4-03291D63CE9C}.Debug|x64.ActiveCfg = Debug|Any CPU
{5ADAA902-5A75-4ECB-B4B4-03291D63CE9C}.Debug|x64.Build.0 = Debug|Any CPU
{5ADAA902-5A75-4ECB-B4B4-03291D63CE9C}.Debug|x86.ActiveCfg = Debug|Any CPU
{5ADAA902-5A75-4ECB-B4B4-03291D63CE9C}.Debug|x86.Build.0 = Debug|Any CPU
{5ADAA902-5A75-4ECB-B4B4-03291D63CE9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5ADAA902-5A75-4ECB-B4B4-03291D63CE9C}.Release|Any CPU.Build.0 = Release|Any CPU
{5ADAA902-5A75-4ECB-B4B4-03291D63CE9C}.Release|x64.ActiveCfg = Release|Any CPU
{5ADAA902-5A75-4ECB-B4B4-03291D63CE9C}.Release|x64.Build.0 = Release|Any CPU
{5ADAA902-5A75-4ECB-B4B4-03291D63CE9C}.Release|x86.ActiveCfg = Release|Any CPU
{5ADAA902-5A75-4ECB-B4B4-03291D63CE9C}.Release|x86.Build.0 = Release|Any CPU
{7FC5DFA3-6F66-4FD8-820D-81E93856F252}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7FC5DFA3-6F66-4FD8-820D-81E93856F252}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7FC5DFA3-6F66-4FD8-820D-81E93856F252}.Debug|x64.ActiveCfg = Debug|Any CPU
{7FC5DFA3-6F66-4FD8-820D-81E93856F252}.Debug|x64.Build.0 = Debug|Any CPU
{7FC5DFA3-6F66-4FD8-820D-81E93856F252}.Debug|x86.ActiveCfg = Debug|Any CPU
{7FC5DFA3-6F66-4FD8-820D-81E93856F252}.Debug|x86.Build.0 = Debug|Any CPU
{7FC5DFA3-6F66-4FD8-820D-81E93856F252}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7FC5DFA3-6F66-4FD8-820D-81E93856F252}.Release|Any CPU.Build.0 = Release|Any CPU
{7FC5DFA3-6F66-4FD8-820D-81E93856F252}.Release|x64.ActiveCfg = Release|Any CPU
{7FC5DFA3-6F66-4FD8-820D-81E93856F252}.Release|x64.Build.0 = Release|Any CPU
{7FC5DFA3-6F66-4FD8-820D-81E93856F252}.Release|x86.ActiveCfg = Release|Any CPU
{7FC5DFA3-6F66-4FD8-820D-81E93856F252}.Release|x86.Build.0 = Release|Any CPU
{77F98215-3085-422E-B99D-4C404C2114CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{77F98215-3085-422E-B99D-4C404C2114CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{77F98215-3085-422E-B99D-4C404C2114CF}.Debug|x64.ActiveCfg = Debug|Any CPU
{77F98215-3085-422E-B99D-4C404C2114CF}.Debug|x64.Build.0 = Debug|Any CPU
{77F98215-3085-422E-B99D-4C404C2114CF}.Debug|x86.ActiveCfg = Debug|Any CPU
{77F98215-3085-422E-B99D-4C404C2114CF}.Debug|x86.Build.0 = Debug|Any CPU
{77F98215-3085-422E-B99D-4C404C2114CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{77F98215-3085-422E-B99D-4C404C2114CF}.Release|Any CPU.Build.0 = Release|Any CPU
{77F98215-3085-422E-B99D-4C404C2114CF}.Release|x64.ActiveCfg = Release|Any CPU
{77F98215-3085-422E-B99D-4C404C2114CF}.Release|x64.Build.0 = Release|Any CPU
{77F98215-3085-422E-B99D-4C404C2114CF}.Release|x86.ActiveCfg = Release|Any CPU
{77F98215-3085-422E-B99D-4C404C2114CF}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -258,6 +300,9 @@ Global
{210FD61E-FF5D-4CEE-8E0D-C739ECCCBA21} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
{71287D6F-6C3B-44B4-9FCA-E78FE3F02289} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF}
{B693DE14-BB28-496F-AB39-B4E674ABCA80} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
{5ADAA902-5A75-4ECB-B4B4-03291D63CE9C} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF}
{7FC5DFA3-6F66-4FD8-820D-81E93856F252} = {026FBC6C-AF76-4568-9B87-EC73457899FD}
{77F98215-3085-422E-B99D-4C404C2114CF} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A2421882-8F0A-4905-928F-B550B192F9A4}
Expand Down
139 changes: 139 additions & 0 deletions docs/usage/openapi-client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# OpenAPI Client

You can generate a JSON:API client in various programming languages from the [OpenAPI specification](https://swagger.io/specification/) file that JsonApiDotNetCore APIs provide.

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".

## Getting started

### Visual Studio

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.

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`.
Optionally provide a class name and namespace and click **Finish**.
Visual Studio now downloads your swagger.json and updates your project file. This results in 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. Although not strictly required, we recommend to run package update now, which fixes some issues and removes the `Stream` parameter from generated calls.

4. Add some demo code that calls one of your JSON:API endpoints. For example:

```c#
using var httpClient = new HttpClient();
var apiClient = new ExampleApiClient("http://localhost:14140", httpClient);

PersonCollectionResponseDocument getResponse =
await apiClient.GetPersonCollectionAsync();

foreach (PersonDataInResponse person in getResponse.Data)
{
Console.WriteLine($"Found user {person.Id} named " +
$"'{person.Attributes.FirstName} {person.Attributes.LastName}'.");
}
```

5. Add our client package to your project:

```
dotnet add package JsonApiDotNetCore.OpenApi.Client
```

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.

```c#
using JsonApiDotNetCore.OpenApi.Client;
using Newtonsoft.Json;

partial class ExampleApiClient : JsonApiClient
{
partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings)
{
SetSerializerSettingsForJsonApi(settings);
}
}
```

7. Extend your demo code to send a partial PATCH request with the help of our package:

```c#
var patchRequest = new PersonPatchRequestDocument
{
Data = new PersonDataInPatchRequest
{
Id = "1",
Attributes = new PersonAttributesInPatchRequest
{
FirstName = "Jack"
}
}
};

// This line results in sending "lastName: null" instead of omitting it.
using (apiClient.RegisterAttributesForRequestDocument<PersonPatchRequestDocument,
PersonAttributesInPatchRequest>(patchRequest, person => person.LastName))
{
PersonPrimaryResponseDocument patchResponse =
await apiClient.PatchPersonAsync("1", patchRequest);

// The sent request looks like this:
// {
// "data": {
// "type": "people",
// "id": "1",
// "attributes": {
// "firstName": "Jack",
// "lastName": null
// }
// }
// }
}
```

### Other IDEs

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).
Alternatively, the next section shows what to add to your client project file directly:

```xml
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.ApiDescription.Client" Version="3.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="NSwag.ApiDescription.Client" Version="13.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<OpenApiReference Include="OpenAPIs\swagger.json" CodeGenerator="NSwagCSharp" ClassName="ExampleApiClient">
<SourceUri>http://localhost:14140/swagger/v1/swagger.json</SourceUri>
</OpenApiReference>
</ItemGroup>
```

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

## Configuration

### NSwag

The `OpenApiReference` element in the project file accepts an `Options` element to pass additional settings to the client generator,
which are listed [here](https://github.com/RicoSuter/NSwag/blob/master/src/NSwag.Commands/Commands/CodeGeneration/OpenApiToCSharpClientCommand.cs).
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):

```xml
<OpenApiReference Include="swagger.json">
<Namespace>ExampleProject.GeneratedCode</Namespace>
<ClassName>SalesApiClient</ClassName>
<CodeGenerator>NSwagCSharp</CodeGenerator>
<Options>/UseBaseUrl:false /GenerateClientInterfaces:true</Options>
</OpenApiReference>
```
73 changes: 29 additions & 44 deletions docs/usage/openapi.md
Original file line number Diff line number Diff line change
@@ -1,64 +1,49 @@
# OpenAPI

You can describe your API with an OpenAPI specification using the [Swashbuckle](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) integration for JsonApiDotNetCore.
JsonApiDotNetCore provides an extension package that enables you to produce an [OpenAPI specification](https://swagger.io/specification/) for your JSON:API endpoints. This can be used to generate a [documentation website](https://swagger.io/tools/swagger-ui/) or to generate [client libraries](https://openapi-generator.tech/docs/generators/) in various languages. The package provides an integration with [Swashbuckle](https://github.com/domaindrivendev/Swashbuckle.AspNetCore).

## Installation

Install the `JsonApiDotNetCore.OpenApi` NuGet package.
## Getting started

### CLI
1. Install the `JsonApiDotNetCore.OpenApi` NuGet package:

```
dotnet add package JsonApiDotNetCore.OpenApi
```

### Visual Studio

```powershell
Install-Package JsonApiDotNetCore.OpenApi
```

### *.csproj

```xml
<ItemGroup>
<!-- Be sure to check NuGet for the latest version # -->
<PackageReference Include="JsonApiDotNetCore.OpenApi" Version="4.0.0" />
</ItemGroup>
```

## Usage
```
dotnet add package JsonApiDotNetCore.OpenApi
```

Add the integration in your `Startup` class.
2. Add the integration in your `Startup` class.

```c#
public class Startup
{
public void ConfigureServices(IServiceCollection services)
```c#
public class Startup
{
IMvcCoreBuilder mvcBuilder = services.AddMvcCore();
services.AddJsonApi<AppDbContext>(mvcBuilder: mvcBuilder);
public void ConfigureServices(IServiceCollection services)
{
IMvcCoreBuilder mvcBuilder = services.AddMvcCore();

// Adds the Swashbuckle integration.
services.AddOpenApi(mvcBuilder);
}
services.AddJsonApi<AppDbContext>(mvcBuilder: mvcBuilder);

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseJsonApi();
// Adds the Swashbuckle integration.
services.AddOpenApi(mvcBuilder);
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseJsonApi();

// Adds the Swashbuckle middleware.
app.UseSwagger();
// Adds the Swashbuckle middleware.
app.UseSwagger();

app.UseEndpoints(endpoints => endpoints.MapControllers());
app.UseEndpoints(endpoints => endpoints.MapControllers());
}
}
}
```
```

By default, the OpenAPI specification will be available at `http://localhost:<port>/swagger/v1/swagger.json`.
Swashbuckle also ships with [SwaggerUI](https://swagger.io/tools/swagger-ui/), tooling for a generated documentation page. This can be enabled by installing the `Swashbuckle.AspNetCore.SwaggerUI` NuGet package and adding the following to your `Startup` class.
## Documentation

Swashbuckle also ships with [SwaggerUI](https://swagger.io/tools/swagger-ui/), tooling for a generated documentation page. This can be enabled by installing the `Swashbuckle.AspNetCore.SwaggerUI` NuGet package and adding the following to your `Startup` class:
```c#
// Startup.cs
Expand Down
2 changes: 2 additions & 0 deletions docs/usage/toc.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
# [Errors](errors.md)
# [Metadata](meta.md)
# [Caching](caching.md)

# [OpenAPI](openapi.md)
## [Client](openapi-client.md)

# Extensibility
## [Layer Overview](extensibility/layer-overview.md)
Expand Down
15 changes: 15 additions & 0 deletions src/Examples/JsonApiDotNetCoreExampleClient/ExampleApiClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using JsonApiDotNetCore.OpenApi.Client;
using Newtonsoft.Json;

namespace JsonApiDotNetCoreExampleClient
{
public partial class ExampleApiClient : JsonApiClient
{
partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings)
{
SetSerializerSettingsForJsonApi(settings);

settings.Formatting = Formatting.Indented;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$(NetCoreAppVersion)</TargetFramework>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\JsonApiDotNetCore.OpenApi.Client\JsonApiDotNetCore.OpenApi.Client.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.ApiDescription.Client" Version="5.0.10">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NSwag.ApiDescription.Client" Version="13.13.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<OpenApiReference Include="OpenAPIs\swagger.json" CodeGenerator="NSwagCSharp" ClassName="ExampleApiClient">
<SourceUri>http://localhost:14140/swagger/v1/swagger.json</SourceUri>
</OpenApiReference>
</ItemGroup>
</Project>
Loading

0 comments on commit 362bf5f

Please sign in to comment.