From 7f9398a32ea27fc7399e7e37146ecf582de7e42e Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 31 Oct 2025 15:14:19 +0800 Subject: [PATCH 1/5] Initial commit for DWB customization --- .../generichost/ClientUtils.mustache | 8 + .../DateOnlyJsonConverter.mustache | 3 +- .../DateOnlyNullableJsonConverter.mustache | 3 +- .../generichost/HostConfiguration.mustache | 6 + .../libraries/generichost/IApi.mustache | 73 +++++++ .../generichost/JsonConverter.mustache | 44 +++-- .../libraries/generichost/Option.mustache | 7 +- .../generichost/README.client.mustache | 19 +- .../csharp/libraries/generichost/api.mustache | 187 ++++++++++++++++-- .../libraries/generichost/api_test.mustache | 29 ++- .../libraries/generichost/modelEnum.mustache | 181 +++++++++++++++++ .../generichost/modelGeneric.mustache | 5 +- .../generichost/netcore_project.mustache | 85 ++++++++ 13 files changed, 595 insertions(+), 55 deletions(-) create mode 100644 modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelEnum.mustache create mode 100644 modules/openapi-generator/src/main/resources/csharp/libraries/generichost/netcore_project.mustache diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/ClientUtils.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/ClientUtils.mustache index 357d2197cd97..ed31df50b0a5 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/ClientUtils.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/ClientUtils.mustache @@ -209,6 +209,14 @@ using System.Runtime.CompilerServices; entries.Add(ParameterToString(entry)); return string.Join(",", entries); } + // DNV customization: Add parameter type IEnumerable + else if (obj is IEnumerable enumerable) + { + List entries = new{{^net70OrLater}} List{{/net70OrLater}}(); + foreach (var entry in enumerable) + entries.Add(ParameterToString(entry)); + return string.Join(",", entries); + } return Convert.ToString(obj, System.Globalization.CultureInfo.InvariantCulture); } diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/DateOnlyJsonConverter.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/DateOnlyJsonConverter.mustache index 209979c8db42..2e2dcc14c0e9 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/DateOnlyJsonConverter.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/DateOnlyJsonConverter.mustache @@ -33,7 +33,8 @@ namespace {{packageName}}.{{clientPackage}} string value = reader.GetString(){{nrt!}}; foreach(string format in Formats) - if (DateOnly.TryParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out DateOnly result)) + // DNV customization: DateOnly is irrelevant to time zone. + if (DateOnly.TryParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly result)) return result; throw new NotSupportedException(); diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/DateOnlyNullableJsonConverter.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/DateOnlyNullableJsonConverter.mustache index 17c847365369..4955cac8f0e5 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/DateOnlyNullableJsonConverter.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/DateOnlyNullableJsonConverter.mustache @@ -33,7 +33,8 @@ namespace {{packageName}}.{{clientPackage}} string value = reader.GetString(){{nrt!}}; foreach(string format in Formats) - if (DateOnly.TryParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out DateOnly result)) + // DNV customization: DateOnly is irrelevant to time zone. + if (DateOnly.TryParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly result)) return result; throw new NotSupportedException(); diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/HostConfiguration.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/HostConfiguration.mustache index 1333f0e67ea2..6f10fef79510 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/HostConfiguration.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/HostConfiguration.mustache @@ -16,6 +16,7 @@ using {{packageName}}.{{apiPackage}}; using {{packageName}}.{{modelPackage}}; {{/-first}} {{/models}} +using System.Text.Json.Serialization.Metadata; namespace {{packageName}}.{{clientPackage}} { @@ -54,6 +55,8 @@ namespace {{packageName}}.{{clientPackage}} {{/isEnum}} {{/model}} {{/models}} + // DNV customization: Use default resolver to support AOT + _jsonOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver(); JsonSerializerOptionsProvider jsonSerializerOptionsProvider = new{{^net60OrLater}} JsonSerializerOptionsProvider{{/net60OrLater}}(_jsonOptions); _services.AddSingleton(jsonSerializerOptionsProvider); {{#useSourceGeneration}} @@ -82,6 +85,8 @@ namespace {{packageName}}.{{clientPackage}} _services.AddSingleton();{{#apiInfo}}{{#apis}} _services.AddSingleton<{{classname}}Events>(); _services.AddTransient<{{interfacePrefix}}{{classname}}, {{classname}}>();{{/apis}}{{/apiInfo}} + // DNV customization: Inject client name. + _services.AddTransient<{{interfacePrefix}}{{clientName}}, {{clientName}}>(); } /// @@ -101,6 +106,7 @@ namespace {{packageName}}.{{clientPackage}} {{#apiInfo}}{{#apis}}builders.Add(_services.AddHttpClient<{{interfacePrefix}}{{classname}}, {{classname}}>(client)); {{/apis}}{{/apiInfo}} + builders.Add(_services.AddHttpClient<{{interfacePrefix}}{{clientName}}, {{clientName}}>(client)); if (builder != null) foreach (IHttpClientBuilder instance in builders) builder(instance); diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/IApi.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/IApi.mustache index af31cffe9293..45e78ba525eb 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/IApi.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/IApi.mustache @@ -1,4 +1,13 @@ +using System; +using System.Collections.Generic; using System.Net.Http; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using Microsoft.Extensions.Logging; +using {{packageName}}.{{clientPackage}}; +using {{packageName}}.{{apiPackage}}; +using {{packageName}}.{{modelPackage}}; namespace {{packageName}}.{{apiPackage}} { @@ -12,4 +21,68 @@ namespace {{packageName}}.{{apiPackage}} /// HttpClient HttpClient { get; } } + + // DNV customization: Add client interface/object + /// + /// + public partial interface {{interfacePrefix}}{{clientName}} + { + JsonSerializerOptionsProvider JsonSerializerOptionsProvider { get; } + +{{#apiInfo.apis}} + {{interfacePrefix}}{{classname}} {{classname}} { get; } +{{/apiInfo.apis}} + + } +} + +namespace {{packageName}} +{ + public class {{clientName}} : {{interfacePrefix}}{{clientName}} + { + public HttpClient HttpClient { get; } + public JsonSerializerOptionsProvider JsonSerializerOptionsProvider { get; private set; } +{{#apiInfo.apis}} + {{>visibility}} virtual {{interfacePrefix}}{{classname}} {{classname}} { get; private set; } +{{/apiInfo.apis}} + + public {{clientName}}( + HttpClient httpClient, bool dummy = true, JsonSerializerOptionsProvider? jsonSerializerOptionsProvider = null) + { + HttpClient = httpClient; + if (jsonSerializerOptionsProvider != null) + { + JsonSerializerOptionsProvider = jsonSerializerOptionsProvider; + } + else + { + var jsonSerializerOptions = new JsonSerializerOptions(); + jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + jsonSerializerOptions.Converters.Add(new DateTimeJsonConverter()); + jsonSerializerOptions.Converters.Add(new DateTimeNullableJsonConverter()); + {{#supportsDateOnly}} + jsonSerializerOptions.Converters.Add(new DateOnlyJsonConverter()); + jsonSerializerOptions.Converters.Add(new DateOnlyNullableJsonConverter()); + {{/supportsDateOnly}} + {{#models}} + {{#model}} + {{#isEnum}} + jsonSerializerOptions.Converters.Add(new {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}JsonConverter()); + jsonSerializerOptions.Converters.Add(new {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}NullableJsonConverter()); + {{/isEnum}} + {{^isEnum}} + jsonSerializerOptions.Converters.Add(new {{classname}}JsonConverter()); + {{/isEnum}} + {{/model}} + {{/models}} + jsonSerializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver(); + JsonSerializerOptionsProvider = new JsonSerializerOptionsProvider(jsonSerializerOptions); + } + var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Information)); +{{#apiInfo.apis}} + {{classname}} = new {{classname}}(loggerFactory.CreateLogger<{{classname}}>(), loggerFactory, httpClient, JsonSerializerOptionsProvider, new {{classname}}Events()); +{{/apiInfo.apis}} + } + + } } \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache index 99a9a902d03a..a15399ede085 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache @@ -225,20 +225,40 @@ {{/isDateTime}} {{#isEnum}} {{^isMap}} - {{#isNumeric}} - {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}utf8JsonReader.TokenType == JsonTokenType.Null ? ({{#isInnerEnum}}{{classname}}.{{/isInnerEnum}}{{{datatypeWithEnum}}}?)null : ({{#isInnerEnum}}{{classname}}.{{/isInnerEnum}}{{{datatypeWithEnum}}})utf8JsonReader.Get{{#vendorExtensions.x-unsigned}}U{{/vendorExtensions.x-unsigned}}Int32()); - {{/isNumeric}} + // DNV customization: Handle enum by value +/* + "EnumByValue": { + "enum": [ + 100, + 200 + ], + "type": "integer", + "format": "int32", + "x-enum-descriptions": [ + "", + "" + ], + "x-enum-varnames": [ + "A", + "B" + ], + "description": "\n\n100 - A\n\n200 - B" + },*/ + {{#allowableValues}}{{#enumVars}}{{#-first}}{{#isString}} {{^isNumeric}} - string{{nrt?}} {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue = utf8JsonReader.GetString(); + string{{nrt?}} {{#lambda.camelcase_sanitize_param}}{{baseName}}{{/lambda.camelcase_sanitize_param}}RawValue = utf8JsonReader.GetString(); {{^isInnerEnum}} - if ({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue != null) - {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}{{{datatypeWithEnum}}}ValueConverter.FromStringOrDefault({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue)); + if ({{#lambda.camelcase_sanitize_param}}{{baseName}}{{/lambda.camelcase_sanitize_param}}RawValue != null) + {{#lambda.camelcase_sanitize_param}}{{baseName}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}{{{datatypeWithEnum}}}ValueConverter.FromStringOrDefault({{#lambda.camelcase_sanitize_param}}{{baseName}}{{/lambda.camelcase_sanitize_param}}RawValue)); {{/isInnerEnum}} {{#isInnerEnum}} - if ({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue != null) - {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}{{classname}}.{{{datatypeWithEnum}}}FromStringOrDefault({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue)); + if ({{#lambda.camelcase_sanitize_param}}{{baseName}}{{/lambda.camelcase_sanitize_param}}RawValue != null) + {{#lambda.camelcase_sanitize_param}}{{baseName}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}{{classname}}.{{{datatypeWithEnum}}}FromStringOrDefault({{#lambda.camelcase_sanitize_param}}{{baseName}}{{/lambda.camelcase_sanitize_param}}RawValue)); {{/isInnerEnum}} {{/isNumeric}} + {{/isString}}{{#isNumeric}} + {{#lambda.camelcase_sanitize_param}}{{baseName}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}utf8JsonReader.TokenType == JsonTokenType.Null ? ({{#isInnerEnum}}{{classname}}.{{/isInnerEnum}}{{{datatypeWithEnum}}}?)null : ({{#isInnerEnum}}{{classname}}.{{/isInnerEnum}}{{{datatypeWithEnum}}})utf8JsonReader.Get{{#vendorExtensions.x-unsigned}}U{{/vendorExtensions.x-unsigned}}Int32()); + {{/isNumeric}}{{/-first}}{{/enumVars}}{{/allowableValues}} {{/isMap}} {{/isEnum}} {{#isUuid}} @@ -338,9 +358,7 @@ public override void Write(Utf8JsonWriter writer, {{classname}} {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}, JsonSerializerOptions jsonSerializerOptions) { {{#lambda.trimLineBreaks}} - {{#lambda.copyText}} - {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}} - {{/lambda.copyText}} + {{#lambda.copyText}}{{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}{{/lambda.copyText}} {{#discriminator}} {{#children}} if ({{#lambda.paste}}{{/lambda.paste}} is {{classname}} {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}){ @@ -520,9 +538,7 @@ {{/isNullable}} {{/isInnerEnum}} {{^isInnerEnum}} - {{#lambda.copyText}} - {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} - {{/lambda.copyText}} + {{#lambda.copyText}}{{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}{{/lambda.copyText}} {{#required}} {{#isNullable}} if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}} == null) diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/Option.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/Option.mustache index eed49143e41d..7dfd385c0dd5 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/Option.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/Option.mustache @@ -28,8 +28,11 @@ namespace {{packageName}}.{{clientPackage}} /// public Option(TType value) { - IsSet = true; - Value = value; + if (value != null) + { + IsSet = true; + Value = value; + } } /// diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/README.client.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/README.client.mustache index 371b9daa9211..cbdffb5d819f 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/README.client.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/README.client.mustache @@ -50,9 +50,8 @@ using System; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection; -using {{packageName}}.Api; -using {{packageName}}.Client; -using {{packageName}}.Model; +using {{packageName}}.Extensions; +using {{packageName}}.Interfaces; namespace YourProject { @@ -61,13 +60,13 @@ namespace YourProject public static async Task Main(string[] args) { var host = CreateHostBuilder(args).Build();{{#apiInfo}}{{#apis}}{{#-first}} - var api = host.Services.GetRequiredService<{{interfacePrefix}}{{classname}}>(); + var api = host.Services.GetRequiredService<{{interfacePrefix}}{{clientName}}>(); {{#operations}} {{#-first}} {{#operation}} {{#-first}} - {{operationId}}ApiResponse apiResponse = await api.{{operationId}}Async("todo"); - {{#returnType}}{{{.}}}{{/returnType}}{{^returnType}}object{{/returnType}} model = apiResponse.Ok(); + var apiResponse = await api.{{baseName}}.{{#vendorExtensions.x-csharp-operationId}}{{vendorExtensions.x-csharp-operationId}}{{/vendorExtensions.x-csharp-operationId}}{{^vendorExtensions.x-csharp-operationId}}{{operationId}}{{/vendorExtensions.x-csharp-operationId}}Async("todo"); + Console.WriteLine($"Response: {apiResponse.ToString()}"); {{/-first}} {{/operation}} {{/-first}} @@ -78,7 +77,7 @@ namespace YourProject } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) - .Configure{{apiName}}((context, options) => + .Configure{{apiName}}((context, services, options) => { {{#authMethods}} {{#-first}} @@ -96,7 +95,9 @@ namespace YourProject // your custom converters if any }); - options.Add{{apiName}}HttpClients(builder: builder => builder + options.Add{{apiName}}HttpClients( + client: client => client.BaseAddress = new Uri(context.Configuration["ApiSettings:BaseAddress"]), + builder: builder => builder .AddRetryPolicy(2) .AddTimeoutPolicy(TimeSpan.FromSeconds(5)) .AddCircuitBreakerPolicy(10, TimeSpan.FromSeconds(30)) @@ -138,7 +139,7 @@ All URIs are relative to *{{{basePath}}}* Class | Method | HTTP request | Description ------------ | ------------- | ------------- | -------------{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} -*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{{summary}}}{{/summary}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}{{/apiDocs}}{{#modelDocs}} +*{{classname}}* | [**{{#vendorExtensions.x-csharp-operationId}}{{vendorExtensions.x-csharp-operationId}}{{/vendorExtensions.x-csharp-operationId}}{{^vendorExtensions.x-csharp-operationId}}{{operationId}}{{/vendorExtensions.x-csharp-operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{{summary}}}{{/summary}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}{{/apiDocs}}{{#modelDocs}} ## Documentation for Models diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache index 1c872fd8afb7..d26d9006628c 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache @@ -31,8 +31,11 @@ using {{packageName}}.{{modelPackage}}; {{^netStandard}} using System.Diagnostics.CodeAnalysis; {{/netStandard}} +using {{packageName}}.{{package}}; +// DNV customization: Todo: Microsoft.Rest will be removed +using Microsoft.Rest; -namespace {{packageName}}.{{apiPackage}} +namespace {{packageName}}.{{package}} { {{#operations}} /// @@ -58,11 +61,12 @@ namespace {{packageName}}.{{apiPackage}} /// {{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}} {{/allParams}} /// Cancellation Token to cancel the request. + /// The headers that will be added to request. /// <> {{#isDeprecated}} [Obsolete] {{/isDeprecated}} - Task<{{interfacePrefix}}{{operationId}}ApiResponse> {{operationId}}Async({{>OperationSignature}}); + Task<{{interfacePrefix}}{{operationId}}ApiResponse> {{operationId}}NativeAsync({{>OperationSignature}}, Dictionary>? customHeaders = null); /// /// {{summary}} @@ -79,6 +83,45 @@ namespace {{packageName}}.{{apiPackage}} [Obsolete] {{/isDeprecated}} Task<{{interfacePrefix}}{{operationId}}ApiResponse{{nrt?}}> {{operationId}}OrDefaultAsync({{>OperationSignature}}); + // DNV customization: Add wrap methods for only returning dto + /// + /// {{summary}} + /// + /// + /// {{notes}} + /// + /// Thrown when fails to make API call + {{#allParams}} + /// {{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}} + {{/allParams}} + /// Cancellation Token to cancel the request. + /// The headers that will be added to request. + /// <> + {{#isDeprecated}} + [Obsolete] + {{/isDeprecated}} + Task{{/returnType}}> {{#vendorExtensions.x-csharp-operationId}}{{vendorExtensions.x-csharp-operationId}}{{/vendorExtensions.x-csharp-operationId}}{{^vendorExtensions.x-csharp-operationId}}{{operationId}}{{/vendorExtensions.x-csharp-operationId}}WithHttpMessagesAsync( + {{>OperationSignature}}, + Dictionary>? customHeaders = null); + + /// + /// {{summary}} + /// + /// + /// {{notes}} + /// + /// Thrown when fails to make API call + {{#allParams}} + /// {{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}} + {{/allParams}} + /// Cancellation Token to cancel the request. + /// {{#returnType}}<>{{/returnType}} + {{#isDeprecated}} + [Obsolete] + {{/isDeprecated}} + Task{{#returnType}}<{{{returnType}}}>{{/returnType}} {{#vendorExtensions.x-csharp-operationId}}{{vendorExtensions.x-csharp-operationId}}{{/vendorExtensions.x-csharp-operationId}}{{^vendorExtensions.x-csharp-operationId}}{{operationId}}{{/vendorExtensions.x-csharp-operationId}}Async( + {{>OperationSignature}}); + {{^-last}} {{/-last}} @@ -94,6 +137,11 @@ namespace {{packageName}}.{{apiPackage}} /// {{>visibility}} interface {{interfacePrefix}}{{operationId}}ApiResponse : {{#lambda.joinWithComma}}{{packageName}}.{{clientPackage}}.{{interfacePrefix}}ApiResponse {{#responses}}{{#dataType}}{{interfacePrefix}}{{vendorExtensions.x-http-status}}<{{#isModel}}{{^containerType}}{{packageName}}.{{modelPackage}}.{{/containerType}}{{/isModel}}{{{dataType}}}{{#nrt}}?{{/nrt}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}}> {{/dataType}}{{/responses}}{{/lambda.joinWithComma}} { + // DNV customization: + HttpRequestMessage Request { get; } + + HttpResponseMessage Response { get; } + {{#responses}} {{#vendorExtensions.x-http-status-is-default}} /// @@ -149,6 +197,14 @@ namespace {{packageName}}.{{apiPackage}} {{/operation}} {{/lambda.trimTrailingWithNewLine}} } + + {{/operations}} +} + +// DNV customization: Separate interfaces for implementation +namespace {{packageName}} +{ + {{#operations}} /// /// Represents a collection of functions to interact with the API endpoints @@ -342,7 +398,7 @@ namespace {{packageName}}.{{apiPackage}} { try { - return await {{operationId}}Async({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}cancellationToken).ConfigureAwait(false); + return await {{operationId}}NativeAsync({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}cancellationToken).ConfigureAwait(false); } catch (Exception) { @@ -358,8 +414,9 @@ namespace {{packageName}}.{{apiPackage}} /// {{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}} {{/allParams}} /// Cancellation Token to cancel the request. + /// The headers that will be added to request. /// <> - public async Task<{{interfacePrefix}}{{operationId}}ApiResponse> {{operationId}}Async({{>OperationSignature}}) + public async Task<{{interfacePrefix}}{{operationId}}ApiResponse> {{operationId}}NativeAsync({{>OperationSignature}}, Dictionary>? customHeaders = null) { {{#lambda.trimLineBreaks}} UriBuilder uriBuilderLocalVar = new UriBuilder(); @@ -427,8 +484,24 @@ namespace {{packageName}}.{{apiPackage}} {{#-first}} {{/-first}} - parseQueryStringLocalVar["{{baseName}}"] = ClientUtils.ParameterToString({{paramName}}); {{/required}} + // DNV customization: non-required parameters are of type Option + {{#isArray}} + foreach (var item in {{paramName}}{{^required}}.Value{{/required}}) + { + var value = ClientUtils.ParameterToString(item); + if (!string.IsNullOrEmpty(value)) + { + parseQueryStringLocalVar["{{baseName}}"] = value; + } + } + {{/isArray}} + {{^isArray}} + {{^required}}if ({{paramName}}.IsSet){{/required}} + { + parseQueryStringLocalVar["{{baseName}}"] = ClientUtils.ParameterToString({{paramName}}{{^required}}.Value{{/required}}); + } + {{/isArray}} {{/queryParams}} {{#constantParams}} @@ -438,11 +511,6 @@ namespace {{packageName}}.{{apiPackage}} {{/isQueryParam}} {{/constantParams}} {{#queryParams}} - {{^required}} - if ({{paramName}}.IsSet) - parseQueryStringLocalVar["{{baseName}}"] = ClientUtils.ParameterToString({{paramName}}.Value); - - {{/required}} {{#-last}} uriBuilderLocalVar.Query = parseQueryStringLocalVar.ToString(); @@ -465,6 +533,16 @@ namespace {{packageName}}.{{apiPackage}} {{/required}} {{/headerParams}} + + // DNV customization: Add custom headers + if (customHeaders != null) + { + foreach (var header in customHeaders) + { + httpRequestMessageLocalVar.Headers.Add(header.Key, header.Value); + } + } + {{#formParams}} {{#-first}} MultipartContent multipartContentLocalVar = new MultipartContent(); @@ -619,9 +697,27 @@ namespace {{packageName}}.{{apiPackage}} DateTime requestedAtLocalVar = DateTime.UtcNow; + // DNV customization: Save request and response + string requestContentLocalVar = string.Empty; + if (httpRequestMessageLocalVar.Content != null) + { + requestContentLocalVar = await httpRequestMessageLocalVar.Content.ReadAsStringAsync().ConfigureAwait(false); + httpRequestMessageLocalVar.Content = new StringContent(requestContentLocalVar, System.Text.Encoding.UTF8, "application/json"); + } using (HttpResponseMessage httpResponseMessageLocalVar = await HttpClient.SendAsync(httpRequestMessageLocalVar, cancellationToken).ConfigureAwait(false)) { - string responseContentLocalVar = await httpResponseMessageLocalVar.Content.ReadAsStringAsync({{#net60OrLater}}cancellationToken{{/net60OrLater}}).ConfigureAwait(false); + string responseContentLocalVar = string.Empty; + if (httpResponseMessageLocalVar.Content != null) { + responseContentLocalVar = await httpResponseMessageLocalVar.Content.ReadAsStringAsync({{#net60OrLater}}cancellationToken{{/net60OrLater}}).ConfigureAwait(false); + } + if (!httpResponseMessageLocalVar.IsSuccessStatusCode) + { + throw new HttpOperationException(string.Format("Operation returned an invalid status code '{0}'", httpResponseMessageLocalVar.StatusCode)) + { + Request = new HttpRequestMessageWrapper(httpRequestMessageLocalVar, requestContentLocalVar), + Response = new HttpResponseMessageWrapper(httpResponseMessageLocalVar, responseContentLocalVar) + }; + } ILogger<{{#vendorExtensions.x-duplicates}}{{.}}.{{/vendorExtensions.x-duplicates}}{{operationId}}ApiResponse> apiResponseLoggerLocalVar = LoggerFactory.CreateLogger<{{#vendorExtensions.x-duplicates}}{{.}}.{{/vendorExtensions.x-duplicates}}{{operationId}}ApiResponse>(); @@ -697,6 +793,9 @@ namespace {{packageName}}.{{apiPackage}} /// The logger /// public ILogger<{{operationId}}ApiResponse> Logger { get; } + public HttpRequestMessage Request { get; } + + public HttpResponseMessage Response { get; } /// /// The @@ -711,6 +810,8 @@ namespace {{packageName}}.{{apiPackage}} public {{operationId}}ApiResponse(ILogger<{{operationId}}ApiResponse> logger, System.Net.Http.HttpRequestMessage httpRequestMessage, System.Net.Http.HttpResponseMessage httpResponseMessage, string rawContent, string path, DateTime requestedAt, System.Text.Json.JsonSerializerOptions jsonSerializerOptions) : base(httpRequestMessage, httpResponseMessage, rawContent, path, requestedAt, jsonSerializerOptions) { Logger = logger; + Request = httpRequestMessage; + Response = httpResponseMessage; OnCreated(httpRequestMessage, httpResponseMessage); } @@ -798,6 +899,70 @@ namespace {{packageName}}.{{apiPackage}} {{/-first}} {{/responses}} {{/vendorExtensions.x-duplicates}} + + /// + /// {{summary}} + /// + /// + /// {{notes}} + /// + /// Thrown when fails to make API call + {{#allParams}} + /// {{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}} + {{/allParams}} + /// Cancellation Token to cancel the request. + /// {{#returnType}}<>{{/returnType}} + public async Task{{#returnType}}<{{{returnType}}}>{{/returnType}} {{#vendorExtensions.x-csharp-operationId}}{{vendorExtensions.x-csharp-operationId}}{{/vendorExtensions.x-csharp-operationId}}{{^vendorExtensions.x-csharp-operationId}}{{operationId}}{{/vendorExtensions.x-csharp-operationId}}Async( + {{>OperationSignature}}) + { + // Call the WithHttpMessagesAsync method + {{#returnType}}var httpOperationResponse = {{/returnType}}await {{#vendorExtensions.x-csharp-operationId}}{{vendorExtensions.x-csharp-operationId}}{{/vendorExtensions.x-csharp-operationId}}{{^vendorExtensions.x-csharp-operationId}}{{operationId}}{{/vendorExtensions.x-csharp-operationId}}WithHttpMessagesAsync( + {{#allParams}} + {{paramName}}, + {{/allParams}} + cancellationToken, + null // customHeaders + ).ConfigureAwait(false); + + {{#returnType}} + // Return the deserialized body + return httpOperationResponse.Body; + {{/returnType}} + } + + /// + /// {{summary}} + /// + /// + /// {{notes}} + /// + /// Thrown when fails to make API call + {{#allParams}} + /// {{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}} + {{/allParams}} + /// Cancellation Token to cancel the request. + /// The headers that will be added to request. + /// {{#returnType}}<>{{/returnType}} + public async Task{{/returnType}}> {{#vendorExtensions.x-csharp-operationId}}{{vendorExtensions.x-csharp-operationId}}{{/vendorExtensions.x-csharp-operationId}}{{^vendorExtensions.x-csharp-operationId}}{{operationId}}{{/vendorExtensions.x-csharp-operationId}}WithHttpMessagesAsync( + {{>OperationSignature}}, + Dictionary>? customHeaders = null) + { + // Call the WithHttpMessagesAsync method + var apiResponse = await {{operationId}}NativeAsync( + {{#allParams}} + {{paramName}}, + {{/allParams}} + cancellationToken, + customHeaders + ).ConfigureAwait(false); + + return new HttpOperationResponse{{#returnType}}<{{{returnType}}}>{{/returnType}} + { + {{#returnType}}Body = JsonSerializer.Deserialize<{{{returnType}}}>(apiResponse.RawContent, _jsonSerializerOptions),{{/returnType}} + Request = apiResponse.Request, + Response = apiResponse.Response + }; + } {{/operation}} } {{/operations}} diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api_test.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api_test.mustache index 02ce2216830c..3d05a9641b77 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api_test.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api_test.mustache @@ -4,45 +4,44 @@ using System.Collections.Generic; using System.Threading.Tasks; using Xunit; using Microsoft.Extensions.DependencyInjection; -using {{packageName}}.{{apiPackage}};{{#hasImport}} +{{#hasImport}} using {{packageName}}.{{modelPackage}};{{/hasImport}} - +using {{packageName}}.Interfaces; {{>testInstructions}} -namespace {{packageName}}.Test.{{apiPackage}} +namespace {{packageName}}.Test.Interfaces { /// - /// Class for testing {{classname}} + /// Class for testing {{baseName}} /// - public sealed class {{classname}}Tests : ApiTestsBase + public sealed class {{baseName}}Tests : ApiTestsBase { - private readonly {{interfacePrefix}}{{classname}} _instance; + private readonly {{interfacePrefix}}{{baseName}} _instance; - public {{classname}}Tests(): base(Array.Empty()) + public {{baseName}}Tests(): base(Array.Empty()) { - _instance = _host.Services.GetRequiredService<{{interfacePrefix}}{{classname}}>(); + _instance = _host.Services.GetRequiredService<{{interfacePrefix}}{{baseName}}>(); } {{#operations}} {{#operation}} /// - /// Test {{operationId}} + /// Test {{#vendorExtensions.x-csharp-operationId}}{{vendorExtensions.x-csharp-operationId}}{{/vendorExtensions.x-csharp-operationId}}{{^vendorExtensions.x-csharp-operationId}}{{operationId}}{{/vendorExtensions.x-csharp-operationId}} /// [Fact (Skip = "not implemented")] - public async Task {{operationId}}AsyncTest() + public async Task {{#vendorExtensions.x-csharp-operationId}}{{vendorExtensions.x-csharp-operationId}}{{/vendorExtensions.x-csharp-operationId}}{{^vendorExtensions.x-csharp-operationId}}{{operationId}}{{/vendorExtensions.x-csharp-operationId}}AsyncTest() { {{#allParams}} - {{^required}}Client.Option<{{/required}}{{{dataType}}}{{>NullConditionalParameter}}{{^required}}>{{/required}} {{paramName}} = default{{nrt!}}; + {{{dataType}}}{{>NullConditionalParameter}} {{paramName}} = default{{nrt!}}; {{/allParams}} {{#returnType}} - var response = await _instance.{{operationId}}Async({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); - var model = response.{{#lambda.first}}{{#responses}}{{vendorExtensions.x-http-status}} {{/responses}}{{/lambda.first}}(); - Assert.IsType<{{{.}}}>(model); + var response = await _instance.{{#vendorExtensions.x-csharp-operationId}}{{vendorExtensions.x-csharp-operationId}}{{/vendorExtensions.x-csharp-operationId}}{{^vendorExtensions.x-csharp-operationId}}{{operationId}}{{/vendorExtensions.x-csharp-operationId}}Async({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + Assert.IsType<{{{.}}}>(response); {{/returnType}} {{^returnType}} - await _instance.{{operationId}}Async({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + await _instance.{{#vendorExtensions.x-csharp-operationId}}{{vendorExtensions.x-csharp-operationId}}{{/vendorExtensions.x-csharp-operationId}}{{^vendorExtensions.x-csharp-operationId}}{{operationId}}{{/vendorExtensions.x-csharp-operationId}}Async({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); {{/returnType}} } {{/operation}} diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelEnum.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelEnum.mustache new file mode 100644 index 000000000000..c5c7d5c1627d --- /dev/null +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelEnum.mustache @@ -0,0 +1,181 @@ + /// + /// {{description}}{{^description}}Defines a {{{name}}}{{/description}} + /// + {{#description}} + /// {{.}} + {{/description}} + {{#vendorExtensions.x-cls-compliant}} + [CLSCompliant({{{.}}})] + {{/vendorExtensions.x-cls-compliant}} + {{#vendorExtensions.x-com-visible}} + [ComVisible({{{.}}})] + {{/vendorExtensions.x-com-visible}} + {{#allowableValues}} + {{#enumVars}} + {{#-first}} + {{#isString}} + [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + {{/isString}} + {{/-first}} + {{/enumVars}} + {{/allowableValues}} + {{>visibility}} enum {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}{{#vendorExtensions.x-enum-byte}}: byte{{/vendorExtensions.x-enum-byte}} + { + {{#allowableValues}} + {{#enumVars}} + /// + /// Enum {{name}} for value: {{value}} + /// + {{#isString}} + {{! EnumMember not currently supported in System.Text.Json, use a converter instead }} + [System.Runtime.Serialization.EnumMember(Value = "{{{value}}}")] + {{/isString}} + {{name}}{{^isString}} = {{{value}}}{{/isString}}{{#isString}}{{^vendorExtensions.x-zero-based-enum}} = {{-index}}{{/vendorExtensions.x-zero-based-enum}}{{/isString}}{{^-last}},{{/-last}} + {{^-last}} + + {{/-last}} + {{/enumVars}} + {{/allowableValues}} + } + {{#useGenericHost}} + + /// + /// Converts to and from the JSON value + /// + public static class {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}ValueConverter + { + /// + /// Parses a given value to + /// + /// + /// + public static {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}} FromString(string value) + { + {{#allowableValues}} + {{#enumVars}} + if (value.Equals({{^isString}}({{{value}}}).ToString(){{/isString}}{{#isString}}"{{{value}}}"{{/isString}})) + return {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}.{{name}}; + + {{/enumVars}} + {{/allowableValues}} + throw new NotImplementedException($"Could not convert value to type {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}: '{value}'"); + } + + /// + /// Parses a given value to + /// + /// + /// + public static {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}? FromStringOrDefault(string value) + { + {{#allowableValues}} + {{#enumVars}} + if (value.Equals({{^isString}}({{{value}}}).ToString(){{/isString}}{{#isString}}"{{{value}}}"{{/isString}})) + return {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}.{{name}}; + + {{/enumVars}} + {{/allowableValues}} + return null; + } + + /// + /// Converts the to the json value + /// + /// + /// + /// + public static {{>EnumValueDataType}} ToJsonValue({{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}} value) + { + {{^isString}} + return ({{>EnumValueDataType}}) value; + {{/isString}} + {{#isString}} + {{#allowableValues}} + {{#enumVars}} + if (value == {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}.{{name}}) + return {{^isNumeric}}"{{/isNumeric}}{{{value}}}{{^isNumeric}}"{{/isNumeric}}; + + {{/enumVars}} + {{/allowableValues}} + throw new NotImplementedException($"Value could not be handled: '{value}'"); + {{/isString}} + } + } + + /// + /// A Json converter for type + /// + /// + public class {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}JsonConverter : JsonConverter<{{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}> + { + /// + /// Returns a {{datatypeWithEnum}} from the Json object + /// + /// + /// + /// + /// + public override {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}} Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string{{nrt?}} rawValue = reader.GetString(); + + {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}? result = rawValue == null + ? null + : {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}ValueConverter.FromStringOrDefault(rawValue); + + if (result != null) + return result.Value; + + throw new JsonException(); + } + + /// + /// Writes the {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}} to the json writer + /// + /// + /// + /// + public override void Write(Utf8JsonWriter writer, {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}} {{#lambda.camelcase_sanitize_param}}{{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}{{/lambda.camelcase_sanitize_param}}, JsonSerializerOptions options) + { + writer.WriteStringValue({{#lambda.camelcase_sanitize_param}}{{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}{{/lambda.camelcase_sanitize_param}}.ToString()); + } + } + + /// + /// A Json converter for type + /// + public class {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}NullableJsonConverter : JsonConverter<{{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}?> + { + /// + /// Returns a {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}} from the Json object + /// + /// + /// + /// + /// + public override {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string{{nrt?}} rawValue = reader.GetString(); + + {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}? result = rawValue == null + ? null + : {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}ValueConverter.FromStringOrDefault(rawValue); + + if (result != null) + return result.Value; + + throw new JsonException(); + } + + /// + /// Writes the DateTime to the json writer + /// + /// + /// + /// + public override void Write(Utf8JsonWriter writer, {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}? {{#lambda.camelcase_sanitize_param}}{{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}{{/lambda.camelcase_sanitize_param}}, JsonSerializerOptions options) + { + writer.WriteStringValue({{#lambda.camelcase_sanitize_param}}{{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}{{/lambda.camelcase_sanitize_param}}?.ToString() ?? "null"); + } + } + {{/useGenericHost}} diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelGeneric.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelGeneric.mustache index 04fb37138629..064d1ebc606f 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelGeneric.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelGeneric.mustache @@ -238,8 +238,9 @@ {{#deprecated}} [Obsolete] {{/deprecated}} - public {{{datatypeWithEnum}}}{{#lambda.first}}{{#isNullable}}{{>NullConditionalProperty}} {{/isNullable}}{{^required}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}} {{/required}}{{/lambda.first}} {{name}} {{#required}}{ get; {{^isReadOnly}}set; {{/isReadOnly}}}{{/required}}{{^required}}{ get { return this.{{name}}Option; } {{^isReadOnly}}set { this.{{name}}Option = new{{^net70OrLater}} Option<{{{datatypeWithEnum}}}{{>NullConditionalProperty}}>{{/net70OrLater}}(value); } {{/isReadOnly}}}{{/required}} - + // DNV customization: Implicit cast of nullable template type for Option may not work so add .Value explicitly + public {{{datatypeWithEnum}}}{{#lambda.first}}{{#isNullable}}{{>NullConditionalProperty}} {{/isNullable}}{{^required}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}} {{/required}}{{/lambda.first}} {{name}} {{#required}}{ get; {{^isReadOnly}}set; {{/isReadOnly}}}{{/required}}{{^required}}{ get { return this.{{name}}Option.Value; } {{^isReadOnly}}set { this.{{name}}Option = new{{^net70OrLater}} Option<{{{datatypeWithEnum}}}{{>NullConditionalProperty}}>{{/net70OrLater}}(value); } {{/isReadOnly}}}{{/required}} + {{/isInherited}} {{/isEnum}} {{/vendorExtensions.x-is-base-or-new-discriminator}} diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/netcore_project.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/netcore_project.mustache new file mode 100644 index 000000000000..561e4f05e299 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/netcore_project.mustache @@ -0,0 +1,85 @@ + + + {{#useGenericHost}} + true {{/useGenericHost}}{{^useGenericHost}} + false{{/useGenericHost}} + {{targetFramework}} + {{packageName}} + {{packageName}} + Library + {{packageAuthors}} + {{packageCompany}} + {{packageTitle}} + {{packageDescription}} + {{packageCopyright}} + {{packageName}} + {{packageVersion}} + bin\$(Configuration)\$(TargetFramework)\{{packageName}}.xml{{#licenseId}} + {{.}}{{/licenseId}} + https://{{{gitHost}}}/{{{gitUserId}}}/{{{gitRepoId}}}.git + git{{#releaseNote}} + {{.}}{{/releaseNote}}{{#packageTags}} + {{{.}}}{{/packageTags}}{{#nrt}} + {{#useGenericHost}}enable{{/useGenericHost}}{{^useGenericHost}}annotations{{/useGenericHost}}{{/nrt}} + false + + + + {{#useCompareNetObjects}} + + {{/useCompareNetObjects}} + {{^useGenericHost}} + + + {{/useGenericHost}} + {{#useRestSharp}} + + {{/useRestSharp}} + {{#useGenericHost}} + + + + {{#supportsRetry}} + + {{/supportsRetry}} + {{#net80OrLater}} + + {{/net80OrLater}} + {{^net60OrLater}} + + {{#net47OrLater}} + + {{/net47OrLater}} + {{/net60OrLater}} + {{/useGenericHost}} + {{^useGenericHost}} + {{#supportsRetry}} + + {{/supportsRetry}} + {{/useGenericHost}} + {{#validatable}} + {{^net60OrLater}} + + {{/net60OrLater}} + {{/validatable}} + + +{{^useGenericHost}} + + {{^net60OrLater}} + + {{/net60OrLater}} + {{#net48}} + + {{/net48}} + + + {{^net60OrLater}} + + {{/net60OrLater}} + {{#net48}} + + {{/net48}} + +{{/useGenericHost}} +{{>netcore_project.additions}} From b88ba79e17365af94c04b1d30bd728e4aa6dc814 Mon Sep 17 00:00:00 2001 From: Jason Date: Wed, 5 Nov 2025 17:46:50 +0800 Subject: [PATCH 2/5] Add HttpOperationException Hide enum by value temporary Remove customHeaders Remove ~WithHttpMessagesAsync() method Add second ctor without Option for automapping --- .../generichost/ApiException.mustache | 17 ++++ .../generichost/JsonConverter.mustache | 20 +++- .../csharp/libraries/generichost/api.mustache | 93 ++----------------- .../libraries/generichost/modelEnum.mustache | 2 +- .../generichost/modelGeneric.mustache | 42 +++++++++ 5 files changed, 84 insertions(+), 90 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/ApiException.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/ApiException.mustache index c14c1010ffd7..8c37933e2f4b 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/ApiException.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/ApiException.mustache @@ -5,6 +5,7 @@ {{/nrt}} using System; +using System.Net.Http; namespace {{packageName}}.{{clientPackage}} { @@ -43,4 +44,20 @@ namespace {{packageName}}.{{clientPackage}} RawContent = rawContent; } } + + /// + /// Http Operation Exception + /// + {{>visibility}} class HttpOperationException : Exception + { + {{>visibility}} HttpRequestMessage Request { get; } + {{>visibility}} HttpResponseMessage Response { get; } + + {{>visibility}} HttpOperationException(HttpRequestMessage request, HttpResponseMessage response, string message) + : base(message) + { + Request = request; + Response = response; + } + } } diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache index a15399ede085..b27c6755787c 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/JsonConverter.mustache @@ -225,7 +225,7 @@ {{/isDateTime}} {{#isEnum}} {{^isMap}} - // DNV customization: Handle enum by value + // DNV customization (candidate): Handle enum by value /* "EnumByValue": { "enum": [ @@ -244,7 +244,7 @@ ], "description": "\n\n100 - A\n\n200 - B" },*/ - {{#allowableValues}}{{#enumVars}}{{#-first}}{{#isString}} +/* {{#allowableValues}}{{#enumVars}}{{#-first}}{{#isString}} {{^isNumeric}} string{{nrt?}} {{#lambda.camelcase_sanitize_param}}{{baseName}}{{/lambda.camelcase_sanitize_param}}RawValue = utf8JsonReader.GetString(); {{^isInnerEnum}} @@ -258,7 +258,21 @@ {{/isNumeric}} {{/isString}}{{#isNumeric}} {{#lambda.camelcase_sanitize_param}}{{baseName}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}utf8JsonReader.TokenType == JsonTokenType.Null ? ({{#isInnerEnum}}{{classname}}.{{/isInnerEnum}}{{{datatypeWithEnum}}}?)null : ({{#isInnerEnum}}{{classname}}.{{/isInnerEnum}}{{{datatypeWithEnum}}})utf8JsonReader.Get{{#vendorExtensions.x-unsigned}}U{{/vendorExtensions.x-unsigned}}Int32()); - {{/isNumeric}}{{/-first}}{{/enumVars}}{{/allowableValues}} + {{/isNumeric}}{{/-first}}{{/enumVars}}{{/allowableValues}}*/ + {{#isNumeric}} + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}utf8JsonReader.TokenType == JsonTokenType.Null ? ({{#isInnerEnum}}{{classname}}.{{/isInnerEnum}}{{{datatypeWithEnum}}}?)null : ({{#isInnerEnum}}{{classname}}.{{/isInnerEnum}}{{{datatypeWithEnum}}})utf8JsonReader.Get{{#vendorExtensions.x-unsigned}}U{{/vendorExtensions.x-unsigned}}Int32()); + {{/isNumeric}} + {{^isNumeric}} + string{{nrt?}} {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue = utf8JsonReader.GetString(); + {{^isInnerEnum}} + if ({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue != null) + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}{{{datatypeWithEnum}}}ValueConverter.FromStringOrDefault({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue)); + {{/isInnerEnum}} + {{#isInnerEnum}} + if ({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue != null) + {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} = {{>OptionProperty}}{{classname}}.{{{datatypeWithEnum}}}FromStringOrDefault({{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}RawValue)); + {{/isInnerEnum}} + {{/isNumeric}} {{/isMap}} {{/isEnum}} {{#isUuid}} diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache index d26d9006628c..f497af46ff65 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache @@ -32,8 +32,6 @@ using {{packageName}}.{{modelPackage}}; using System.Diagnostics.CodeAnalysis; {{/netStandard}} using {{packageName}}.{{package}}; -// DNV customization: Todo: Microsoft.Rest will be removed -using Microsoft.Rest; namespace {{packageName}}.{{package}} { @@ -61,12 +59,11 @@ namespace {{packageName}}.{{package}} /// {{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}} {{/allParams}} /// Cancellation Token to cancel the request. - /// The headers that will be added to request. /// <> {{#isDeprecated}} [Obsolete] {{/isDeprecated}} - Task<{{interfacePrefix}}{{operationId}}ApiResponse> {{operationId}}NativeAsync({{>OperationSignature}}, Dictionary>? customHeaders = null); + Task<{{interfacePrefix}}{{operationId}}ApiResponse> {{operationId}}NativeAsync({{>OperationSignature}}); /// /// {{summary}} @@ -83,26 +80,6 @@ namespace {{packageName}}.{{package}} [Obsolete] {{/isDeprecated}} Task<{{interfacePrefix}}{{operationId}}ApiResponse{{nrt?}}> {{operationId}}OrDefaultAsync({{>OperationSignature}}); - // DNV customization: Add wrap methods for only returning dto - /// - /// {{summary}} - /// - /// - /// {{notes}} - /// - /// Thrown when fails to make API call - {{#allParams}} - /// {{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}} - {{/allParams}} - /// Cancellation Token to cancel the request. - /// The headers that will be added to request. - /// <> - {{#isDeprecated}} - [Obsolete] - {{/isDeprecated}} - Task{{/returnType}}> {{#vendorExtensions.x-csharp-operationId}}{{vendorExtensions.x-csharp-operationId}}{{/vendorExtensions.x-csharp-operationId}}{{^vendorExtensions.x-csharp-operationId}}{{operationId}}{{/vendorExtensions.x-csharp-operationId}}WithHttpMessagesAsync( - {{>OperationSignature}}, - Dictionary>? customHeaders = null); /// /// {{summary}} @@ -414,9 +391,8 @@ namespace {{packageName}} /// {{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}} {{/allParams}} /// Cancellation Token to cancel the request. - /// The headers that will be added to request. /// <> - public async Task<{{interfacePrefix}}{{operationId}}ApiResponse> {{operationId}}NativeAsync({{>OperationSignature}}, Dictionary>? customHeaders = null) + public async Task<{{interfacePrefix}}{{operationId}}ApiResponse> {{operationId}}NativeAsync({{>OperationSignature}}) { {{#lambda.trimLineBreaks}} UriBuilder uriBuilderLocalVar = new UriBuilder(); @@ -534,15 +510,6 @@ namespace {{packageName}} {{/required}} {{/headerParams}} - // DNV customization: Add custom headers - if (customHeaders != null) - { - foreach (var header in customHeaders) - { - httpRequestMessageLocalVar.Headers.Add(header.Key, header.Value); - } - } - {{#formParams}} {{#-first}} MultipartContent multipartContentLocalVar = new MultipartContent(); @@ -697,13 +664,6 @@ namespace {{packageName}} DateTime requestedAtLocalVar = DateTime.UtcNow; - // DNV customization: Save request and response - string requestContentLocalVar = string.Empty; - if (httpRequestMessageLocalVar.Content != null) - { - requestContentLocalVar = await httpRequestMessageLocalVar.Content.ReadAsStringAsync().ConfigureAwait(false); - httpRequestMessageLocalVar.Content = new StringContent(requestContentLocalVar, System.Text.Encoding.UTF8, "application/json"); - } using (HttpResponseMessage httpResponseMessageLocalVar = await HttpClient.SendAsync(httpRequestMessageLocalVar, cancellationToken).ConfigureAwait(false)) { string responseContentLocalVar = string.Empty; @@ -712,11 +672,7 @@ namespace {{packageName}} } if (!httpResponseMessageLocalVar.IsSuccessStatusCode) { - throw new HttpOperationException(string.Format("Operation returned an invalid status code '{0}'", httpResponseMessageLocalVar.StatusCode)) - { - Request = new HttpRequestMessageWrapper(httpRequestMessageLocalVar, requestContentLocalVar), - Response = new HttpResponseMessageWrapper(httpResponseMessageLocalVar, responseContentLocalVar) - }; + throw new HttpOperationException(httpRequestMessageLocalVar, httpResponseMessageLocalVar, string.Format("Operation returned an invalid status code '{0}'", httpResponseMessageLocalVar.StatusCode)); } ILogger<{{#vendorExtensions.x-duplicates}}{{.}}.{{/vendorExtensions.x-duplicates}}{{operationId}}ApiResponse> apiResponseLoggerLocalVar = LoggerFactory.CreateLogger<{{#vendorExtensions.x-duplicates}}{{.}}.{{/vendorExtensions.x-duplicates}}{{operationId}}ApiResponse>(); @@ -915,54 +871,19 @@ namespace {{packageName}} public async Task{{#returnType}}<{{{returnType}}}>{{/returnType}} {{#vendorExtensions.x-csharp-operationId}}{{vendorExtensions.x-csharp-operationId}}{{/vendorExtensions.x-csharp-operationId}}{{^vendorExtensions.x-csharp-operationId}}{{operationId}}{{/vendorExtensions.x-csharp-operationId}}Async( {{>OperationSignature}}) { - // Call the WithHttpMessagesAsync method - {{#returnType}}var httpOperationResponse = {{/returnType}}await {{#vendorExtensions.x-csharp-operationId}}{{vendorExtensions.x-csharp-operationId}}{{/vendorExtensions.x-csharp-operationId}}{{^vendorExtensions.x-csharp-operationId}}{{operationId}}{{/vendorExtensions.x-csharp-operationId}}WithHttpMessagesAsync( + var apiResponse = await {{operationId}}NativeAsync( {{#allParams}} {{paramName}}, {{/allParams}} - cancellationToken, - null // customHeaders - ).ConfigureAwait(false); + cancellationToken + ).ConfigureAwait(false); {{#returnType}} // Return the deserialized body - return httpOperationResponse.Body; + return JsonSerializer.Deserialize<{{{returnType}}}>(apiResponse.RawContent, _jsonSerializerOptions); {{/returnType}} } - /// - /// {{summary}} - /// - /// - /// {{notes}} - /// - /// Thrown when fails to make API call - {{#allParams}} - /// {{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}} - {{/allParams}} - /// Cancellation Token to cancel the request. - /// The headers that will be added to request. - /// {{#returnType}}<>{{/returnType}} - public async Task{{/returnType}}> {{#vendorExtensions.x-csharp-operationId}}{{vendorExtensions.x-csharp-operationId}}{{/vendorExtensions.x-csharp-operationId}}{{^vendorExtensions.x-csharp-operationId}}{{operationId}}{{/vendorExtensions.x-csharp-operationId}}WithHttpMessagesAsync( - {{>OperationSignature}}, - Dictionary>? customHeaders = null) - { - // Call the WithHttpMessagesAsync method - var apiResponse = await {{operationId}}NativeAsync( - {{#allParams}} - {{paramName}}, - {{/allParams}} - cancellationToken, - customHeaders - ).ConfigureAwait(false); - - return new HttpOperationResponse{{#returnType}}<{{{returnType}}}>{{/returnType}} - { - {{#returnType}}Body = JsonSerializer.Deserialize<{{{returnType}}}>(apiResponse.RawContent, _jsonSerializerOptions),{{/returnType}} - Request = apiResponse.Request, - Response = apiResponse.Response - }; - } {{/operation}} } {{/operations}} diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelEnum.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelEnum.mustache index c5c7d5c1627d..0249fa098df6 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelEnum.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelEnum.mustache @@ -1,5 +1,5 @@ /// - /// {{description}}{{^description}}Defines a {{{name}}}{{/description}} + /// {{description}}{{^description}}Defines {{{name}}}{{/description}} /// {{#description}} /// {{.}} diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelGeneric.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelGeneric.mustache index 064d1ebc606f..da5d950cceff 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelGeneric.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelGeneric.mustache @@ -89,6 +89,48 @@ OnCreated(); } + /// + /// Second constructor (without Option-ed parameters) of the class. + /// + {{#composedSchemas.anyOf}} + /// + {{/composedSchemas.anyOf}} + {{#allVars}} + {{^isDiscriminator}} + /// {{description}}{{^description}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/description}}{{#defaultValue}} (default to {{.}}){{/defaultValue}} + {{/isDiscriminator}} + {{/allVars}} + /// dummy to avoid duplicated ctors + {{^composedSchemas.anyOf}} + [JsonConstructor] + {{/composedSchemas.anyOf}} + {{#model.vendorExtensions.x-model-is-mutable}}{{>visibility}}{{/model.vendorExtensions.x-model-is-mutable}}{{^model.vendorExtensions.x-model-is-mutable}}internal{{/model.vendorExtensions.x-model-is-mutable}} {{classname}}({{#lambda.joinWithComma}}{{#composedSchemas.anyOf}}{{{baseType}}}{{>NullConditionalProperty}} {{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}} {{/composedSchemas.anyOf}}{{#model.allVars}}{{^isDiscriminator}}{{{datatypeWithEnum}}}{{>NullConditionalProperty}} {{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}} {{/isDiscriminator}}{{/model.allVars}}{{/lambda.joinWithComma}}, bool dummy = true){{#parent}} : base({{#lambda.joinWithComma}}{{>ModelBaseSignature}}{{/lambda.joinWithComma}}){{/parent}} + { + {{#composedSchemas.anyOf}} + {{#lambda.titlecase}}{{name}}{{/lambda.titlecase}} = {{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}}; + {{/composedSchemas.anyOf}} + {{#allVars}} + {{^isDiscriminator}} + {{^isInherited}} + {{name}} = {{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}}; + {{/isInherited}} + {{#isInherited}} + {{#isNew}} + {{name}} = {{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}}; + {{/isNew}} + {{/isInherited}} + {{/isDiscriminator}} + {{#vendorExtensions.x-is-base-or-new-discriminator}} + {{^model.composedSchemas.anyOf}} + {{^model.composedSchemas.oneOf}} + {{name}} = {{^isEnum}}this.GetType().Name{{/isEnum}}{{#isEnum}}({{datatypeWithEnum}})Enum.Parse(typeof({{datatypeWithEnum}}), this.GetType().Name){{/isEnum}}; + {{/model.composedSchemas.oneOf}} + {{/model.composedSchemas.anyOf}} + {{/vendorExtensions.x-is-base-or-new-discriminator}} + {{/allVars}} + OnCreated(); + } + {{/composedSchemas.oneOf}} partial void OnCreated(); From 9d42e7d30ad465b896a15f09f34a890946a61107 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 6 Nov 2025 13:40:34 +0800 Subject: [PATCH 3/5] Remove request and response --- .../csharp/libraries/generichost/IApi.mustache | 6 +----- .../csharp/libraries/generichost/api.mustache | 11 ----------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/IApi.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/IApi.mustache index 45e78ba525eb..3e5861cfed78 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/IApi.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/IApi.mustache @@ -27,12 +27,9 @@ namespace {{packageName}}.{{apiPackage}} /// public partial interface {{interfacePrefix}}{{clientName}} { - JsonSerializerOptionsProvider JsonSerializerOptionsProvider { get; } - {{#apiInfo.apis}} {{interfacePrefix}}{{classname}} {{classname}} { get; } {{/apiInfo.apis}} - } } @@ -41,7 +38,7 @@ namespace {{packageName}} public class {{clientName}} : {{interfacePrefix}}{{clientName}} { public HttpClient HttpClient { get; } - public JsonSerializerOptionsProvider JsonSerializerOptionsProvider { get; private set; } + public JsonSerializerOptionsProvider JsonSerializerOptionsProvider { get; private set; } {{#apiInfo.apis}} {{>visibility}} virtual {{interfacePrefix}}{{classname}} {{classname}} { get; private set; } {{/apiInfo.apis}} @@ -83,6 +80,5 @@ namespace {{packageName}} {{classname}} = new {{classname}}(loggerFactory.CreateLogger<{{classname}}>(), loggerFactory, httpClient, JsonSerializerOptionsProvider, new {{classname}}Events()); {{/apiInfo.apis}} } - } } \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache index f497af46ff65..47d5c024dcae 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache @@ -114,11 +114,6 @@ namespace {{packageName}}.{{package}} /// {{>visibility}} interface {{interfacePrefix}}{{operationId}}ApiResponse : {{#lambda.joinWithComma}}{{packageName}}.{{clientPackage}}.{{interfacePrefix}}ApiResponse {{#responses}}{{#dataType}}{{interfacePrefix}}{{vendorExtensions.x-http-status}}<{{#isModel}}{{^containerType}}{{packageName}}.{{modelPackage}}.{{/containerType}}{{/isModel}}{{{dataType}}}{{#nrt}}?{{/nrt}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}}> {{/dataType}}{{/responses}}{{/lambda.joinWithComma}} { - // DNV customization: - HttpRequestMessage Request { get; } - - HttpResponseMessage Response { get; } - {{#responses}} {{#vendorExtensions.x-http-status-is-default}} /// @@ -509,7 +504,6 @@ namespace {{packageName}} {{/required}} {{/headerParams}} - {{#formParams}} {{#-first}} MultipartContent multipartContentLocalVar = new MultipartContent(); @@ -749,9 +743,6 @@ namespace {{packageName}} /// The logger /// public ILogger<{{operationId}}ApiResponse> Logger { get; } - public HttpRequestMessage Request { get; } - - public HttpResponseMessage Response { get; } /// /// The @@ -766,8 +757,6 @@ namespace {{packageName}} public {{operationId}}ApiResponse(ILogger<{{operationId}}ApiResponse> logger, System.Net.Http.HttpRequestMessage httpRequestMessage, System.Net.Http.HttpResponseMessage httpResponseMessage, string rawContent, string path, DateTime requestedAt, System.Text.Json.JsonSerializerOptions jsonSerializerOptions) : base(httpRequestMessage, httpResponseMessage, rawContent, path, requestedAt, jsonSerializerOptions) { Logger = logger; - Request = httpRequestMessage; - Response = httpResponseMessage; OnCreated(httpRequestMessage, httpResponseMessage); } From 68a817a810dd7c65d343b4fa373ff07f6143932b Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 7 Nov 2025 16:51:17 +0800 Subject: [PATCH 4/5] Revert adding HttpOperationException --- .../libraries/generichost/ApiException.mustache | 17 ----------------- .../csharp/libraries/generichost/api.mustache | 15 ++++++++++++++- .../libraries/generichost/modelGeneric.mustache | 5 +---- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/ApiException.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/ApiException.mustache index 8c37933e2f4b..c14c1010ffd7 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/ApiException.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/ApiException.mustache @@ -5,7 +5,6 @@ {{/nrt}} using System; -using System.Net.Http; namespace {{packageName}}.{{clientPackage}} { @@ -44,20 +43,4 @@ namespace {{packageName}}.{{clientPackage}} RawContent = rawContent; } } - - /// - /// Http Operation Exception - /// - {{>visibility}} class HttpOperationException : Exception - { - {{>visibility}} HttpRequestMessage Request { get; } - {{>visibility}} HttpResponseMessage Response { get; } - - {{>visibility}} HttpOperationException(HttpRequestMessage request, HttpResponseMessage response, string message) - : base(message) - { - Request = request; - Response = response; - } - } } diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache index 47d5c024dcae..ed35afcf326f 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache @@ -32,6 +32,8 @@ using {{packageName}}.{{modelPackage}}; using System.Diagnostics.CodeAnalysis; {{/netStandard}} using {{packageName}}.{{package}}; +// DNV customization: Todo: Microsoft.Rest will be removed +using Microsoft.Rest; namespace {{packageName}}.{{package}} { @@ -658,6 +660,13 @@ namespace {{packageName}} DateTime requestedAtLocalVar = DateTime.UtcNow; + // DNV customization: Save request and response + string requestContentLocalVar = string.Empty; + if (httpRequestMessageLocalVar.Content != null) + { + requestContentLocalVar = await httpRequestMessageLocalVar.Content.ReadAsStringAsync().ConfigureAwait(false); + httpRequestMessageLocalVar.Content = new StringContent(requestContentLocalVar, System.Text.Encoding.UTF8, "application/json"); + } using (HttpResponseMessage httpResponseMessageLocalVar = await HttpClient.SendAsync(httpRequestMessageLocalVar, cancellationToken).ConfigureAwait(false)) { string responseContentLocalVar = string.Empty; @@ -666,7 +675,11 @@ namespace {{packageName}} } if (!httpResponseMessageLocalVar.IsSuccessStatusCode) { - throw new HttpOperationException(httpRequestMessageLocalVar, httpResponseMessageLocalVar, string.Format("Operation returned an invalid status code '{0}'", httpResponseMessageLocalVar.StatusCode)); + throw new HttpOperationException(string.Format("Operation returned an invalid status code '{0}'", httpResponseMessageLocalVar.StatusCode)) + { + Request = new HttpRequestMessageWrapper(httpRequestMessageLocalVar, requestContentLocalVar), + Response = new HttpResponseMessageWrapper(httpResponseMessageLocalVar, responseContentLocalVar) + }; } ILogger<{{#vendorExtensions.x-duplicates}}{{.}}.{{/vendorExtensions.x-duplicates}}{{operationId}}ApiResponse> apiResponseLoggerLocalVar = LoggerFactory.CreateLogger<{{#vendorExtensions.x-duplicates}}{{.}}.{{/vendorExtensions.x-duplicates}}{{operationId}}ApiResponse>(); diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelGeneric.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelGeneric.mustache index da5d950cceff..042110ce6c66 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelGeneric.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/modelGeneric.mustache @@ -101,9 +101,6 @@ {{/isDiscriminator}} {{/allVars}} /// dummy to avoid duplicated ctors - {{^composedSchemas.anyOf}} - [JsonConstructor] - {{/composedSchemas.anyOf}} {{#model.vendorExtensions.x-model-is-mutable}}{{>visibility}}{{/model.vendorExtensions.x-model-is-mutable}}{{^model.vendorExtensions.x-model-is-mutable}}internal{{/model.vendorExtensions.x-model-is-mutable}} {{classname}}({{#lambda.joinWithComma}}{{#composedSchemas.anyOf}}{{{baseType}}}{{>NullConditionalProperty}} {{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}} {{/composedSchemas.anyOf}}{{#model.allVars}}{{^isDiscriminator}}{{{datatypeWithEnum}}}{{>NullConditionalProperty}} {{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}} {{/isDiscriminator}}{{/model.allVars}}{{/lambda.joinWithComma}}, bool dummy = true){{#parent}} : base({{#lambda.joinWithComma}}{{>ModelBaseSignature}}{{/lambda.joinWithComma}}){{/parent}} { {{#composedSchemas.anyOf}} @@ -112,7 +109,7 @@ {{#allVars}} {{^isDiscriminator}} {{^isInherited}} - {{name}} = {{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}}; + {{name}}{{#isReadOnly}}{{^required}}Option{{/required}}{{/isReadOnly}} = {{#isReadOnly}}{{^required}}new({{/required}}{{/isReadOnly}}{{#lambda.escape_reserved_word}}{{#lambda.camel_case}}{{name}}{{/lambda.camel_case}}{{/lambda.escape_reserved_word}}{{#isReadOnly}}{{^required}}){{/required}}{{/isReadOnly}}; {{/isInherited}} {{#isInherited}} {{#isNew}} From 6b88c0c13dba210967ed4475099418088224015b Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 27 Nov 2025 10:27:35 +0800 Subject: [PATCH 5/5] Inject api client instead of new instance --- .../generichost/HostConfiguration.mustache | 25 +++++++++++++------ .../libraries/generichost/IApi.mustache | 12 +++------ .../csharp/libraries/generichost/api.mustache | 14 ++++++++--- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/HostConfiguration.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/HostConfiguration.mustache index 6f10fef79510..b0c7d5681213 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/HostConfiguration.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/HostConfiguration.mustache @@ -85,7 +85,7 @@ namespace {{packageName}}.{{clientPackage}} _services.AddSingleton();{{#apiInfo}}{{#apis}} _services.AddSingleton<{{classname}}Events>(); _services.AddTransient<{{interfacePrefix}}{{classname}}, {{classname}}>();{{/apis}}{{/apiInfo}} - // DNV customization: Inject client name. + // DNV customization: Inject api client object. _services.AddTransient<{{interfacePrefix}}{{clientName}}, {{clientName}}>(); } @@ -93,20 +93,31 @@ namespace {{packageName}}.{{clientPackage}} /// Configures the HttpClients. /// /// + /// /// /// - public HostConfiguration Add{{apiName}}HttpClients - ( - Action{{nrt?}} client = null, Action{{nrt?}} builder = null) + public HostConfiguration Add{{apiName}}HttpClients( + Action{{nrt?}} client = null, string? name = null, Action{{nrt?}} builder = null) { if (client == null) client = c => c.BaseAddress = new Uri(ClientUtils.BASE_ADDRESS); List builders = new List(); - {{#apiInfo}}{{#apis}}builders.Add(_services.AddHttpClient<{{interfacePrefix}}{{classname}}, {{classname}}>(client)); - {{/apis}}{{/apiInfo}} - builders.Add(_services.AddHttpClient<{{interfacePrefix}}{{clientName}}, {{clientName}}>(client)); + // DNV customization: Add Http client for api client + // DNV customization: Add name for http client + if (!string.IsNullOrEmpty(name)) + { + {{#apiInfo}}{{#apis}}builders.Add(_services.AddHttpClient<{{interfacePrefix}}{{classname}}, {{classname}}>(name, client)); + {{/apis}}{{/apiInfo}} + builders.Add(_services.AddHttpClient<{{interfacePrefix}}{{clientName}}, {{clientName}}>(name, client)); + } + else + { + {{#apiInfo}}{{#apis}}builders.Add(_services.AddHttpClient<{{interfacePrefix}}{{classname}}, {{classname}}>(client)); + {{/apis}}{{/apiInfo}} + builders.Add(_services.AddHttpClient<{{interfacePrefix}}{{clientName}}, {{clientName}}>(client)); + } if (builder != null) foreach (IHttpClientBuilder instance in builders) builder(instance); diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/IApi.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/IApi.mustache index 3e5861cfed78..c38cb7608977 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/IApi.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/IApi.mustache @@ -1,10 +1,7 @@ -using System; -using System.Collections.Generic; using System.Net.Http; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; -using Microsoft.Extensions.Logging; using {{packageName}}.{{clientPackage}}; using {{packageName}}.{{apiPackage}}; using {{packageName}}.{{modelPackage}}; @@ -44,9 +41,12 @@ namespace {{packageName}} {{/apiInfo.apis}} public {{clientName}}( - HttpClient httpClient, bool dummy = true, JsonSerializerOptionsProvider? jsonSerializerOptionsProvider = null) + HttpClient httpClient, {{#apiInfo.apis}}{{interfacePrefix}}{{classname}} {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}, {{/apiInfo.apis}}JsonSerializerOptionsProvider? jsonSerializerOptionsProvider = null) { HttpClient = httpClient; +{{#apiInfo.apis}} + {{classname}} = {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}; +{{/apiInfo.apis}} if (jsonSerializerOptionsProvider != null) { JsonSerializerOptionsProvider = jsonSerializerOptionsProvider; @@ -75,10 +75,6 @@ namespace {{packageName}} jsonSerializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver(); JsonSerializerOptionsProvider = new JsonSerializerOptionsProvider(jsonSerializerOptions); } - var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Information)); -{{#apiInfo.apis}} - {{classname}} = new {{classname}}(loggerFactory.CreateLogger<{{classname}}>(), loggerFactory, httpClient, JsonSerializerOptionsProvider, new {{classname}}Events()); -{{/apiInfo.apis}} } } } \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache index ed35afcf326f..fd9863f9b136 100644 --- a/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache +++ b/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache @@ -65,6 +65,7 @@ namespace {{packageName}}.{{package}} {{#isDeprecated}} [Obsolete] {{/isDeprecated}} + // DNV customization: Original method name is renamed as ~NativeAsync() to avoid naming conflict Task<{{interfacePrefix}}{{operationId}}ApiResponse> {{operationId}}NativeAsync({{>OperationSignature}}); /// @@ -83,6 +84,7 @@ namespace {{packageName}}.{{package}} {{/isDeprecated}} Task<{{interfacePrefix}}{{operationId}}ApiResponse{{nrt?}}> {{operationId}}OrDefaultAsync({{>OperationSignature}}); + // DNV customization: Add {{operationId}}Async to return response DTO /// /// {{summary}} /// @@ -460,12 +462,15 @@ namespace {{packageName}} {{/required}} // DNV customization: non-required parameters are of type Option {{#isArray}} - foreach (var item in {{paramName}}{{^required}}.Value{{/required}}) + if ({{paramName}}{{#required}} != null{{/required}}{{^required}}.IsSet{{/required}}) { - var value = ClientUtils.ParameterToString(item); - if (!string.IsNullOrEmpty(value)) + foreach (var item in {{paramName}}{{^required}}.Value{{/required}}) { - parseQueryStringLocalVar["{{baseName}}"] = value; + var value = ClientUtils.ParameterToString(item); + if (!string.IsNullOrEmpty(value)) + { + parseQueryStringLocalVar["{{baseName}}"] = value; + } } } {{/isArray}} @@ -673,6 +678,7 @@ namespace {{packageName}} if (httpResponseMessageLocalVar.Content != null) { responseContentLocalVar = await httpResponseMessageLocalVar.Content.ReadAsStringAsync({{#net60OrLater}}cancellationToken{{/net60OrLater}}).ConfigureAwait(false); } + // DNV customization: Throw HttpOperationException for non-successful responses if (!httpResponseMessageLocalVar.IsSuccessStatusCode) { throw new HttpOperationException(string.Format("Operation returned an invalid status code '{0}'", httpResponseMessageLocalVar.StatusCode))