Skip to content

Commit 71ba7a1

Browse files
bt-Knodelclaude
andcommitted
Use IAsyncSwaggerProvider in CLI to support async filters
The CLI's `tofile` command previously resolved only `ISwaggerProvider`, which uses the synchronous `GenerateOperation` path that skips `IOperationAsyncFilter` registrations. This meant async operation filters had no effect on CLI-generated swagger documents. When available, the CLI now prefers `IAsyncSwaggerProvider.GetSwaggerAsync` which invokes both async and sync filters, matching the runtime middleware behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d22870e commit 71ba7a1

File tree

4 files changed

+45
-7
lines changed

4 files changed

+45
-7
lines changed

src/Swashbuckle.AspNetCore.Cli/Program.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,14 @@ public static int Main(string[] args)
6262

6363
c.OnRun((namedArgs) =>
6464
{
65-
SetupAndRetrieveSwaggerProviderAndOptions(namedArgs, out var swaggerProvider, out var swaggerOptions);
65+
SetupAndRetrieveSwaggerProviderAndOptions(namedArgs, out var asyncSwaggerProvider, out var swaggerProvider, out var swaggerOptions);
6666
var swaggerDocumentSerializer = swaggerOptions?.Value?.CustomDocumentSerializer;
67-
var swagger = swaggerProvider.GetSwagger(
68-
namedArgs["swaggerdoc"],
69-
namedArgs.TryGetValue("--host", out var arg) ? arg : null,
70-
namedArgs.TryGetValue("--basepath", out var namedArg) ? namedArg : null);
67+
68+
var host = namedArgs.TryGetValue("--host", out var arg) ? arg : null;
69+
var basePath = namedArgs.TryGetValue("--basepath", out var namedArg) ? namedArg : null;
70+
var swagger = asyncSwaggerProvider != null
71+
? asyncSwaggerProvider.GetSwaggerAsync(namedArgs["swaggerdoc"], host, basePath).GetAwaiter().GetResult()
72+
: swaggerProvider.GetSwagger(namedArgs["swaggerdoc"], host, basePath);
7173

7274
// 4) Serialize to specified output location or stdout
7375
var outputPath = namedArgs.TryGetValue("--output", out var arg1)
@@ -150,7 +152,7 @@ public static int Main(string[] args)
150152
c.Option("--output", "");
151153
c.OnRun((namedArgs) =>
152154
{
153-
SetupAndRetrieveSwaggerProviderAndOptions(namedArgs, out var swaggerProvider, out var swaggerOptions);
155+
SetupAndRetrieveSwaggerProviderAndOptions(namedArgs, out _, out var swaggerProvider, out var swaggerOptions);
154156
IList<string> docNames = [];
155157

156158
string outputPath = namedArgs.TryGetValue("--output", out var arg1)
@@ -189,7 +191,7 @@ public static int Main(string[] args)
189191
return runner.Run(args);
190192
}
191193

192-
private static void SetupAndRetrieveSwaggerProviderAndOptions(IDictionary<string, string> namedArgs, out ISwaggerProvider swaggerProvider, out IOptions<SwaggerOptions> swaggerOptions)
194+
private static void SetupAndRetrieveSwaggerProviderAndOptions(IDictionary<string, string> namedArgs, out IAsyncSwaggerProvider asyncSwaggerProvider, out ISwaggerProvider swaggerProvider, out IOptions<SwaggerOptions> swaggerOptions)
193195
{
194196
// 1) Configure host with provided startupassembly
195197
var startupAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(
@@ -199,6 +201,7 @@ private static void SetupAndRetrieveSwaggerProviderAndOptions(IDictionary<string
199201
var serviceProvider = GetServiceProvider(startupAssembly);
200202

201203
// 3) Retrieve Swagger via configured provider
204+
asyncSwaggerProvider = serviceProvider.GetService<IAsyncSwaggerProvider>();
202205
swaggerProvider = serviceProvider.GetRequiredService<ISwaggerProvider>();
203206
swaggerOptions = serviceProvider.GetService<IOptions<SwaggerOptions>>();
204207
}

test/Swashbuckle.AspNetCore.Cli.Test/ToolTests.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,26 @@ public static void Can_Generate_Swagger_Json_v3_OpenApiVersion()
133133
Assert.True(productsPath.TryGetProperty("post", out _));
134134
}
135135

136+
[Fact]
137+
public static void Can_Generate_Swagger_Json_With_Async_Operation_Filters()
138+
{
139+
using var document = RunToJsonCommand((outputPath) =>
140+
[
141+
"tofile",
142+
"--output",
143+
outputPath,
144+
Path.Combine(Directory.GetCurrentDirectory(), "Basic.dll"),
145+
"v1"
146+
]);
147+
148+
// verify that async operation filter output is included
149+
var paths = document.RootElement.GetProperty("paths");
150+
var productsPath = paths.GetProperty("/products");
151+
var postOp = productsPath.GetProperty("post");
152+
Assert.True(postOp.TryGetProperty("x-async-filter", out var asyncFilterValue));
153+
Assert.Equal("async-test", asyncFilterValue.GetString());
154+
}
155+
136156
[Fact]
137157
public static void Overwrites_Existing_File()
138158
{

test/WebSites/Basic/Startup.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public void ConfigureServices(IServiceCollection services)
2727
c.RequestBodyFilter<AssignRequestBodyVendorExtensions>();
2828

2929
c.OperationFilter<AssignOperationVendorExtensions>();
30+
c.OperationAsyncFilter<AssignOperationVendorExtensionsAsync>();
3031

3132
c.SchemaFilter<ExamplesSchemaFilter>();
3233

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Microsoft.OpenApi;
2+
using Swashbuckle.AspNetCore.SwaggerGen;
3+
4+
namespace Basic.Swagger;
5+
6+
public class AssignOperationVendorExtensionsAsync : IOperationAsyncFilter
7+
{
8+
public Task ApplyAsync(OpenApiOperation operation, OperationFilterContext context, CancellationToken cancellationToken)
9+
{
10+
operation.Extensions ??= new Dictionary<string, IOpenApiExtension>();
11+
operation.Extensions.Add("x-async-filter", new JsonNodeExtension("async-test"));
12+
return Task.CompletedTask;
13+
}
14+
}

0 commit comments

Comments
 (0)