Skip to content

Commit 1d06234

Browse files
CopilotMatDumoulinMathieu DumoulinGérald Barrégeraldbarre-workleap
authored
Upgrade to .NET 10 with Swashbuckle v10 (dropping .NET 8 support) (#103)
* Initial plan * Update project files for .NET 10 support and add conditional compilation for Swashbuckle v10 Co-authored-by: MatDumoulin <6119223+MatDumoulin@users.noreply.github.com> * Fix filter implementations for Swashbuckle v10 compatibility Co-authored-by: MatDumoulin <6119223+MatDumoulin@users.noreply.github.com> * Fix test project for .NET 10 compatibility Co-authored-by: MatDumoulin <6119223+MatDumoulin@users.noreply.github.com> * Remove .NET 8 support - target only .NET 10 - Changed all projects from multi-targeting (net8.0;net10.0) to single target (net10.0) - Removed conditional compilation directives (#if NET10_0_OR_GREATER) - Kept only .NET 10 / Swashbuckle v10 code paths - Updated all package references to use only .NET 10 versions - Removed deprecated WithOpenApi() calls (already done for .NET 10) - Maintained fully qualified type names for Forbidden/InternalServerError to avoid conflicts with framework types Co-authored-by: MatDumoulin <6119223+MatDumoulin@users.noreply.github.com> * Use framework's InternalServerError, keep custom Forbidden types - Removed custom InternalServerError and InternalServerError<T> types (now provided by .NET 10) - Kept custom Forbidden and Forbidden<T> types (not provided by framework) - Updated TypedResultsExtensions to only provide Forbidden methods - Updated HttpResultsStatusCodeTypeHelpers to use framework InternalServerError - Updated analyzer to reference framework InternalServerError - Updated tests to use TypedResults.InternalServerError() from framework - Updated PublicAPI.Unshipped.txt to remove InternalServerError entries Co-authored-by: MatDumoulin <6119223+MatDumoulin@users.noreply.github.com> * Finishing to update to .Net 10 * Fixing compilation warnings * Fix for spec * wip * address comments --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: MatDumoulin <6119223+MatDumoulin@users.noreply.github.com> Co-authored-by: Mathieu Dumoulin <mathieu.dumoulin@workleap.com> Co-authored-by: Gérald Barré <gerald.barre@gsoft.com> Co-authored-by: Gérald Barré <gerald.barre@workleap.com>
1 parent 8152b9c commit 1d06234

26 files changed

+139
-284
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*.user
1010
*.userosscache
1111
*.sln.docstates
12+
.claude
1213

1314
# User-specific files (MonoDevelop/Xamarin Studio)
1415
*.userprefs

global.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"sdk": {
3-
"version": "9.0.311",
3+
"version": "10.0.103",
44
"rollForward": "latestMinor",
55
"allowPrerelease": false
66
}

src/Shared/HttpResultsStatusCodeTypeHelpers.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,8 @@ internal static class HttpResultsStatusCodeTypeHelpers
2424
{ "Microsoft.AspNetCore.Http.HttpResults.Conflict`1", 409 },
2525
{ "Microsoft.AspNetCore.Http.HttpResults.UnprocessableEntity", 422 },
2626
{ "Microsoft.AspNetCore.Http.HttpResults.UnprocessableEntity`1", 422 },
27-
// Will be Supported in .NET 9
2827
{ "Microsoft.AspNetCore.Http.HttpResults.InternalServerError", 500 },
2928
{ "Microsoft.AspNetCore.Http.HttpResults.InternalServerError`1", 500 },
30-
// Workleap's definition of the InternalServerError type result for other .NET versions
31-
{ "Workleap.Extensions.OpenAPI.TypedResult.InternalServerError", 500 },
32-
{ "Workleap.Extensions.OpenAPI.TypedResult.InternalServerError`1", 500 },
3329
};
3430

3531
// Using the same descriptions from the Swashbuckle library: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/fca056a330a8ddb5173eaa6ad912b77fd0cf0b39/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs#L1027

src/Workleap.Extensions.OpenAPI.Analyzers/CompareTypedResultWithAnnotationAnalyzer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ private static Dictionary<int, ITypeSymbol> InitializeStatusCodeMapHttpResultMap
8787
Add(404, "Microsoft.AspNetCore.Http.HttpResults.NotFound");
8888
Add(409, "Microsoft.AspNetCore.Http.HttpResults.Conflict");
8989
Add(422, "Microsoft.AspNetCore.Http.HttpResults.UnprocessableEntity");
90-
Add(500, "Workleap.Extensions.OpenAPI.TypedResult.InternalServerError");
90+
Add(500, "Microsoft.AspNetCore.Http.HttpResults.InternalServerError");
9191

9292
return dictionary;
9393

src/Workleap.Extensions.OpenAPI.Tests/Workleap.Extensions.OpenAPI.Tests.csproj

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
3+
<TargetFramework>net10.0</TargetFramework>
44
<IsPackable>false</IsPackable>
55
<IsTestProject>true</IsTestProject>
66
<SignAssembly>true</SignAssembly>
@@ -15,14 +15,13 @@
1515
<ItemGroup>
1616
<PackageReference Include="Meziantou.Extensions.Logging.Xunit" Version="1.0.23" />
1717
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
18-
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="9.0.6" />
18+
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="10.1.2" />
1919
<PackageReference Include="xunit" Version="2.9.3" />
2020
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
2121
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2222
<PrivateAssets>all</PrivateAssets>
2323
</PackageReference>
24-
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.24" Condition="'$(TargetFramework)' == 'net8.0'" />
25-
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.13" Condition="'$(TargetFramework)' == 'net9.0'" />
24+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.3" />
2625
</ItemGroup>
2726

2827
<ItemGroup>

src/Workleap.Extensions.OpenAPI.sln

Lines changed: 0 additions & 62 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Solution>
2+
<Folder Name="/files/">
3+
<File Path="Directory.Build.props" />
4+
</Folder>
5+
<Folder Name="/tests/">
6+
<File Path="tests/expected-openapi-document.yaml" />
7+
<File Path="tests/RunSystemTest.ps1" />
8+
<Project Path="tests/WebApi.OpenAPI.SystemTest/WebApi.OpenAPI.SystemTest.csproj" />
9+
<Project Path="tests/Workleap.Extensions.OpenAPI.Analyzers.Tests/Workleap.Extensions.OpenAPI.Analyzers.Tests.csproj" />
10+
<Project Path="Workleap.Extensions.OpenAPI.Tests/Workleap.Extensions.OpenAPI.Tests.csproj" />
11+
</Folder>
12+
<Project Path="Workleap.Extensions.OpenAPI.Analyzers/Workleap.Extensions.OpenAPI.Analyzers.csproj" />
13+
<Project Path="Workleap.Extensions.OpenAPI/Workleap.Extensions.OpenAPI.csproj" />
14+
</Solution>

src/Workleap.Extensions.OpenAPI/OperationId/FallbackOperationIdToMethodNameFilter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using Microsoft.AspNetCore.Mvc;
2-
using Microsoft.OpenApi.Models;
2+
using Microsoft.OpenApi;
33
using Swashbuckle.AspNetCore.SwaggerGen;
44

55
namespace Workleap.Extensions.OpenAPI.OperationId;

src/Workleap.Extensions.OpenAPI/Ordering/OrderResponseFilter.cs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Microsoft.OpenApi.Models;
1+
using Microsoft.OpenApi;
22
using Swashbuckle.AspNetCore.SwaggerGen;
33

44
namespace Workleap.Extensions.OpenAPI.Ordering;
@@ -8,31 +8,62 @@ namespace Workleap.Extensions.OpenAPI.Ordering;
88
/// </summary>
99
internal sealed class OrderResponseFilter : IDocumentFilter
1010
{
11+
// In Microsoft.OpenApi v2, operation keys are no longer the OperationType enum (where we could cast to int),
12+
// they are objects with a string Method property. This dictionary preserves explicit ordering.
13+
private static readonly Dictionary<string, int> HttpMethodOrder = new(StringComparer.OrdinalIgnoreCase)
14+
{
15+
{ "GET", 0 },
16+
{ "POST", 1 },
17+
{ "PUT", 2 },
18+
{ "PATCH", 3 },
19+
{ "DELETE", 4 },
20+
{ "OPTIONS", 5 },
21+
{ "HEAD", 6 },
22+
{ "TRACE", 7 }
23+
};
24+
1125
public void Apply(OpenApiDocument document, DocumentFilterContext context)
1226
{
27+
if (document.Paths == null)
28+
{
29+
return;
30+
}
31+
1332
var paths = document.Paths.ToList();
1433
document.Paths.Clear();
1534
document.Paths = new OpenApiPaths();
1635
foreach (var path in paths)
1736
{
1837
document.Paths.Add(path.Key, path.Value);
1938

20-
var sortedOperations = path.Value.Operations.OrderBy(op => (int)op.Key).ToList();
21-
path.Value.Operations.Clear();
39+
if (path.Value is not OpenApiPathItem pathItem || pathItem.Operations == null)
40+
{
41+
continue;
42+
}
43+
44+
var sortedOperations = pathItem.Operations
45+
.OrderBy(op => HttpMethodOrder.TryGetValue(op.Key.Method, out var order) ? order : 99)
46+
.ToList();
47+
pathItem.Operations.Clear();
2248
foreach (var operation in sortedOperations)
2349
{
24-
path.Value.Operations.Add(operation.Key, operation.Value);
50+
pathItem.Operations.Add(operation.Key, operation.Value);
51+
52+
if (operation.Value is not OpenApiOperation openApiOperation || openApiOperation.Responses == null)
53+
{
54+
continue;
55+
}
2556

2657
// Sort responses by status code (200, 400, 403, 404, 500, etc.)
2758
// This is critical because responses from both controller-level ProducesResponseType
2859
// and method-level attributes are added in the order they're processed, not by status code.
2960
// Without sorting, a 403 from a controller-level attribute might appear before a 200
3061
// from the method-level TypedResult, causing unpredictable ordering and noisy diffs.
31-
var sortedResponse = operation.Value.Responses.OrderBy(responseKvp => responseKvp.Key, StringComparer.Ordinal).ToList();
32-
operation.Value.Responses.Clear();
62+
var sortedResponse = openApiOperation.Responses.OrderBy(responseKvp => responseKvp.Key, StringComparer.Ordinal).ToList();
63+
openApiOperation.Responses.Clear();
3364
foreach (var response in sortedResponse)
3465
{
35-
operation.Value.Responses.Add(response.Key, response.Value);
66+
openApiOperation.Responses.Add(response.Key, response.Value);
3667
}
3768
}
3869
}

src/Workleap.Extensions.OpenAPI/PublicAPI.Unshipped.txt

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ static Workleap.Extensions.OpenAPI.OpenApiServiceCollectionExtensions.ConfigureO
33
static Workleap.Extensions.OpenAPI.OpenApiServiceCollectionExtensions.ConfigureStandardJsonSerializerOptions(this Microsoft.Extensions.DependencyInjection.IMvcBuilder! mvcBuilder) -> Microsoft.Extensions.DependencyInjection.IMvcBuilder!
44
static Workleap.Extensions.OpenAPI.TypedResult.TypedResultsExtensions.Forbidden() -> Workleap.Extensions.OpenAPI.TypedResult.Forbidden!
55
static Workleap.Extensions.OpenAPI.TypedResult.TypedResultsExtensions.Forbidden<T>(T? error) -> Workleap.Extensions.OpenAPI.TypedResult.Forbidden<T>!
6-
static Workleap.Extensions.OpenAPI.TypedResult.TypedResultsExtensions.InternalServerError() -> Workleap.Extensions.OpenAPI.TypedResult.InternalServerError!
7-
static Workleap.Extensions.OpenAPI.TypedResult.TypedResultsExtensions.InternalServerError<T>(T? error) -> Workleap.Extensions.OpenAPI.TypedResult.InternalServerError<T>!
86
Workleap.Extensions.OpenAPI.Builder.JsonSerializerDifferenceException
97
Workleap.Extensions.OpenAPI.Builder.JsonSerializerDifferenceException.JsonSerializerDifferenceException(string! propertyName) -> void
108
Workleap.Extensions.OpenAPI.Builder.OpenApiBuilder
@@ -16,9 +14,4 @@ Workleap.Extensions.OpenAPI.TypedResult.Forbidden.ExecuteAsync(Microsoft.AspNetC
1614
Workleap.Extensions.OpenAPI.TypedResult.Forbidden<TValue>
1715
Workleap.Extensions.OpenAPI.TypedResult.Forbidden<TValue>.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
1816
Workleap.Extensions.OpenAPI.TypedResult.Forbidden<TValue>.Value.get -> TValue?
19-
Workleap.Extensions.OpenAPI.TypedResult.InternalServerError
20-
Workleap.Extensions.OpenAPI.TypedResult.InternalServerError.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
21-
Workleap.Extensions.OpenAPI.TypedResult.InternalServerError<TValue>
22-
Workleap.Extensions.OpenAPI.TypedResult.InternalServerError<TValue>.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
23-
Workleap.Extensions.OpenAPI.TypedResult.InternalServerError<TValue>.Value.get -> TValue?
2417
Workleap.Extensions.OpenAPI.TypedResult.TypedResultsExtensions

0 commit comments

Comments
 (0)