Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,16 @@
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.OData" Version="10.0.0-preview.2" />
<PackageVersion Include="Microsoft.AspNetCore.OData.NewtonsoftJson" Version="8.2.0" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.ApiDescription.Server" Version="10.0.7" />
<PackageVersion Include="NetEscapades.AspNetCore.SecurityHeaders" Version="1.3.1" />
<PackageVersion Include="SimpleInjector.Integration.AspNetCore.Mvc.Core" Version="5.5.0" />
<PackageVersion Include="Microsoft.Identity.Web" Version="4.8.0" />
<PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="10.1.7" />
<PackageVersion Include="Swashbuckle.AspNetCore.Swagger" Version="10.1.7" />
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="10.1.7" />
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.7" />
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="10.1.7" />
<PackageVersion Include="Swashbuckle.AspNetCore.Newtonsoft" Version="10.1.7" />
<PackageVersion Include="Swashbuckle.AspNetCore.Filters" Version="10.0.1" />

Expand Down Expand Up @@ -157,4 +160,4 @@

</ItemGroup>

</Project>
</Project>
61 changes: 61 additions & 0 deletions docs/openapi/migration-from-swashbuckle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Migration from Swashbuckle to Microsoft OpenAPI

Ark.Tools now uses `Microsoft.AspNetCore.OpenApi` as the default generator in `ArkStartupWebApiCommon`.
Existing applications can keep the legacy Swashbuckle generator by overriding:

```csharp
public override bool UseSwashbuckleOpenApi => true;
```

Swagger UI and Redoc are still hosted by the API and read the same `/swagger/docs/{documentName}` specification route.
The route first serves build-generated JSON when present and falls back to runtime generation for development and tests.
Comment thread
AndreaCuneo marked this conversation as resolved.
Outdated

## Build-time generation

Application projects that use the Microsoft OpenAPI path should reference `Microsoft.Extensions.ApiDescription.Server`.
This enables OpenAPI JSON generation during normal `dotnet build`.

Recommended project settings:

```xml
<PropertyGroup>
<OpenApiDocumentsDirectory>$(OutputPath)</OpenApiDocumentsDirectory>
<OpenApiGenerateDocumentsOptions>--openapi-version OpenApi3_1</OpenApiGenerateDocumentsOptions>
</PropertyGroup>
```

Build-time generation starts the application entry point with a mock server.
Guard startup code that performs external side effects, migrations, outbound calls, or hosted background work during document generation.

## Swashbuckle attributes

Swashbuckle-specific attributes are not interpreted automatically by Microsoft OpenAPI.
Prefer ASP.NET Core metadata, XML comments, and Ark.Tools/Microsoft OpenAPI transformers for new code.

| Swashbuckle attribute | Recommended replacement | Notes |
| --- | --- | --- |
| `SwaggerOperationAttribute` | XML comments or endpoint metadata transformed by `IOpenApiOperationTransformer` | Use for summary, description, tags, and operation-specific metadata. |
Comment thread
AndreaCuneo marked this conversation as resolved.
Outdated
| `SwaggerResponseAttribute` | ASP.NET Core `ProducesResponseTypeAttribute` | Microsoft OpenAPI reads ApiExplorer response metadata. |
Comment thread
AndreaCuneo marked this conversation as resolved.
Outdated
| `SwaggerParameterAttribute` | XML comments, `FromQuery`/`FromRoute` metadata, or an operation transformer | Use native binding metadata for required and source information. |
| `SwaggerRequestBodyAttribute` | ASP.NET Core request metadata plus XML comments or an operation transformer | Prefer deterministic transformers for examples and descriptions. |
| `SwaggerSchemaAttribute` | System.Text.Json metadata, XML comments, or `IOpenApiSchemaTransformer` | Swashbuckle schema filters remain legacy-only. |
| `SwaggerTagAttribute` | Controller/action grouping metadata or document/operation transformers | Use generator-neutral tag metadata where possible. |
| `SwaggerSchemaFilterAttribute` | `IOpenApiSchemaTransformer` | Treat this as Swashbuckle-only during the transition. |

## Swashbuckle filters

| Swashbuckle extension point | Microsoft OpenAPI replacement |
| --- | --- |
| `IDocumentFilter` | `IOpenApiDocumentTransformer` |
| `IOperationFilter` | `IOpenApiOperationTransformer` |
| `ISchemaFilter` | `IOpenApiSchemaTransformer` |
| `IExamplesProvider<T>` from `Swashbuckle.AspNetCore.Filters` | Prefer a generator-neutral example model or an operation/schema transformer that writes examples deterministically. |

Current Ark.Tools defaults provide Microsoft OpenAPI equivalents for default problem responses, NodaTime schema formats, flags enum query parameters, OData media type cleanup, versioned document selection, and stable operation IDs.

## Compatibility guidance

- Use the Microsoft OpenAPI default for new applications.
- Override `UseSwashbuckleOpenApi` only when existing Swashbuckle filters or attributes cannot be migrated immediately.
- Keep Swagger UI and Redoc pointed at `/swagger/docs/{documentName}` so UI routes do not change while the generator changes.
- Move custom Swashbuckle filters to generator-neutral Ark.Tools options or Microsoft OpenAPI transformers before the .NET 12 removal window.
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,14 @@
"Microsoft.Spatial": "[9.0.0-preview.4, 10.0.0)"
}
},
"Microsoft.AspNetCore.OpenApi": {
"type": "Transitive",
"resolved": "10.0.7",
"contentHash": "vKiAcGXG0BwNVw3bOjjWRLnp9tR18dR7MiwpvC94h0yFS+zfnzGHzS/JmmgwUdRixrGxrlIMRAWrVc+2DfAGlg==",
"dependencies": {
"Microsoft.OpenApi": "2.0.0"
}
},
"Microsoft.AspNetCore.WebUtilities": {
"type": "Transitive",
"resolved": "2.1.1",
Expand Down Expand Up @@ -1944,6 +1952,11 @@
"Swashbuckle.AspNetCore.SwaggerGen": "10.0.0"
}
},
"Swashbuckle.AspNetCore.ReDoc": {
"type": "Transitive",
"resolved": "10.1.7",
"contentHash": "aoVMCDlS4v6X6EgaowPxSSDwdvIP0NQNJR42lq+iQCVFaGRCN67wALoAZYddyxGQeBoiKSdATvP+psj7OVCuPw=="
},
"Swashbuckle.AspNetCore.Swagger": {
"type": "Transitive",
"resolved": "10.1.7",
Expand Down Expand Up @@ -2224,6 +2237,7 @@
"FluentValidation": "[12.1.1, )",
"Hellang.Middleware.ProblemDetails": "[6.5.1, )",
"Microsoft.AspNetCore.OData": "[10.0.0-preview.2, )",
"Microsoft.AspNetCore.OpenApi": "[10.0.7, )",
"NetEscapades.AspNetCore.SecurityHeaders": "[1.3.1, )",
"SimpleInjector.Integration.AspNetCore.Mvc.Core": "[5.5.0, )"
}
Expand Down Expand Up @@ -2288,6 +2302,7 @@
"Microsoft.AspNetCore.OData": "[10.0.0-preview.2, )",
"Swashbuckle.AspNetCore.Annotations": "[10.1.7, )",
"Swashbuckle.AspNetCore.Filters": "[10.0.1, )",
"Swashbuckle.AspNetCore.ReDoc": "[10.1.7, )",
"Swashbuckle.AspNetCore.Swagger": "[10.1.7, )",
"Swashbuckle.AspNetCore.SwaggerGen": "[10.1.7, )",
"Swashbuckle.AspNetCore.SwaggerUI": "[10.1.7, )"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,14 @@
"Microsoft.Spatial": "[9.0.0-preview.4, 10.0.0)"
}
},
"Microsoft.AspNetCore.OpenApi": {
"type": "Transitive",
"resolved": "10.0.7",
"contentHash": "vKiAcGXG0BwNVw3bOjjWRLnp9tR18dR7MiwpvC94h0yFS+zfnzGHzS/JmmgwUdRixrGxrlIMRAWrVc+2DfAGlg==",
"dependencies": {
"Microsoft.OpenApi": "2.0.0"
}
},
"Microsoft.Azure.Amqp": {
"type": "Transitive",
"resolved": "2.7.0",
Expand Down Expand Up @@ -1138,6 +1146,11 @@
"Swashbuckle.AspNetCore.SwaggerGen": "10.0.0"
}
},
"Swashbuckle.AspNetCore.ReDoc": {
"type": "Transitive",
"resolved": "10.1.7",
"contentHash": "aoVMCDlS4v6X6EgaowPxSSDwdvIP0NQNJR42lq+iQCVFaGRCN67wALoAZYddyxGQeBoiKSdATvP+psj7OVCuPw=="
},
"Swashbuckle.AspNetCore.Swagger": {
"type": "Transitive",
"resolved": "10.1.7",
Expand Down Expand Up @@ -1392,6 +1405,7 @@
"FluentValidation": "[12.1.1, )",
"Hellang.Middleware.ProblemDetails": "[6.5.1, )",
"Microsoft.AspNetCore.OData": "[10.0.0-preview.2, )",
"Microsoft.AspNetCore.OpenApi": "[10.0.7, )",
"NetEscapades.AspNetCore.SecurityHeaders": "[1.3.1, )",
"SimpleInjector.Integration.AspNetCore.Mvc.Core": "[5.5.0, )"
}
Expand Down Expand Up @@ -1456,6 +1470,7 @@
"Microsoft.AspNetCore.OData": "[10.0.0-preview.2, )",
"Swashbuckle.AspNetCore.Annotations": "[10.1.7, )",
"Swashbuckle.AspNetCore.Filters": "[10.0.1, )",
"Swashbuckle.AspNetCore.ReDoc": "[10.1.7, )",
"Swashbuckle.AspNetCore.Swagger": "[10.1.7, )",
"Swashbuckle.AspNetCore.SwaggerGen": "[10.1.7, )",
"Swashbuckle.AspNetCore.SwaggerUI": "[10.1.7, )"
Expand Down
17 changes: 17 additions & 0 deletions samples/ProblemDetailsSample/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,7 @@
"FluentValidation": "[12.1.1, )",
"Hellang.Middleware.ProblemDetails": "[6.5.1, )",
"Microsoft.AspNetCore.OData": "[10.0.0-preview.2, )",
"Microsoft.AspNetCore.OpenApi": "[10.0.7, )",
"NetEscapades.AspNetCore.SecurityHeaders": "[1.3.1, )",
"SimpleInjector.Integration.AspNetCore.Mvc.Core": "[5.5.0, )"
}
Expand Down Expand Up @@ -643,6 +644,7 @@
"Microsoft.AspNetCore.OData": "[10.0.0-preview.2, )",
"Swashbuckle.AspNetCore.Annotations": "[10.1.7, )",
"Swashbuckle.AspNetCore.Filters": "[10.0.1, )",
"Swashbuckle.AspNetCore.ReDoc": "[10.1.7, )",
"Swashbuckle.AspNetCore.Swagger": "[10.1.7, )",
"Swashbuckle.AspNetCore.SwaggerGen": "[10.1.7, )",
"Swashbuckle.AspNetCore.SwaggerUI": "[10.1.7, )"
Expand Down Expand Up @@ -1056,6 +1058,15 @@
"Microsoft.Spatial": "[9.0.0-preview.4, 10.0.0)"
}
},
"Microsoft.AspNetCore.OpenApi": {
"type": "CentralTransitive",
"requested": "[10.0.7, )",
"resolved": "10.0.7",
"contentHash": "vKiAcGXG0BwNVw3bOjjWRLnp9tR18dR7MiwpvC94h0yFS+zfnzGHzS/JmmgwUdRixrGxrlIMRAWrVc+2DfAGlg==",
"dependencies": {
"Microsoft.OpenApi": "2.0.0"
}
},
"Microsoft.Data.SqlClient": {
"type": "CentralTransitive",
"requested": "[7.0.1, )",
Expand Down Expand Up @@ -1256,6 +1267,12 @@
"Swashbuckle.AspNetCore.SwaggerGen": "10.0.0"
}
},
"Swashbuckle.AspNetCore.ReDoc": {
"type": "CentralTransitive",
"requested": "[10.1.7, )",
"resolved": "10.1.7",
"contentHash": "aoVMCDlS4v6X6EgaowPxSSDwdvIP0NQNJR42lq+iQCVFaGRCN67wALoAZYddyxGQeBoiKSdATvP+psj7OVCuPw=="
},
"Swashbuckle.AspNetCore.Swagger": {
"type": "CentralTransitive",
"requested": "[10.1.7, )",
Expand Down
17 changes: 17 additions & 0 deletions samples/TestLinkGenerator/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,7 @@
"FluentValidation": "[12.1.1, )",
"Hellang.Middleware.ProblemDetails": "[6.5.1, )",
"Microsoft.AspNetCore.OData": "[10.0.0-preview.2, )",
"Microsoft.AspNetCore.OpenApi": "[10.0.7, )",
"NetEscapades.AspNetCore.SecurityHeaders": "[1.3.1, )",
"SimpleInjector.Integration.AspNetCore.Mvc.Core": "[5.5.0, )"
}
Expand Down Expand Up @@ -761,6 +762,7 @@
"Microsoft.AspNetCore.OData": "[10.0.0-preview.2, )",
"Swashbuckle.AspNetCore.Annotations": "[10.1.7, )",
"Swashbuckle.AspNetCore.Filters": "[10.0.1, )",
"Swashbuckle.AspNetCore.ReDoc": "[10.1.7, )",
"Swashbuckle.AspNetCore.Swagger": "[10.1.7, )",
"Swashbuckle.AspNetCore.SwaggerGen": "[10.1.7, )",
"Swashbuckle.AspNetCore.SwaggerUI": "[10.1.7, )"
Expand Down Expand Up @@ -1165,6 +1167,15 @@
"Microsoft.Spatial": "[9.0.0-preview.4, 10.0.0)"
}
},
"Microsoft.AspNetCore.OpenApi": {
"type": "CentralTransitive",
"requested": "[10.0.7, )",
"resolved": "10.0.7",
"contentHash": "vKiAcGXG0BwNVw3bOjjWRLnp9tR18dR7MiwpvC94h0yFS+zfnzGHzS/JmmgwUdRixrGxrlIMRAWrVc+2DfAGlg==",
"dependencies": {
"Microsoft.OpenApi": "2.0.0"
}
},
"Microsoft.Data.SqlClient": {
"type": "CentralTransitive",
"requested": "[7.0.1, )",
Expand Down Expand Up @@ -1365,6 +1376,12 @@
"Swashbuckle.AspNetCore.SwaggerGen": "10.0.0"
}
},
"Swashbuckle.AspNetCore.ReDoc": {
"type": "CentralTransitive",
"requested": "[10.1.7, )",
"resolved": "10.1.7",
"contentHash": "aoVMCDlS4v6X6EgaowPxSSDwdvIP0NQNJR42lq+iQCVFaGRCN67wALoAZYddyxGQeBoiKSdATvP+psj7OVCuPw=="
},
"Swashbuckle.AspNetCore.Swagger": {
"type": "CentralTransitive",
"requested": "[10.1.7, )",
Expand Down
9 changes: 8 additions & 1 deletion samples/WebApplicationDemo/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public Startup(IConfiguration configuration, IHostEnvironment env)

public override IEnumerable<ApiVersion> Versions => ApiVersions.All;

public override bool UseSwashbuckleOpenApi => HostEnvironment.IsEnvironment("IntegrationTests");

public override OpenApiInfo MakeInfo(ApiVersion version)
=> new()
{
Expand All @@ -48,6 +50,11 @@ public override void ConfigureServices(IServiceCollection services)
{
base.ConfigureServices(services);

foreach (var version in Versions)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be in base class and guarded based on the OpenApi enablement

{
services.AddOpenApi($"v{version.ToString("VVVV", CultureInfo.InvariantCulture)}");
}

var auth0Scheme = "Auth0";
var audience = "Audience";
var domain = "Domain";
Expand Down Expand Up @@ -178,4 +185,4 @@ protected override void RegisterContainer(IServiceProvider services)
var apiHost = new ApiHost(cfg)
.WithContainer(Container);
}
}
}
16 changes: 16 additions & 0 deletions samples/WebApplicationDemo/WebApplicationDemo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
<TargetFrameworks>net10.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<OpenApiGenerateDocumentsOptions>--openapi-version OpenApi3_1</OpenApiGenerateDocumentsOptions>
</PropertyGroup>

<ItemGroup>
<Content Include="UIHealthChecks.css" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
<PackageReference Include="Microsoft.Extensions.ApiDescription.Server" />
<PackageReference Include="Microsoft.Identity.Web" />
</ItemGroup>

Expand All @@ -26,4 +29,17 @@
<ProjectReference Include="../../src/common/Ark.Tools.Solid.SimpleInjector/Ark.Tools.Solid.SimpleInjector.csproj" />
<ProjectReference Include="../../src/common/Ark.Tools.Solid/Ark.Tools.Solid.csproj" />
</ItemGroup>

<Target Name="RemoveReferencedXmlCommentsFromOpenApi" AfterTargets="GenerateAdditionalXmlFilesForOpenApi">
<ItemGroup>
<AdditionalFiles Remove="@(AdditionalFiles)" Condition="'%(Extension)' == '.xml' and '%(Filename)' != 'WebApplicationDemo'" />
Comment thread
AndreaCuneo marked this conversation as resolved.
Outdated
</ItemGroup>
</Target>

<Target Name="CopyGeneratedOpenApiDocumentsToOutput" AfterTargets="GenerateOpenApiDocuments">
<ItemGroup>
<GeneratedOpenApiDocument Include="$(BaseIntermediateOutputPath)$(MSBuildProjectName)*.json" />
Comment thread
AndreaCuneo marked this conversation as resolved.
Outdated
</ItemGroup>
<Copy SourceFiles="@(GeneratedOpenApiDocument)" DestinationFolder="$(MSBuildProjectDirectory)/bin/$(Configuration)/$(TargetFramework)/" SkipUnchangedFiles="true" />
</Target>
</Project>
23 changes: 23 additions & 0 deletions samples/WebApplicationDemo/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@
"version": 2,
"dependencies": {
"net10.0": {
"Microsoft.AspNetCore.OpenApi": {
"type": "Direct",
"requested": "[10.0.7, )",
"resolved": "10.0.7",
"contentHash": "vKiAcGXG0BwNVw3bOjjWRLnp9tR18dR7MiwpvC94h0yFS+zfnzGHzS/JmmgwUdRixrGxrlIMRAWrVc+2DfAGlg==",
"dependencies": {
"Microsoft.OpenApi": "2.0.0"
}
},
"Microsoft.Extensions.ApiDescription.Server": {
"type": "Direct",
"requested": "[10.0.7, )",
"resolved": "10.0.7",
"contentHash": "VBNJzdInUkjhL/Habj16j3kMHIccjjuWg2zILcapH2QDq5GJF4bfQzJvt2gynuT98PkTpLUvPAsPeefKBDT/mg=="
},
"Microsoft.Identity.Web": {
"type": "Direct",
"requested": "[4.8.0, )",
Expand Down Expand Up @@ -720,6 +735,7 @@
"FluentValidation": "[12.1.1, )",
"Hellang.Middleware.ProblemDetails": "[6.5.1, )",
"Microsoft.AspNetCore.OData": "[10.0.0-preview.2, )",
"Microsoft.AspNetCore.OpenApi": "[10.0.7, )",
"NetEscapades.AspNetCore.SecurityHeaders": "[1.3.1, )",
"SimpleInjector.Integration.AspNetCore.Mvc.Core": "[5.5.0, )"
}
Expand Down Expand Up @@ -784,6 +800,7 @@
"Microsoft.AspNetCore.OData": "[10.0.0-preview.2, )",
"Swashbuckle.AspNetCore.Annotations": "[10.1.7, )",
"Swashbuckle.AspNetCore.Filters": "[10.0.1, )",
"Swashbuckle.AspNetCore.ReDoc": "[10.1.7, )",
"Swashbuckle.AspNetCore.Swagger": "[10.1.7, )",
"Swashbuckle.AspNetCore.SwaggerGen": "[10.1.7, )",
"Swashbuckle.AspNetCore.SwaggerUI": "[10.1.7, )"
Expand Down Expand Up @@ -1455,6 +1472,12 @@
"Swashbuckle.AspNetCore.SwaggerGen": "10.0.0"
}
},
"Swashbuckle.AspNetCore.ReDoc": {
"type": "CentralTransitive",
"requested": "[10.1.7, )",
"resolved": "10.1.7",
"contentHash": "aoVMCDlS4v6X6EgaowPxSSDwdvIP0NQNJR42lq+iQCVFaGRCN67wALoAZYddyxGQeBoiKSdATvP+psj7OVCuPw=="
},
"Swashbuckle.AspNetCore.Swagger": {
"type": "CentralTransitive",
"requested": "[10.1.7, )",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@
"Swashbuckle.AspNetCore.SwaggerGen": "10.0.0"
}
},
"Swashbuckle.AspNetCore.ReDoc": {
"type": "Direct",
"requested": "[10.1.7, )",
"resolved": "10.1.7",
"contentHash": "aoVMCDlS4v6X6EgaowPxSSDwdvIP0NQNJR42lq+iQCVFaGRCN67wALoAZYddyxGQeBoiKSdATvP+psj7OVCuPw=="
},
"Swashbuckle.AspNetCore.Swagger": {
"type": "Direct",
"requested": "[10.1.7, )",
Expand Down
Loading
Loading