From d07f1d37712d692be64493e305785fb99ccab44e Mon Sep 17 00:00:00 2001 From: david-brink-talogy Date: Wed, 13 Mar 2024 11:24:32 -0400 Subject: [PATCH] Add new settings Sync/AsyncMethodFormat --- .../generate-proxy-client.md | 8 +-- .../GenerateProxyClientWithCLI/sample.nswag | Bin 6628 -> 6800 bytes .../NSwag.ApiDescription.Client.props | 4 ++ .../NSwag.ApiDescription.Client.targets | 8 ++- .../CSharpClientSettingsTests.cs | 49 +++++++++++++++++- .../CSharpClientGeneratorSettings.cs | 6 +++ .../Models/CSharpClientTemplateModel.cs | 6 +++ .../Templates/Client.Class.liquid | 10 ++-- .../Templates/Client.Interface.liquid | 6 +-- .../OpenApiToCSharpClientCommand.cs | 16 ++++++ src/NSwag.Sample.NET70Minimal/nswag.json | 10 ++-- src/NSwag.Sample.NET80Minimal/nswag.json | 10 ++-- .../SwaggerToCSharpClientGeneratorView.xaml | 6 +++ 13 files changed, 120 insertions(+), 19 deletions(-) diff --git a/docs/tutorials/GenerateProxyClientWithCLI/generate-proxy-client.md b/docs/tutorials/GenerateProxyClientWithCLI/generate-proxy-client.md index f4acff2de1..eef3fbd18d 100644 --- a/docs/tutorials/GenerateProxyClientWithCLI/generate-proxy-client.md +++ b/docs/tutorials/GenerateProxyClientWithCLI/generate-proxy-client.md @@ -16,7 +16,7 @@ Also, since you've come to this repo, we'll assume you want to use NSwag as part - [OpenAPI Swagger Editor VS Code Extension](https://marketplace.visualstudio.com/items?itemName=42Crunch.vscode-openapi) *(optional)* - This Visual Studio Code (VS Code) extension adds rich support for the OpenAPI Specification (OAS) (formerly known as Swagger Specification) in JSON or YAML format. The features include, for example, SwaggerUI and ReDoc preview, IntelliSense, linting, schema enforcement, code navigation, definition links, snippets, static security analysis, and more! - If in later steps you choose to download the 3rd-Party Service's Open API Spec, this plugin makes it easy visualize -**Notes**: +**Notes**: - You may need to specify runtime version `nswag version /runtime:Net50` to run nswag on your local machine, since the sample [nswag config](https://github.com/RicoSuter/NSwag/wiki/NSwag-Configuration-Document) we'll use specifies `runtime` as `Net50`. - If you chose to download NSwag as a ZIP Archive, you may see dotnet version errors when trying to execute commands. If you are not able to resolve the issues, you may opt to install via Chocolatey or the MSI from the install instructions page provided above. @@ -73,7 +73,7 @@ nswag run sample.nswag /runtime:Net50 "output": null, "newLineBehavior": "Auto" } - }, + }, "codeGenerators": { "openApiToCSharpClient": { "generateClientClasses": true, @@ -121,7 +121,7 @@ nswag run sample.nswag /runtime:Net50 "timeType": "System.TimeSpan", "timeSpanType": "System.TimeSpan", "arrayType": "System.Collections.ObjectModel.ObservableCollection", - "arrayInstanceType": "System.Collections.ObjectModel.ObservableCollection", + "arrayInstanceType": "System.Collections.ObjectModel.ObservableCollection", "dictionaryType": "System.Collections.Generic.Dictionary", "arrayBaseType": "System.Collections.ObjectModel.ObservableCollection", "dictionaryBaseType": "System.Collections.Generic.Dictionary", @@ -132,6 +132,8 @@ nswag run sample.nswag /runtime:Net50 "handleReferences": false, "generateImmutableArrayProperties": false, "generateImmutableDictionaryProperties": false, + "asyncMethodFormat": "{0}Async", + "syncMethodFormat": "{0}", "output": "GENERATEDCODE.cs" } } diff --git a/docs/tutorials/GenerateProxyClientWithCLI/sample.nswag b/docs/tutorials/GenerateProxyClientWithCLI/sample.nswag index 8de7619a79eb39bfd88d27ea6a6f33dd09541229..2ed2d11eeaabc291ebc839baf625f75d005eb305 100644 GIT binary patch delta 121 zcmaE2Ji&BBgQR65Loq`oLmopigD*oWLkU9$Lq0$(NSwagServiceSchemes) $(NSwagOutput) $(NSwagNewLineBehavior) + $(NSwagAsyncMethodFormat) + $(NSwagSyncMethodFormat) NSwagCSharp @@ -177,6 +179,8 @@ $(NSwagServiceSchemes) $(NSwagOutput) $(NSwagNewLineBehavior) + $(NSwagAsyncMethodFormat) + $(NSwagSyncMethodFormat) diff --git a/src/NSwag.ApiDescription.Client/NSwag.ApiDescription.Client.targets b/src/NSwag.ApiDescription.Client/NSwag.ApiDescription.Client.targets index 234b673567..01a73d886c 100644 --- a/src/NSwag.ApiDescription.Client/NSwag.ApiDescription.Client.targets +++ b/src/NSwag.ApiDescription.Client/NSwag.ApiDescription.Client.targets @@ -276,7 +276,13 @@ %(Command) /newLineBehavior:%(NSwagNewLineBehavior) - + + %(Command) /asyncMethodFormat:%(NSwagAsyncMethodFormat) + + + %(Command) /syncMethodFormat:%(NSwagSyncMethodFormat) + + diff --git a/src/NSwag.CodeGeneration.CSharp.Tests/CSharpClientSettingsTests.cs b/src/NSwag.CodeGeneration.CSharp.Tests/CSharpClientSettingsTests.cs index 05350c01f2..da3dd21d96 100644 --- a/src/NSwag.CodeGeneration.CSharp.Tests/CSharpClientSettingsTests.cs +++ b/src/NSwag.CodeGeneration.CSharp.Tests/CSharpClientSettingsTests.cs @@ -1,6 +1,6 @@ +using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; -using NJsonSchema.Generation; using NJsonSchema.NewtonsoftJson.Generation; using NSwag.Generation.WebApi; using Xunit; @@ -249,5 +249,52 @@ public async Task When_client_interface_generation_is_enabled_and_suppressed_the Assert.DoesNotContain("public partial interface IFooClient", code); Assert.Contains("public partial class FooClient : IFooClient", code); } + + [Fact] + public async Task When_async_method_format_specified_then_methods_use_it() + { + // Arrange + var swaggerGenerator = new WebApiOpenApiDocumentGenerator(new WebApiOpenApiDocumentGeneratorSettings + { + SchemaSettings = new NewtonsoftJsonSchemaGeneratorSettings() + }); + + var document = await swaggerGenerator.GenerateForControllerAsync(); + var generator = new CSharpClientGenerator(document, new CSharpClientGeneratorSettings + { + GenerateClientInterfaces = true, + AsyncMethodFormat = "{0}Asynchronous" + }); + + // Act + var code = generator.GenerateFile(); + + // Assert + Assert.Equal(4, Regex.Matches(code, @"Task GetPersonAsynchronous\(").Count); + } + + [Fact] + public async Task When_sync_method_format_specified_then_methods_use_it() + { + // Arrange + var swaggerGenerator = new WebApiOpenApiDocumentGenerator(new WebApiOpenApiDocumentGeneratorSettings + { + SchemaSettings = new NewtonsoftJsonSchemaGeneratorSettings() + }); + + var document = await swaggerGenerator.GenerateForControllerAsync(); + var generator = new CSharpClientGenerator(document, new CSharpClientGeneratorSettings + { + GenerateClientInterfaces = true, + GenerateSyncMethods = true, + SyncMethodFormat = "{0}Synchronous" + }); + + // Act + var code = generator.GenerateFile(); + + // Assert + Assert.Equal(2, Regex.Matches(code, @"object GetPersonSynchronous\(").Count); + } } } \ No newline at end of file diff --git a/src/NSwag.CodeGeneration.CSharp/CSharpClientGeneratorSettings.cs b/src/NSwag.CodeGeneration.CSharp/CSharpClientGeneratorSettings.cs index efe684aa93..56337d4609 100644 --- a/src/NSwag.CodeGeneration.CSharp/CSharpClientGeneratorSettings.cs +++ b/src/NSwag.CodeGeneration.CSharp/CSharpClientGeneratorSettings.cs @@ -112,5 +112,11 @@ public CSharpClientGeneratorSettings() /// Gets or sets a value indicating whether to expose the JsonSerializerSettings property (default: false). public bool ExposeJsonSerializerSettings { get; set; } + + /// Gets or sets the format for asynchronous methods (default: "{0}Async"). + public string AsyncMethodFormat { get; set; } = "{0}Async"; + + /// Gets or sets the format for synchronous methods (default: "{0}"). + public string SyncMethodFormat { get; set; } = "{0}"; } } diff --git a/src/NSwag.CodeGeneration.CSharp/Models/CSharpClientTemplateModel.cs b/src/NSwag.CodeGeneration.CSharp/Models/CSharpClientTemplateModel.cs index e73787d160..647c0717a5 100644 --- a/src/NSwag.CodeGeneration.CSharp/Models/CSharpClientTemplateModel.cs +++ b/src/NSwag.CodeGeneration.CSharp/Models/CSharpClientTemplateModel.cs @@ -159,6 +159,12 @@ public CSharpClientTemplateModel( /// public bool GeneratePrepareRequestAndProcessResponseAsAsyncMethods => _settings.GeneratePrepareRequestAndProcessResponseAsAsyncMethods; + /// Gets the format for asynchronous methods. + public string AsyncMethodFormat => _settings.AsyncMethodFormat; + + /// Gets the format for synchronous methods. + public string SyncMethodFormat => _settings.SyncMethodFormat; + /// Gets the JSON serializer parameter code. public string JsonSerializerParameterCode { diff --git a/src/NSwag.CodeGeneration.CSharp/Templates/Client.Class.liquid b/src/NSwag.CodeGeneration.CSharp/Templates/Client.Class.liquid index 584842c89f..215dba9ca0 100644 --- a/src/NSwag.CodeGeneration.CSharp/Templates/Client.Class.liquid +++ b/src/NSwag.CodeGeneration.CSharp/Templates/Client.Class.liquid @@ -125,25 +125,25 @@ {% if GenerateOptionalParameters == false -%} {% template Client.Method.Documentation %} {% template Client.Method.Annotations %} - {{ operation.MethodAccessModifier }} virtual {{ operation.ResultType }} {{ operation.ActualOperationName }}Async({% for parameter in operation.Parameters %}{{ parameter.Type }} {{ parameter.VariableName }}{% if GenerateOptionalParameters and parameter.IsOptional %} = null{% endif %}{% if parameter.IsLast == false %}, {% endif %}{% endfor %}) + {{ operation.MethodAccessModifier }} virtual {{ operation.ResultType }} {{ AsyncMethodFormat | format_string: operation.ActualOperationName }}({% for parameter in operation.Parameters %}{{ parameter.Type }} {{ parameter.VariableName }}{% if GenerateOptionalParameters and parameter.IsOptional %} = null{% endif %}{% if parameter.IsLast == false %}, {% endif %}{% endfor %}) { - return {{ operation.ActualOperationName }}Async({% for parameter in operation.Parameters %}{{ parameter.VariableName }}, {% endfor %}System.Threading.CancellationToken.None); + return {{ AsyncMethodFormat | format_string: operation.ActualOperationName }}({% for parameter in operation.Parameters %}{{ parameter.VariableName }}, {% endfor %}System.Threading.CancellationToken.None); } {% endif -%} {% if GenerateSyncMethods -%} {% template Client.Method.Documentation %} {% template Client.Method.Annotations %} - {{ operation.MethodAccessModifier }} virtual {{ operation.SyncResultType }} {{ operation.ActualOperationName }}({% for parameter in operation.Parameters %}{{ parameter.Type }} {{ parameter.VariableName }}{% if GenerateOptionalParameters and parameter.IsOptional %} = null{% endif %}{% if parameter.IsLast == false %}, {% endif %}{% endfor %}) + {{ operation.MethodAccessModifier }} virtual {{ operation.SyncResultType }} {{ SyncMethodFormat | format_string: operation.ActualOperationName }}({% for parameter in operation.Parameters %}{{ parameter.Type }} {{ parameter.VariableName }}{% if GenerateOptionalParameters and parameter.IsOptional %} = null{% endif %}{% if parameter.IsLast == false %}, {% endif %}{% endfor %}) { - {% if operation.HasResult or operation.WrapResponse %}return {% endif %}System.Threading.Tasks.Task.Run(async () => await {{ operation.ActualOperationName }}Async({% for parameter in operation.Parameters %}{{ parameter.VariableName }}, {% endfor %}System.Threading.CancellationToken.None)).GetAwaiter().GetResult(); + {% if operation.HasResult or operation.WrapResponse %}return {% endif %}System.Threading.Tasks.Task.Run(async () => await {{ AsyncMethodFormat | format_string: operation.ActualOperationName }}({% for parameter in operation.Parameters %}{{ parameter.VariableName }}, {% endfor %}System.Threading.CancellationToken.None)).GetAwaiter().GetResult(); } {% endif -%} /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. {% template Client.Method.Documentation %} {% template Client.Method.Annotations %} - {{ operation.MethodAccessModifier }} virtual async {{ operation.ResultType }} {{ operation.ActualOperationName }}Async({% for parameter in operation.Parameters %}{{ parameter.Type }} {{ parameter.VariableName }}{% if GenerateOptionalParameters and parameter.IsOptional %} = null{% endif %}, {% endfor %}System.Threading.CancellationToken cancellationToken{% if GenerateOptionalParameters %} = default(System.Threading.CancellationToken){% endif %}) + {{ operation.MethodAccessModifier }} virtual async {{ operation.ResultType }} {{ AsyncMethodFormat | format_string: operation.ActualOperationName }}({% for parameter in operation.Parameters %}{{ parameter.Type }} {{ parameter.VariableName }}{% if GenerateOptionalParameters and parameter.IsOptional %} = null{% endif %}, {% endfor %}System.Threading.CancellationToken cancellationToken{% if GenerateOptionalParameters %} = default(System.Threading.CancellationToken){% endif %}) { {% for parameter in operation.PathParameters -%} {% if parameter.IsNullable == false and parameter.IsRequired -%} diff --git a/src/NSwag.CodeGeneration.CSharp/Templates/Client.Interface.liquid b/src/NSwag.CodeGeneration.CSharp/Templates/Client.Interface.liquid index 8fbb51e451..4225505265 100644 --- a/src/NSwag.CodeGeneration.CSharp/Templates/Client.Interface.liquid +++ b/src/NSwag.CodeGeneration.CSharp/Templates/Client.Interface.liquid @@ -7,19 +7,19 @@ public partial interface I{{ Class }}{% if HasClientBaseInterface %} : {{ Client {% if GenerateOptionalParameters == false -%} {% template Client.Method.Documentation %} {% template Client.Method.Annotations %} - {{ operation.ResultType }} {{ operation.ActualOperationName }}Async({% for parameter in operation.Parameters %}{{ parameter.Type }} {{ parameter.VariableName }}{% if GenerateOptionalParameters and parameter.IsOptional %} = null{% endif %}{% if parameter.IsLast == false %}, {% endif %}{% endfor %}); + {{ operation.ResultType }} {{ AsyncMethodFormat | format_string: operation.ActualOperationName }}({% for parameter in operation.Parameters %}{{ parameter.Type }} {{ parameter.VariableName }}{% if GenerateOptionalParameters and parameter.IsOptional %} = null{% endif %}{% if parameter.IsLast == false %}, {% endif %}{% endfor %}); {% endif -%} {% if GenerateSyncMethods -%} {% template Client.Method.Documentation %} {% template Client.Method.Annotations %} - {{ operation.SyncResultType }} {{ operation.ActualOperationName }}({% for parameter in operation.Parameters %}{{ parameter.Type }} {{ parameter.VariableName }}{% if GenerateOptionalParameters and parameter.IsOptional %} = null{% endif %}{% if parameter.IsLast == false %}, {% endif %}{% endfor %}); + {{ operation.SyncResultType }} {{ SyncMethodFormat | format_string: operation.ActualOperationName }}({% for parameter in operation.Parameters %}{{ parameter.Type }} {{ parameter.VariableName }}{% if GenerateOptionalParameters and parameter.IsOptional %} = null{% endif %}{% if parameter.IsLast == false %}, {% endif %}{% endfor %}); {%- endif %} /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. {% template Client.Method.Documentation %} {% template Client.Method.Annotations %} - {{ operation.ResultType }} {{ operation.ActualOperationName }}Async({% for parameter in operation.Parameters %}{{ parameter.Type }} {{ parameter.VariableName }}{% if GenerateOptionalParameters and parameter.IsOptional %} = null{% endif %}, {% endfor %}System.Threading.CancellationToken cancellationToken{% if GenerateOptionalParameters %} = default(System.Threading.CancellationToken){% endif %}); + {{ operation.ResultType }} {{ AsyncMethodFormat | format_string: operation.ActualOperationName }}({% for parameter in operation.Parameters %}{{ parameter.Type }} {{ parameter.VariableName }}{% if GenerateOptionalParameters and parameter.IsOptional %} = null{% endif %}, {% endfor %}System.Threading.CancellationToken cancellationToken{% if GenerateOptionalParameters %} = default(System.Threading.CancellationToken){% endif %}); {% endfor -%} } \ No newline at end of file diff --git a/src/NSwag.Commands/Commands/CodeGeneration/OpenApiToCSharpClientCommand.cs b/src/NSwag.Commands/Commands/CodeGeneration/OpenApiToCSharpClientCommand.cs index fcb02cd5b0..fb6acf739b 100644 --- a/src/NSwag.Commands/Commands/CodeGeneration/OpenApiToCSharpClientCommand.cs +++ b/src/NSwag.Commands/Commands/CodeGeneration/OpenApiToCSharpClientCommand.cs @@ -265,6 +265,22 @@ public string QueryNullValue set { Settings.QueryNullValue = value; } } + [Argument(Name = "AsyncMethodFormat", IsRequired = false, + Description = "Specifies the format for asynchronous methods (default: '{0}Async').")] + public string AsyncMethodFormat + { + get { return Settings.AsyncMethodFormat; } + set { Settings.AsyncMethodFormat = value; } + } + + [Argument(Name = "SyncMethodFormat", IsRequired = false, + Description = "Specifies the format for synchronous methods (default: '{0}').")] + public string SyncMethodFormat + { + get { return Settings.SyncMethodFormat; } + set { Settings.SyncMethodFormat = value; } + } + public override async Task RunAsync(CommandLineProcessor processor, IConsoleHost host) { var result = await RunAsync(); diff --git a/src/NSwag.Sample.NET70Minimal/nswag.json b/src/NSwag.Sample.NET70Minimal/nswag.json index 4d1dd750d0..ef053bae10 100644 --- a/src/NSwag.Sample.NET70Minimal/nswag.json +++ b/src/NSwag.Sample.NET70Minimal/nswag.json @@ -164,7 +164,9 @@ "serviceHost": null, "serviceSchemes": null, "output": "GeneratedClientsCs.gen", - "newLineBehavior": "Auto" + "newLineBehavior": "Auto", + "asyncMethodFormat": "{0}Async", + "syncMethodFormat": "{0}" }, "openApiToCSharpController": { "controllerBaseClass": null, @@ -225,7 +227,9 @@ "serviceHost": null, "serviceSchemes": null, "output": "GeneratedControllersCs.gen", - "newLineBehavior": "Auto" + "newLineBehavior": "Auto", + "asyncMethodFormat": "{0}Async", + "syncMethodFormat": "{0}" } } -} \ No newline at end of file +} diff --git a/src/NSwag.Sample.NET80Minimal/nswag.json b/src/NSwag.Sample.NET80Minimal/nswag.json index 3c1f7acba1..9690e5a7df 100644 --- a/src/NSwag.Sample.NET80Minimal/nswag.json +++ b/src/NSwag.Sample.NET80Minimal/nswag.json @@ -164,7 +164,9 @@ "serviceHost": null, "serviceSchemes": null, "output": "GeneratedClientsCs.gen", - "newLineBehavior": "Auto" + "newLineBehavior": "Auto", + "asyncMethodFormat": "{0}Async", + "syncMethodFormat": "{0}" }, "openApiToCSharpController": { "controllerBaseClass": null, @@ -225,7 +227,9 @@ "serviceHost": null, "serviceSchemes": null, "output": "GeneratedControllersCs.gen", - "newLineBehavior": "Auto" + "newLineBehavior": "Auto", + "asyncMethodFormat": "{0}Async", + "syncMethodFormat": "{0}" } } -} \ No newline at end of file +} diff --git a/src/NSwagStudio/Views/CodeGenerators/SwaggerToCSharpClientGeneratorView.xaml b/src/NSwagStudio/Views/CodeGenerators/SwaggerToCSharpClientGeneratorView.xaml index 4de29841ac..48dbf65e2b 100644 --- a/src/NSwagStudio/Views/CodeGenerators/SwaggerToCSharpClientGeneratorView.xaml +++ b/src/NSwagStudio/Views/CodeGenerators/SwaggerToCSharpClientGeneratorView.xaml @@ -185,6 +185,12 @@ + + + + + +