From 747519b0e7ab8a4707b5a4606f0b919ac7373b82 Mon Sep 17 00:00:00 2001
From: kwokhe <105217051+Kwok-he-Chu@users.noreply.github.com>
Date: Thu, 5 Jun 2025 11:34:34 +0200
Subject: [PATCH 001/126] Include templates
---
Adyen/Adyen.csproj | 4 +
.../csharp/AbstractOpenAPISchema.mustache | 68 ++
templates-v7/csharp/ApiClient.mustache | 846 ++++++++++++++++++
templates-v7/csharp/ApiClient.v790.mustache | 838 +++++++++++++++++
templates-v7/csharp/ApiException.mustache | 60 ++
templates-v7/csharp/ApiResponse.mustache | 158 ++++
templates-v7/csharp/AssemblyInfo.mustache | 40 +
templates-v7/csharp/ClientUtils.mustache | 269 ++++++
templates-v7/csharp/Configuration.mustache | 737 +++++++++++++++
.../csharp/Configuration.v790.mustache | 737 +++++++++++++++
templates-v7/csharp/ExceptionFactory.mustache | 14 +
.../csharp/GlobalConfiguration.mustache | 59 ++
templates-v7/csharp/HttpMethod.mustache | 25 +
.../csharp/HttpSigningConfiguration.mustache | 805 +++++++++++++++++
templates-v7/csharp/IApiAccessor.mustache | 29 +
.../csharp/IAsynchronousClient.mustache | 92 ++
.../csharp/IReadableConfiguration.mustache | 178 ++++
.../IReadableConfiguration.v790.mustache | 178 ++++
.../csharp/ISynchronousClient.mustache | 85 ++
.../csharp/JsonSubTypesTests.mustache | 125 +++
templates-v7/csharp/Multimap.mustache | 287 ++++++
.../csharp/NullConditionalParameter.mustache | 1 +
.../csharp/NullConditionalProperty.mustache | 1 +
.../csharp/OpenAPIDateConverter.mustache | 21 +
templates-v7/csharp/README.mustache | 267 ++++++
.../csharp/ReadOnlyDictionary.mustache | 137 +++
templates-v7/csharp/RequestOptions.mustache | 87 ++
.../csharp/RetryConfiguration.mustache | 41 +
templates-v7/csharp/Solution.mustache | 27 +
templates-v7/csharp/TestProject.mustache | 35 +
templates-v7/csharp/ValidateRegex.mustache | 6 +
.../csharp/WebRequestPathBuilder.mustache | 45 +
templates-v7/csharp/api.mustache | 799 +++++++++++++++++
templates-v7/csharp/api_doc.mustache | 162 ++++
templates-v7/csharp/api_test.mustache | 77 ++
templates-v7/csharp/appveyor.mustache | 9 +
.../csharp/auth/OAuthAuthenticator.mustache | 136 +++
templates-v7/csharp/auth/OAuthFlow.mustache | 19 +
.../csharp/auth/TokenResponse.mustache | 24 +
templates-v7/csharp/git_push.sh.mustache | 57 ++
templates-v7/csharp/gitignore.mustache | 362 ++++++++
...terOperationDefaultImplementation.mustache | 2 +
.../generichost/ApiException.mustache | 46 +
.../libraries/generichost/ApiFactory.mustache | 49 +
.../generichost/ApiKeyToken.mustache | 56 ++
.../ApiResponseEventArgs`1.mustache | 24 +
.../generichost/ApiResponse`1.mustache | 170 ++++
.../generichost/ApiTestsBase.mustache | 65 ++
.../libraries/generichost/AsModel.mustache | 4 +
.../libraries/generichost/Assembly.mustache | 2 +
.../libraries/generichost/BasicToken.mustache | 46 +
.../generichost/BearerToken.mustache | 41 +
.../generichost/ClientUtils.mustache | 404 +++++++++
.../generichost/CookieContainer.mustache | 22 +
.../generichost/DateFormats.mustache | 2 +
.../DateOnlyJsonConverter.mustache | 51 ++
.../DateOnlyNullableJsonConverter.mustache | 56 ++
.../generichost/DateTimeFormats.mustache | 22 +
.../DateTimeJsonConverter.mustache | 51 ++
.../DateTimeNullableJsonConverter.mustache | 56 ++
.../DependencyInjectionTests.mustache | 211 +++++
.../generichost/EnumValueDataType.mustache | 1 +
.../generichost/ExceptionEventArgs.mustache | 24 +
.../generichost/HostConfiguration.mustache | 166 ++++
.../HttpSigningConfiguration.mustache | 678 ++++++++++++++
.../generichost/HttpSigningToken.mustache | 45 +
.../libraries/generichost/IApi.mustache | 15 +
.../IHostBuilderExtensions.mustache | 55 ++
.../IHttpClientBuilderExtensions.mustache | 75 ++
.../IServiceCollectionExtensions.mustache | 69 ++
.../generichost/ImplementsIEquatable.mustache | 1 +
.../ImplementsValidatable.mustache | 1 +
.../generichost/JsonConverter.mustache | 648 ++++++++++++++
.../JsonSerializerOptionsProvider.mustache | 29 +
.../generichost/ModelBaseSignature.mustache | 1 +
.../generichost/ModelSignature.mustache | 1 +
.../libraries/generichost/OAuthToken.mustache | 41 +
.../OnDeserializationError.mustache | 2 +
.../OnErrorDefaultImplementation.mustache | 2 +
.../generichost/OperationSignature.mustache | 1 +
.../libraries/generichost/Option.mustache | 47 +
.../generichost/OptionProperty.mustache | 1 +
.../generichost/README.client.mustache | 245 +++++
.../generichost/README.solution.mustache | 1 +
.../generichost/README.test.mustache | 0
.../generichost/RateLimitProvider`1.mustache | 79 ++
.../SourceGenerationContext.mustache | 9 +
.../libraries/generichost/TokenBase.mustache | 73 ++
.../generichost/TokenContainer`1.mustache | 39 +
.../generichost/TokenProvider`1.mustache | 38 +
.../generichost/ValidateRegex.mustache | 7 +
.../generichost/WriteProperty.mustache | 9 +
.../generichost/WritePropertyHelper.mustache | 10 +
.../csharp/libraries/generichost/api.mustache | 803 +++++++++++++++++
.../libraries/generichost/api_test.mustache | 51 ++
.../generichost/git_push.ps1.mustache | 75 ++
.../generichost/git_push.sh.mustache | 49 +
.../libraries/generichost/model.mustache | 50 ++
.../generichost/modelGeneric.mustache | 393 ++++++++
.../generichost/testInstructions.mustache | 18 +
.../libraries/httpclient/ApiClient.mustache | 792 ++++++++++++++++
.../httpclient/FileParameter.mustache | 72 ++
.../httpclient/RequestOptions.mustache | 66 ++
.../csharp/libraries/httpclient/api.mustache | 766 ++++++++++++++++
.../libraries/httpclient/model.mustache | 51 ++
templates-v7/csharp/model.mustache | 50 ++
templates-v7/csharp/modelAnyOf.mustache | 275 ++++++
templates-v7/csharp/modelEnum.mustache | 185 ++++
templates-v7/csharp/modelGeneric.mustache | 432 +++++++++
templates-v7/csharp/modelInnerEnum.mustache | 99 ++
templates-v7/csharp/modelOneOf.mustache | 320 +++++++
templates-v7/csharp/model_doc.mustache | 22 +
templates-v7/csharp/model_test.mustache | 87 ++
.../csharp/netcore_project.additions.mustache | 1 +
templates-v7/csharp/netcore_project.mustache | 84 ++
.../netcore_testproject.additions.mustache | 1 +
.../csharp/netcore_testproject.mustache | 20 +
templates-v7/csharp/nuspec.mustache | 57 ++
templates-v7/csharp/openapi.mustache | 1 +
templates-v7/csharp/partial_header.mustache | 17 +
templates-v7/csharp/validatable.mustache | 135 +++
templates-v7/csharp/visibility.mustache | 1 +
122 files changed, 16713 insertions(+)
create mode 100644 templates-v7/csharp/AbstractOpenAPISchema.mustache
create mode 100644 templates-v7/csharp/ApiClient.mustache
create mode 100644 templates-v7/csharp/ApiClient.v790.mustache
create mode 100644 templates-v7/csharp/ApiException.mustache
create mode 100644 templates-v7/csharp/ApiResponse.mustache
create mode 100644 templates-v7/csharp/AssemblyInfo.mustache
create mode 100644 templates-v7/csharp/ClientUtils.mustache
create mode 100644 templates-v7/csharp/Configuration.mustache
create mode 100644 templates-v7/csharp/Configuration.v790.mustache
create mode 100644 templates-v7/csharp/ExceptionFactory.mustache
create mode 100644 templates-v7/csharp/GlobalConfiguration.mustache
create mode 100644 templates-v7/csharp/HttpMethod.mustache
create mode 100644 templates-v7/csharp/HttpSigningConfiguration.mustache
create mode 100644 templates-v7/csharp/IApiAccessor.mustache
create mode 100644 templates-v7/csharp/IAsynchronousClient.mustache
create mode 100644 templates-v7/csharp/IReadableConfiguration.mustache
create mode 100644 templates-v7/csharp/IReadableConfiguration.v790.mustache
create mode 100644 templates-v7/csharp/ISynchronousClient.mustache
create mode 100644 templates-v7/csharp/JsonSubTypesTests.mustache
create mode 100644 templates-v7/csharp/Multimap.mustache
create mode 100644 templates-v7/csharp/NullConditionalParameter.mustache
create mode 100644 templates-v7/csharp/NullConditionalProperty.mustache
create mode 100644 templates-v7/csharp/OpenAPIDateConverter.mustache
create mode 100644 templates-v7/csharp/README.mustache
create mode 100644 templates-v7/csharp/ReadOnlyDictionary.mustache
create mode 100644 templates-v7/csharp/RequestOptions.mustache
create mode 100644 templates-v7/csharp/RetryConfiguration.mustache
create mode 100644 templates-v7/csharp/Solution.mustache
create mode 100644 templates-v7/csharp/TestProject.mustache
create mode 100644 templates-v7/csharp/ValidateRegex.mustache
create mode 100644 templates-v7/csharp/WebRequestPathBuilder.mustache
create mode 100644 templates-v7/csharp/api.mustache
create mode 100644 templates-v7/csharp/api_doc.mustache
create mode 100644 templates-v7/csharp/api_test.mustache
create mode 100644 templates-v7/csharp/appveyor.mustache
create mode 100644 templates-v7/csharp/auth/OAuthAuthenticator.mustache
create mode 100644 templates-v7/csharp/auth/OAuthFlow.mustache
create mode 100644 templates-v7/csharp/auth/TokenResponse.mustache
create mode 100644 templates-v7/csharp/git_push.sh.mustache
create mode 100644 templates-v7/csharp/gitignore.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/AfterOperationDefaultImplementation.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/ApiException.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/ApiFactory.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/ApiKeyToken.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/ApiResponseEventArgs`1.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/ApiResponse`1.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/ApiTestsBase.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/AsModel.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/Assembly.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/BasicToken.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/BearerToken.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/ClientUtils.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/CookieContainer.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/DateFormats.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/DateOnlyJsonConverter.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/DateOnlyNullableJsonConverter.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/DateTimeFormats.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/DateTimeJsonConverter.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/DateTimeNullableJsonConverter.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/DependencyInjectionTests.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/EnumValueDataType.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/ExceptionEventArgs.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/HostConfiguration.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/HttpSigningConfiguration.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/HttpSigningToken.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/IApi.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/IHostBuilderExtensions.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/IHttpClientBuilderExtensions.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/IServiceCollectionExtensions.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/ImplementsIEquatable.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/ImplementsValidatable.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/JsonConverter.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/JsonSerializerOptionsProvider.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/ModelBaseSignature.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/ModelSignature.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/OAuthToken.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/OnDeserializationError.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/OnErrorDefaultImplementation.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/OperationSignature.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/Option.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/OptionProperty.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/README.client.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/README.solution.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/README.test.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/RateLimitProvider`1.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/SourceGenerationContext.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/TokenBase.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/TokenContainer`1.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/TokenProvider`1.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/ValidateRegex.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/WriteProperty.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/WritePropertyHelper.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/api.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/api_test.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/git_push.ps1.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/git_push.sh.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/model.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/modelGeneric.mustache
create mode 100644 templates-v7/csharp/libraries/generichost/testInstructions.mustache
create mode 100644 templates-v7/csharp/libraries/httpclient/ApiClient.mustache
create mode 100644 templates-v7/csharp/libraries/httpclient/FileParameter.mustache
create mode 100644 templates-v7/csharp/libraries/httpclient/RequestOptions.mustache
create mode 100644 templates-v7/csharp/libraries/httpclient/api.mustache
create mode 100644 templates-v7/csharp/libraries/httpclient/model.mustache
create mode 100644 templates-v7/csharp/model.mustache
create mode 100644 templates-v7/csharp/modelAnyOf.mustache
create mode 100644 templates-v7/csharp/modelEnum.mustache
create mode 100644 templates-v7/csharp/modelGeneric.mustache
create mode 100644 templates-v7/csharp/modelInnerEnum.mustache
create mode 100644 templates-v7/csharp/modelOneOf.mustache
create mode 100644 templates-v7/csharp/model_doc.mustache
create mode 100644 templates-v7/csharp/model_test.mustache
create mode 100644 templates-v7/csharp/netcore_project.additions.mustache
create mode 100644 templates-v7/csharp/netcore_project.mustache
create mode 100644 templates-v7/csharp/netcore_testproject.additions.mustache
create mode 100644 templates-v7/csharp/netcore_testproject.mustache
create mode 100644 templates-v7/csharp/nuspec.mustache
create mode 100644 templates-v7/csharp/openapi.mustache
create mode 100644 templates-v7/csharp/partial_header.mustache
create mode 100644 templates-v7/csharp/validatable.mustache
create mode 100644 templates-v7/csharp/visibility.mustache
diff --git a/Adyen/Adyen.csproj b/Adyen/Adyen.csproj
index fa180e07d..8dccb883d 100644
--- a/Adyen/Adyen.csproj
+++ b/Adyen/Adyen.csproj
@@ -54,4 +54,8 @@
+
+
+
+
diff --git a/templates-v7/csharp/AbstractOpenAPISchema.mustache b/templates-v7/csharp/AbstractOpenAPISchema.mustache
new file mode 100644
index 000000000..05e782049
--- /dev/null
+++ b/templates-v7/csharp/AbstractOpenAPISchema.mustache
@@ -0,0 +1,68 @@
+{{>partial_header}}
+
+using System;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace {{packageName}}.{{modelPackage}}
+{
+ ///
+ /// Abstract base class for oneOf, anyOf schemas in the OpenAPI specification
+ ///
+ {{>visibility}} abstract partial class AbstractOpenAPISchema
+ {
+ ///
+ /// Custom JSON serializer
+ ///
+ static public readonly JsonSerializerSettings SerializerSettings = new JsonSerializerSettings
+ {
+ // OpenAPI generated types generally hide default constructors.
+ ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
+ MissingMemberHandling = MissingMemberHandling.Error,
+ ContractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new CamelCaseNamingStrategy
+ {
+ OverrideSpecifiedNames = false
+ }
+ }
+ };
+
+ ///
+ /// Custom JSON serializer for objects with additional properties
+ ///
+ static public readonly JsonSerializerSettings AdditionalPropertiesSerializerSettings = new JsonSerializerSettings
+ {
+ // OpenAPI generated types generally hide default constructors.
+ ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
+ MissingMemberHandling = MissingMemberHandling.Ignore,
+ ContractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new CamelCaseNamingStrategy
+ {
+ OverrideSpecifiedNames = false
+ }
+ }
+ };
+
+ ///
+ /// Gets or Sets the actual instance
+ ///
+ public abstract Object ActualInstance { get; set; }
+
+ ///
+ /// Gets or Sets IsNullable to indicate whether the instance is nullable
+ ///
+ public bool IsNullable { get; protected set; }
+
+ ///
+ /// Gets or Sets the schema type, which can be either `oneOf` or `anyOf`
+ ///
+ public string SchemaType { get; protected set; }
+
+ ///
+ /// Converts the instance into JSON string.
+ ///
+ public abstract string ToJson();
+ }
+}
diff --git a/templates-v7/csharp/ApiClient.mustache b/templates-v7/csharp/ApiClient.mustache
new file mode 100644
index 000000000..a96011d70
--- /dev/null
+++ b/templates-v7/csharp/ApiClient.mustache
@@ -0,0 +1,846 @@
+{{>partial_header}}
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Reflection;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Formatters;
+using System.Text;
+using System.Threading;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+{{^net60OrLater}}
+using System.Web;
+{{/net60OrLater}}
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using RestSharp;
+using RestSharp.Serializers;
+using RestSharpMethod = RestSharp.Method;
+using FileIO = System.IO.File;
+{{#supportsRetry}}
+using Polly;
+{{/supportsRetry}}
+{{#hasOAuthMethods}}
+using {{packageName}}.Client.Auth;
+{{/hasOAuthMethods}}
+using {{packageName}}.{{modelPackage}};
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// Allows RestSharp to Serialize/Deserialize JSON using our custom logic, but only when ContentType is JSON.
+ ///
+ internal class CustomJsonCodec : IRestSerializer, ISerializer, IDeserializer
+ {
+ private readonly IReadableConfiguration _configuration;
+ private readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings
+ {
+ // OpenAPI generated types generally hide default constructors.
+ ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
+ ContractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new CamelCaseNamingStrategy
+ {
+ OverrideSpecifiedNames = false
+ }
+ }
+ };
+
+ public CustomJsonCodec(IReadableConfiguration configuration)
+ {
+ _configuration = configuration;
+ }
+
+ public CustomJsonCodec(JsonSerializerSettings serializerSettings, IReadableConfiguration configuration)
+ {
+ _serializerSettings = serializerSettings;
+ _configuration = configuration;
+ }
+
+ ///
+ /// Serialize the object into a JSON string.
+ ///
+ /// Object to be serialized.
+ /// A JSON string.
+ public string Serialize(object obj)
+ {
+ if (obj != null && obj is AbstractOpenAPISchema)
+ {
+ // the object to be serialized is an oneOf/anyOf schema
+ return ((AbstractOpenAPISchema)obj).ToJson();
+ }
+ else
+ {
+ return JsonConvert.SerializeObject(obj, _serializerSettings);
+ }
+ }
+
+ public string Serialize(Parameter bodyParameter) => Serialize(bodyParameter.Value);
+
+ public T Deserialize(RestResponse response)
+ {
+ var result = (T)Deserialize(response, typeof(T));
+ return result;
+ }
+
+ ///
+ /// Deserialize the JSON string into a proper object.
+ ///
+ /// The HTTP response.
+ /// Object type.
+ /// Object representation of the JSON string.
+ internal object Deserialize(RestResponse response, Type type)
+ {
+ if (type == typeof(byte[])) // return byte array
+ {
+ return response.RawBytes;
+ }
+
+ // TODO: ? if (type.IsAssignableFrom(typeof(Stream)))
+ if (type == typeof(Stream))
+ {
+ var bytes = response.RawBytes;
+ if (response.Headers != null)
+ {
+ var filePath = string.IsNullOrEmpty(_configuration.TempFolderPath)
+ ? global::System.IO.Path.GetTempPath()
+ : _configuration.TempFolderPath;
+ var regex = new Regex(@"Content-Disposition=.*filename=['""]?([^'""\s]+)['""]?$");
+ foreach (var header in response.Headers)
+ {
+ var match = regex.Match(header.ToString());
+ if (match.Success)
+ {
+ string fileName = filePath + ClientUtils.SanitizeFilename(match.Groups[1].Value.Replace("\"", "").Replace("'", ""));
+ FileIO.WriteAllBytes(fileName, bytes);
+ return new FileStream(fileName, FileMode.Open);
+ }
+ }
+ }
+ var stream = new MemoryStream(bytes);
+ return stream;
+ }
+
+ if (type.Name.StartsWith("System.Nullable`1[[System.DateTime")) // return a datetime object
+ {
+ return DateTime.Parse(response.Content, null, DateTimeStyles.RoundtripKind);
+ }
+
+ if (type == typeof(string) || type.Name.StartsWith("System.Nullable")) // return primitive type
+ {
+ return Convert.ChangeType(response.Content, type);
+ }
+
+ // at this point, it must be a model (json)
+ try
+ {
+ return JsonConvert.DeserializeObject(response.Content, type, _serializerSettings);
+ }
+ catch (Exception e)
+ {
+ throw new ApiException(500, e.Message);
+ }
+ }
+
+ public ISerializer Serializer => this;
+ public IDeserializer Deserializer => this;
+
+ public string[] AcceptedContentTypes => ContentType.JsonAccept;
+
+ public SupportsContentType SupportsContentType => contentType =>
+ contentType.Value.EndsWith("json", StringComparison.InvariantCultureIgnoreCase) ||
+ contentType.Value.EndsWith("javascript", StringComparison.InvariantCultureIgnoreCase);
+
+ public ContentType ContentType { get; set; } = ContentType.Json;
+
+ public DataFormat DataFormat => DataFormat.Json;
+ }
+ {{! NOTE: Any changes related to RestSharp should be done in this class. All other client classes are for extensibility by consumers.}}
+ ///
+ /// Provides a default implementation of an Api client (both synchronous and asynchronous implementations),
+ /// encapsulating general REST accessor use cases.
+ ///
+ {{>visibility}} partial class ApiClient : ISynchronousClient{{#supportsAsync}}, IAsynchronousClient{{/supportsAsync}}
+ {
+ private readonly string _baseUrl;
+
+ ///
+ /// Specifies the settings on a object.
+ /// These settings can be adjusted to accommodate custom serialization rules.
+ ///
+ public JsonSerializerSettings SerializerSettings { get; set; } = new JsonSerializerSettings
+ {
+ // OpenAPI generated types generally hide default constructors.
+ ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
+ ContractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new CamelCaseNamingStrategy
+ {
+ OverrideSpecifiedNames = false
+ }
+ }
+ };
+
+ ///
+ /// Allows for extending request processing for generated code.
+ ///
+ /// The RestSharp request object
+ {{#useVirtualForHooks}}public virtual{{/useVirtualForHooks}}{{^useVirtualForHooks}}partial{{/useVirtualForHooks}} void InterceptRequest(RestRequest request){{#useVirtualForHooks}} { }{{/useVirtualForHooks}}{{^useVirtualForHooks}};{{/useVirtualForHooks}}
+
+ ///
+ /// Allows for extending response processing for generated code.
+ ///
+ /// The RestSharp request object
+ /// The RestSharp response object
+ {{#useVirtualForHooks}}public virtual{{/useVirtualForHooks}}{{^useVirtualForHooks}}partial{{/useVirtualForHooks}} void InterceptResponse(RestRequest request, RestResponse response){{#useVirtualForHooks}} { }{{/useVirtualForHooks}}{{^useVirtualForHooks}};{{/useVirtualForHooks}}
+
+ ///
+ /// Initializes a new instance of the , defaulting to the global configurations' base url.
+ ///
+ public ApiClient()
+ {
+ _baseUrl = GlobalConfiguration.Instance.BasePath;
+ }
+
+ ///
+ /// Initializes a new instance of the
+ ///
+ /// The target service's base path in URL format.
+ ///
+ public ApiClient(string basePath)
+ {
+ if (string.IsNullOrEmpty(basePath))
+ throw new ArgumentException("basePath cannot be empty");
+
+ _baseUrl = basePath;
+ }
+
+ ///
+ /// Constructs the RestSharp version of an http method
+ ///
+ /// Swagger Client Custom HttpMethod
+ /// RestSharp's HttpMethod instance.
+ ///
+ private RestSharpMethod Method(HttpMethod method)
+ {
+ RestSharpMethod other;
+ switch (method)
+ {
+ case HttpMethod.Get:
+ other = RestSharpMethod.Get;
+ break;
+ case HttpMethod.Post:
+ other = RestSharpMethod.Post;
+ break;
+ case HttpMethod.Put:
+ other = RestSharpMethod.Put;
+ break;
+ case HttpMethod.Delete:
+ other = RestSharpMethod.Delete;
+ break;
+ case HttpMethod.Head:
+ other = RestSharpMethod.Head;
+ break;
+ case HttpMethod.Options:
+ other = RestSharpMethod.Options;
+ break;
+ case HttpMethod.Patch:
+ other = RestSharpMethod.Patch;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException("method", method, null);
+ }
+
+ return other;
+ }
+
+ ///
+ /// Provides all logic for constructing a new RestSharp .
+ /// At this point, all information for querying the service is known.
+ /// Here, it is simply mapped into the RestSharp request.
+ ///
+ /// The http verb.
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object.
+ /// It is assumed that any merge with GlobalConfiguration has been done before calling this method.
+ /// [private] A new RestRequest instance.
+ ///
+ private RestRequest NewRequest(
+ HttpMethod method,
+ string path,
+ RequestOptions options,
+ IReadableConfiguration configuration)
+ {
+ if (path == null) throw new ArgumentNullException("path");
+ if (options == null) throw new ArgumentNullException("options");
+ if (configuration == null) throw new ArgumentNullException("configuration");
+
+ RestRequest request = new RestRequest(path, Method(method));
+
+ if (options.PathParameters != null)
+ {
+ foreach (var pathParam in options.PathParameters)
+ {
+ request.AddParameter(pathParam.Key, pathParam.Value, ParameterType.UrlSegment);
+ }
+ }
+
+ if (options.QueryParameters != null)
+ {
+ foreach (var queryParam in options.QueryParameters)
+ {
+ foreach (var value in queryParam.Value)
+ {
+ request.AddQueryParameter(queryParam.Key, value);
+ }
+ }
+ }
+
+ if (configuration.DefaultHeaders != null)
+ {
+ foreach (var headerParam in configuration.DefaultHeaders)
+ {
+ request.AddHeader(headerParam.Key, headerParam.Value);
+ }
+ }
+
+ if (options.HeaderParameters != null)
+ {
+ foreach (var headerParam in options.HeaderParameters)
+ {
+ foreach (var value in headerParam.Value)
+ {
+ request.AddOrUpdateHeader(headerParam.Key, value);
+ }
+ }
+ }
+
+ if (options.FormParameters != null)
+ {
+ foreach (var formParam in options.FormParameters)
+ {
+ request.AddParameter(formParam.Key, formParam.Value);
+ }
+ }
+
+ if (options.Data != null)
+ {
+ if (options.Data is Stream stream)
+ {
+ var contentType = "application/octet-stream";
+ if (options.HeaderParameters != null)
+ {
+ var contentTypes = options.HeaderParameters["Content-Type"];
+ contentType = contentTypes[0];
+ }
+
+ var bytes = ClientUtils.ReadAsBytes(stream);
+ request.AddParameter(contentType, bytes, ParameterType.RequestBody);
+ }
+ else
+ {
+ if (options.HeaderParameters != null)
+ {
+ var contentTypes = options.HeaderParameters["Content-Type"];
+ if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json")))
+ {
+ request.RequestFormat = DataFormat.Json;
+ }
+ else
+ {
+ // TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default.
+ }
+ }
+ else
+ {
+ // Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly.
+ request.RequestFormat = DataFormat.Json;
+ }
+
+ request.AddJsonBody(options.Data);
+ }
+ }
+
+ if (options.FileParameters != null)
+ {
+ foreach (var fileParam in options.FileParameters)
+ {
+ foreach (var file in fileParam.Value)
+ {
+ var bytes = ClientUtils.ReadAsBytes(file);
+ var fileStream = file as FileStream;
+ if (fileStream != null)
+ request.AddFile(fileParam.Key, bytes, global::System.IO.Path.GetFileName(fileStream.Name));
+ else
+ request.AddFile(fileParam.Key, bytes, "no_file_name_provided");
+ }
+ }
+ }
+
+ if (options.HeaderParameters != null)
+ {
+ if (options.HeaderParameters.TryGetValue("Content-Type", out var contentTypes) && contentTypes.Any(header => header.Contains("multipart/form-data")))
+ {
+ request.AlwaysMultipartFormData = true;
+ }
+ }
+
+ return request;
+ }
+
+ ///
+ /// Transforms a RestResponse instance into a new ApiResponse instance.
+ /// At this point, we have a concrete http response from the service.
+ /// Here, it is simply mapped into the [public] ApiResponse object.
+ ///
+ /// The RestSharp response object
+ /// A new ApiResponse instance.
+ private ApiResponse ToApiResponse(RestResponse response)
+ {
+ T result = response.Data;
+ string rawContent = response.Content;
+
+ var transformed = new ApiResponse(response.StatusCode, new Multimap({{#caseInsensitiveResponseHeaders}}StringComparer.OrdinalIgnoreCase{{/caseInsensitiveResponseHeaders}}), result, rawContent)
+ {
+ ErrorText = response.ErrorMessage,
+ Cookies = new List()
+ };
+
+ if (response.Headers != null)
+ {
+ foreach (var responseHeader in response.Headers)
+ {
+ transformed.Headers.Add(responseHeader.Name, ClientUtils.ParameterToString(responseHeader.Value));
+ }
+ }
+
+ if (response.ContentHeaders != null)
+ {
+ foreach (var responseHeader in response.ContentHeaders)
+ {
+ transformed.Headers.Add(responseHeader.Name, ClientUtils.ParameterToString(responseHeader.Value));
+ }
+ }
+
+ if (response.Cookies != null)
+ {
+ foreach (var responseCookies in response.Cookies.Cast())
+ {
+ transformed.Cookies.Add(
+ new Cookie(
+ responseCookies.Name,
+ responseCookies.Value,
+ responseCookies.Path,
+ responseCookies.Domain)
+ );
+ }
+ }
+
+ return transformed;
+ }
+
+ ///
+ /// Executes the HTTP request for the current service.
+ /// Based on functions received it can be async or sync.
+ ///
+ /// Local function that executes http request and returns http response.
+ /// Local function to specify options for the service.
+ /// The RestSharp request object
+ /// The RestSharp options object
+ /// A per-request configuration object.
+ /// It is assumed that any merge with GlobalConfiguration has been done before calling this method.
+ /// A new ApiResponse instance.
+ private async Task> ExecClientAsync(Func>> getResponse, Action setOptions, RestRequest request, RequestOptions options, IReadableConfiguration configuration)
+ {
+ var baseUrl = configuration.GetOperationServerUrl(options.Operation, options.OperationIndex) ?? _baseUrl;
+ var clientOptions = new RestClientOptions(baseUrl)
+ {
+ ClientCertificates = configuration.ClientCertificates,
+ Timeout = configuration.Timeout,
+ Proxy = configuration.Proxy,
+ UserAgent = configuration.UserAgent,
+ UseDefaultCredentials = configuration.UseDefaultCredentials,
+ RemoteCertificateValidationCallback = configuration.RemoteCertificateValidationCallback
+ };
+ setOptions(clientOptions);
+
+ {{#hasOAuthMethods}}
+ if (!string.IsNullOrEmpty(configuration.OAuthTokenUrl) &&
+ !string.IsNullOrEmpty(configuration.OAuthClientId) &&
+ !string.IsNullOrEmpty(configuration.OAuthClientSecret) &&
+ configuration.OAuthFlow != null)
+ {
+ clientOptions.Authenticator = new OAuthAuthenticator(
+ configuration.OAuthTokenUrl,
+ configuration.OAuthClientId,
+ configuration.OAuthClientSecret,
+ configuration.OAuthScope,
+ configuration.OAuthFlow,
+ SerializerSettings,
+ configuration);
+ }
+
+ {{/hasOAuthMethods}}
+ using (RestClient client = new RestClient(clientOptions,
+ configureSerialization: serializerConfig => serializerConfig.UseSerializer(() => new CustomJsonCodec(SerializerSettings, configuration))))
+ {
+ InterceptRequest(request);
+
+ RestResponse response = await getResponse(client);
+
+ // if the response type is oneOf/anyOf, call FromJSON to deserialize the data
+ if (typeof(AbstractOpenAPISchema).IsAssignableFrom(typeof(T)))
+ {
+ try
+ {
+ response.Data = (T)typeof(T).GetMethod("FromJson").Invoke(null, new object[] { response.Content });
+ }
+ catch (Exception ex)
+ {
+ throw ex.InnerException != null ? ex.InnerException : ex;
+ }
+ }
+ else if (typeof(T).Name == "Stream") // for binary response
+ {
+ response.Data = (T)(object)new MemoryStream(response.RawBytes);
+ }
+ else if (typeof(T).Name == "Byte[]") // for byte response
+ {
+ response.Data = (T)(object)response.RawBytes;
+ }
+ else if (typeof(T).Name == "String") // for string response
+ {
+ response.Data = (T)(object)response.Content;
+ }
+
+ InterceptResponse(request, response);
+
+ var result = ToApiResponse(response);
+ if (response.ErrorMessage != null)
+ {
+ result.ErrorText = response.ErrorMessage;
+ }
+
+ if (response.Cookies != null && response.Cookies.Count > 0)
+ {
+ if (result.Cookies == null) result.Cookies = new List();
+ foreach (var restResponseCookie in response.Cookies.Cast())
+ {
+ var cookie = new Cookie(
+ restResponseCookie.Name,
+ restResponseCookie.Value,
+ restResponseCookie.Path,
+ restResponseCookie.Domain
+ )
+ {
+ Comment = restResponseCookie.Comment,
+ CommentUri = restResponseCookie.CommentUri,
+ Discard = restResponseCookie.Discard,
+ Expired = restResponseCookie.Expired,
+ Expires = restResponseCookie.Expires,
+ HttpOnly = restResponseCookie.HttpOnly,
+ Port = restResponseCookie.Port,
+ Secure = restResponseCookie.Secure,
+ Version = restResponseCookie.Version
+ };
+
+ result.Cookies.Add(cookie);
+ }
+ }
+ return result;
+ }
+ }
+
+ private async Task> DeserializeRestResponseFromPolicyAsync(RestClient client, RestRequest request, PolicyResult policyResult, CancellationToken cancellationToken = default)
+ {
+ if (policyResult.Outcome == OutcomeType.Successful)
+ {
+ return await client.Deserialize(policyResult.Result, cancellationToken);
+ }
+ else
+ {
+ return new RestResponse(request)
+ {
+ ErrorException = policyResult.FinalException
+ };
+ }
+ }
+
+ private ApiResponse Exec(RestRequest request, RequestOptions options, IReadableConfiguration configuration)
+ {
+ Action setOptions = (clientOptions) =>
+ {
+ var cookies = new CookieContainer();
+
+ if (options.Cookies != null && options.Cookies.Count > 0)
+ {
+ foreach (var cookie in options.Cookies)
+ {
+ cookies.Add(new Cookie(cookie.Name, cookie.Value));
+ }
+ }
+ clientOptions.CookieContainer = cookies;
+ };
+
+ Func>> getResponse = (client) =>
+ {
+ if (RetryConfiguration.RetryPolicy != null)
+ {
+ var policy = RetryConfiguration.RetryPolicy;
+ var policyResult = policy.ExecuteAndCapture(() => client.Execute(request));
+ return DeserializeRestResponseFromPolicyAsync(client, request, policyResult);
+ }
+ else
+ {
+ return Task.FromResult(client.Execute(request));
+ }
+ };
+
+ return ExecClientAsync(getResponse, setOptions, request, options, configuration).GetAwaiter().GetResult();
+ }
+
+ {{#supportsAsync}}
+ private Task> ExecAsync(RestRequest request, RequestOptions options, IReadableConfiguration configuration, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ Action setOptions = (clientOptions) =>
+ {
+ //no extra options
+ };
+
+ Func>> getResponse = async (client) =>
+ {
+ {{#supportsRetry}}
+ if (RetryConfiguration.AsyncRetryPolicy != null)
+ {
+ var policy = RetryConfiguration.AsyncRetryPolicy;
+ var policyResult = await policy.ExecuteAndCaptureAsync((ct) => client.ExecuteAsync(request, ct), cancellationToken).ConfigureAwait(false);
+ return await DeserializeRestResponseFromPolicyAsync(client, request, policyResult, cancellationToken);
+ }
+ else
+ {
+ {{/supportsRetry}}
+ return await client.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
+ {{#supportsRetry}}
+ }
+ {{/supportsRetry}}
+ };
+
+ return ExecClientAsync(getResponse, setOptions, request, options, configuration);
+ }
+
+ #region IAsynchronousClient
+ ///
+ /// Make a HTTP GET request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> GetAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Get, path, options, config), options, config, cancellationToken);
+ }
+
+ ///
+ /// Make a HTTP POST request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> PostAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Post, path, options, config), options, config, cancellationToken);
+ }
+
+ ///
+ /// Make a HTTP PUT request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> PutAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Put, path, options, config), options, config, cancellationToken);
+ }
+
+ ///
+ /// Make a HTTP DELETE request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> DeleteAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Delete, path, options, config), options, config, cancellationToken);
+ }
+
+ ///
+ /// Make a HTTP HEAD request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> HeadAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Head, path, options, config), options, config, cancellationToken);
+ }
+
+ ///
+ /// Make a HTTP OPTION request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> OptionsAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Options, path, options, config), options, config, cancellationToken);
+ }
+
+ ///
+ /// Make a HTTP PATCH request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> PatchAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Patch, path, options, config), options, config, cancellationToken);
+ }
+ #endregion IAsynchronousClient
+ {{/supportsAsync}}
+
+ #region ISynchronousClient
+ ///
+ /// Make a HTTP GET request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Get(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Get, path, options, config), options, config);
+ }
+
+ ///
+ /// Make a HTTP POST request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Post(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Post, path, options, config), options, config);
+ }
+
+ ///
+ /// Make a HTTP PUT request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Put(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Put, path, options, config), options, config);
+ }
+
+ ///
+ /// Make a HTTP DELETE request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Delete(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Delete, path, options, config), options, config);
+ }
+
+ ///
+ /// Make a HTTP HEAD request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Head(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Head, path, options, config), options, config);
+ }
+
+ ///
+ /// Make a HTTP OPTION request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Options(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Options, path, options, config), options, config);
+ }
+
+ ///
+ /// Make a HTTP PATCH request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Patch(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Patch, path, options, config), options, config);
+ }
+ #endregion ISynchronousClient
+ }
+}
diff --git a/templates-v7/csharp/ApiClient.v790.mustache b/templates-v7/csharp/ApiClient.v790.mustache
new file mode 100644
index 000000000..a63d1c2be
--- /dev/null
+++ b/templates-v7/csharp/ApiClient.v790.mustache
@@ -0,0 +1,838 @@
+{{>partial_header}}
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Reflection;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Formatters;
+using System.Text;
+using System.Threading;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+{{^net60OrLater}}
+using System.Web;
+{{/net60OrLater}}
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using RestSharp;
+using RestSharp.Serializers;
+using RestSharpMethod = RestSharp.Method;
+using FileIO = System.IO.File;
+{{#supportsRetry}}
+using Polly;
+{{/supportsRetry}}
+{{#hasOAuthMethods}}
+using {{packageName}}.Client.Auth;
+{{/hasOAuthMethods}}
+using {{packageName}}.{{modelPackage}};
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// Allows RestSharp to Serialize/Deserialize JSON using our custom logic, but only when ContentType is JSON.
+ ///
+ internal class CustomJsonCodec : IRestSerializer, ISerializer, IDeserializer
+ {
+ private readonly IReadableConfiguration _configuration;
+ private readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings
+ {
+ // OpenAPI generated types generally hide default constructors.
+ ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
+ ContractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new CamelCaseNamingStrategy
+ {
+ OverrideSpecifiedNames = false
+ }
+ }
+ };
+
+ public CustomJsonCodec(IReadableConfiguration configuration)
+ {
+ _configuration = configuration;
+ }
+
+ public CustomJsonCodec(JsonSerializerSettings serializerSettings, IReadableConfiguration configuration)
+ {
+ _serializerSettings = serializerSettings;
+ _configuration = configuration;
+ }
+
+ ///
+ /// Serialize the object into a JSON string.
+ ///
+ /// Object to be serialized.
+ /// A JSON string.
+ public string Serialize(object obj)
+ {
+ if (obj != null && obj is AbstractOpenAPISchema)
+ {
+ // the object to be serialized is an oneOf/anyOf schema
+ return ((AbstractOpenAPISchema)obj).ToJson();
+ }
+ else
+ {
+ return JsonConvert.SerializeObject(obj, _serializerSettings);
+ }
+ }
+
+ public string Serialize(Parameter bodyParameter) => Serialize(bodyParameter.Value);
+
+ public T Deserialize(RestResponse response)
+ {
+ var result = (T)Deserialize(response, typeof(T));
+ return result;
+ }
+
+ ///
+ /// Deserialize the JSON string into a proper object.
+ ///
+ /// The HTTP response.
+ /// Object type.
+ /// Object representation of the JSON string.
+ internal object Deserialize(RestResponse response, Type type)
+ {
+ if (type == typeof(byte[])) // return byte array
+ {
+ return response.RawBytes;
+ }
+
+ // TODO: ? if (type.IsAssignableFrom(typeof(Stream)))
+ if (type == typeof(Stream))
+ {
+ var bytes = response.RawBytes;
+ if (response.Headers != null)
+ {
+ var filePath = string.IsNullOrEmpty(_configuration.TempFolderPath)
+ ? global::System.IO.Path.GetTempPath()
+ : _configuration.TempFolderPath;
+ var regex = new Regex(@"Content-Disposition=.*filename=['""]?([^'""\s]+)['""]?$");
+ foreach (var header in response.Headers)
+ {
+ var match = regex.Match(header.ToString());
+ if (match.Success)
+ {
+ string fileName = filePath + ClientUtils.SanitizeFilename(match.Groups[1].Value.Replace("\"", "").Replace("'", ""));
+ FileIO.WriteAllBytes(fileName, bytes);
+ return new FileStream(fileName, FileMode.Open);
+ }
+ }
+ }
+ var stream = new MemoryStream(bytes);
+ return stream;
+ }
+
+ if (type.Name.StartsWith("System.Nullable`1[[System.DateTime")) // return a datetime object
+ {
+ return DateTime.Parse(response.Content, null, DateTimeStyles.RoundtripKind);
+ }
+
+ if (type == typeof(string) || type.Name.StartsWith("System.Nullable")) // return primitive type
+ {
+ return Convert.ChangeType(response.Content, type);
+ }
+
+ // at this point, it must be a model (json)
+ try
+ {
+ return JsonConvert.DeserializeObject(response.Content, type, _serializerSettings);
+ }
+ catch (Exception e)
+ {
+ throw new ApiException(500, e.Message);
+ }
+ }
+
+ public ISerializer Serializer => this;
+ public IDeserializer Deserializer => this;
+
+ public string[] AcceptedContentTypes => ContentType.JsonAccept;
+
+ public SupportsContentType SupportsContentType => contentType =>
+ contentType.Value.EndsWith("json", StringComparison.InvariantCultureIgnoreCase) ||
+ contentType.Value.EndsWith("javascript", StringComparison.InvariantCultureIgnoreCase);
+
+ public ContentType ContentType { get; set; } = ContentType.Json;
+
+ public DataFormat DataFormat => DataFormat.Json;
+ }
+ {{! NOTE: Any changes related to RestSharp should be done in this class. All other client classes are for extensibility by consumers.}}
+ ///
+ /// Provides a default implementation of an Api client (both synchronous and asynchronous implementations),
+ /// encapsulating general REST accessor use cases.
+ ///
+ {{>visibility}} partial class ApiClient : ISynchronousClient{{#supportsAsync}}, IAsynchronousClient{{/supportsAsync}}
+ {
+ private readonly string _baseUrl;
+
+ ///
+ /// Specifies the settings on a object.
+ /// These settings can be adjusted to accommodate custom serialization rules.
+ ///
+ public JsonSerializerSettings SerializerSettings { get; set; } = new JsonSerializerSettings
+ {
+ // OpenAPI generated types generally hide default constructors.
+ ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
+ ContractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new CamelCaseNamingStrategy
+ {
+ OverrideSpecifiedNames = false
+ }
+ }
+ };
+
+ ///
+ /// Allows for extending request processing for generated code.
+ ///
+ /// The RestSharp request object
+ partial void InterceptRequest(RestRequest request);
+
+ ///
+ /// Allows for extending response processing for generated code.
+ ///
+ /// The RestSharp request object
+ /// The RestSharp response object
+ partial void InterceptResponse(RestRequest request, RestResponse response);
+
+ ///
+ /// Initializes a new instance of the , defaulting to the global configurations' base url.
+ ///
+ public ApiClient()
+ {
+ _baseUrl = GlobalConfiguration.Instance.BasePath;
+ }
+
+ ///
+ /// Initializes a new instance of the
+ ///
+ /// The target service's base path in URL format.
+ ///
+ public ApiClient(string basePath)
+ {
+ if (string.IsNullOrEmpty(basePath))
+ throw new ArgumentException("basePath cannot be empty");
+
+ _baseUrl = basePath;
+ }
+
+ ///
+ /// Constructs the RestSharp version of an http method
+ ///
+ /// Swagger Client Custom HttpMethod
+ /// RestSharp's HttpMethod instance.
+ ///
+ private RestSharpMethod Method(HttpMethod method)
+ {
+ RestSharpMethod other;
+ switch (method)
+ {
+ case HttpMethod.Get:
+ other = RestSharpMethod.Get;
+ break;
+ case HttpMethod.Post:
+ other = RestSharpMethod.Post;
+ break;
+ case HttpMethod.Put:
+ other = RestSharpMethod.Put;
+ break;
+ case HttpMethod.Delete:
+ other = RestSharpMethod.Delete;
+ break;
+ case HttpMethod.Head:
+ other = RestSharpMethod.Head;
+ break;
+ case HttpMethod.Options:
+ other = RestSharpMethod.Options;
+ break;
+ case HttpMethod.Patch:
+ other = RestSharpMethod.Patch;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException("method", method, null);
+ }
+
+ return other;
+ }
+
+ ///
+ /// Provides all logic for constructing a new RestSharp .
+ /// At this point, all information for querying the service is known.
+ /// Here, it is simply mapped into the RestSharp request.
+ ///
+ /// The http verb.
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object.
+ /// It is assumed that any merge with GlobalConfiguration has been done before calling this method.
+ /// [private] A new RestRequest instance.
+ ///
+ private RestRequest NewRequest(
+ HttpMethod method,
+ string path,
+ RequestOptions options,
+ IReadableConfiguration configuration)
+ {
+ if (path == null) throw new ArgumentNullException("path");
+ if (options == null) throw new ArgumentNullException("options");
+ if (configuration == null) throw new ArgumentNullException("configuration");
+
+ RestRequest request = new RestRequest(path, Method(method));
+
+ if (options.PathParameters != null)
+ {
+ foreach (var pathParam in options.PathParameters)
+ {
+ request.AddParameter(pathParam.Key, pathParam.Value, ParameterType.UrlSegment);
+ }
+ }
+
+ if (options.QueryParameters != null)
+ {
+ foreach (var queryParam in options.QueryParameters)
+ {
+ foreach (var value in queryParam.Value)
+ {
+ request.AddQueryParameter(queryParam.Key, value);
+ }
+ }
+ }
+
+ if (configuration.DefaultHeaders != null)
+ {
+ foreach (var headerParam in configuration.DefaultHeaders)
+ {
+ request.AddHeader(headerParam.Key, headerParam.Value);
+ }
+ }
+
+ if (options.HeaderParameters != null)
+ {
+ foreach (var headerParam in options.HeaderParameters)
+ {
+ foreach (var value in headerParam.Value)
+ {
+ request.AddHeader(headerParam.Key, value);
+ }
+ }
+ }
+
+ if (options.FormParameters != null)
+ {
+ foreach (var formParam in options.FormParameters)
+ {
+ request.AddParameter(formParam.Key, formParam.Value);
+ }
+ }
+
+ if (options.Data != null)
+ {
+ if (options.Data is Stream stream)
+ {
+ var contentType = "application/octet-stream";
+ if (options.HeaderParameters != null)
+ {
+ var contentTypes = options.HeaderParameters["Content-Type"];
+ contentType = contentTypes[0];
+ }
+
+ var bytes = ClientUtils.ReadAsBytes(stream);
+ request.AddParameter(contentType, bytes, ParameterType.RequestBody);
+ }
+ else
+ {
+ if (options.HeaderParameters != null)
+ {
+ var contentTypes = options.HeaderParameters["Content-Type"];
+ if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json")))
+ {
+ request.RequestFormat = DataFormat.Json;
+ }
+ else
+ {
+ // TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default.
+ }
+ }
+ else
+ {
+ // Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly.
+ request.RequestFormat = DataFormat.Json;
+ }
+
+ request.AddJsonBody(options.Data);
+ }
+ }
+
+ if (options.FileParameters != null)
+ {
+ foreach (var fileParam in options.FileParameters)
+ {
+ foreach (var file in fileParam.Value)
+ {
+ var bytes = ClientUtils.ReadAsBytes(file);
+ var fileStream = file as FileStream;
+ if (fileStream != null)
+ request.AddFile(fileParam.Key, bytes, global::System.IO.Path.GetFileName(fileStream.Name));
+ else
+ request.AddFile(fileParam.Key, bytes, "no_file_name_provided");
+ }
+ }
+ }
+
+ return request;
+ }
+
+ ///
+ /// Transforms a RestResponse instance into a new ApiResponse instance.
+ /// At this point, we have a concrete http response from the service.
+ /// Here, it is simply mapped into the [public] ApiResponse object.
+ ///
+ /// The RestSharp response object
+ /// A new ApiResponse instance.
+ private ApiResponse ToApiResponse(RestResponse response)
+ {
+ T result = response.Data;
+ string rawContent = response.Content;
+
+ var transformed = new ApiResponse(response.StatusCode, new Multimap({{#caseInsensitiveResponseHeaders}}StringComparer.OrdinalIgnoreCase{{/caseInsensitiveResponseHeaders}}), result, rawContent)
+ {
+ ErrorText = response.ErrorMessage,
+ Cookies = new List()
+ };
+
+ if (response.Headers != null)
+ {
+ foreach (var responseHeader in response.Headers)
+ {
+ transformed.Headers.Add(responseHeader.Name, ClientUtils.ParameterToString(responseHeader.Value));
+ }
+ }
+
+ if (response.ContentHeaders != null)
+ {
+ foreach (var responseHeader in response.ContentHeaders)
+ {
+ transformed.Headers.Add(responseHeader.Name, ClientUtils.ParameterToString(responseHeader.Value));
+ }
+ }
+
+ if (response.Cookies != null)
+ {
+ foreach (var responseCookies in response.Cookies.Cast())
+ {
+ transformed.Cookies.Add(
+ new Cookie(
+ responseCookies.Name,
+ responseCookies.Value,
+ responseCookies.Path,
+ responseCookies.Domain)
+ );
+ }
+ }
+
+ return transformed;
+ }
+
+ ///
+ /// Executes the HTTP request for the current service.
+ /// Based on functions received it can be async or sync.
+ ///
+ /// Local function that executes http request and returns http response.
+ /// Local function to specify options for the service.
+ /// The RestSharp request object
+ /// The RestSharp options object
+ /// A per-request configuration object.
+ /// It is assumed that any merge with GlobalConfiguration has been done before calling this method.
+ /// A new ApiResponse instance.
+ private async Task> ExecClientAsync(Func>> getResponse, Action setOptions, RestRequest request, RequestOptions options, IReadableConfiguration configuration)
+ {
+ var baseUrl = configuration.GetOperationServerUrl(options.Operation, options.OperationIndex) ?? _baseUrl;
+ var clientOptions = new RestClientOptions(baseUrl)
+ {
+ ClientCertificates = configuration.ClientCertificates,
+ MaxTimeout = configuration.Timeout,
+ Proxy = configuration.Proxy,
+ UserAgent = configuration.UserAgent,
+ UseDefaultCredentials = configuration.UseDefaultCredentials,
+ RemoteCertificateValidationCallback = configuration.RemoteCertificateValidationCallback
+ };
+ setOptions(clientOptions);
+
+ {{#hasOAuthMethods}}
+ if (!string.IsNullOrEmpty(configuration.OAuthTokenUrl) &&
+ !string.IsNullOrEmpty(configuration.OAuthClientId) &&
+ !string.IsNullOrEmpty(configuration.OAuthClientSecret) &&
+ configuration.OAuthFlow != null)
+ {
+ clientOptions.Authenticator = new OAuthAuthenticator(
+ configuration.OAuthTokenUrl,
+ configuration.OAuthClientId,
+ configuration.OAuthClientSecret,
+ configuration.OAuthScope,
+ configuration.OAuthFlow,
+ SerializerSettings,
+ configuration);
+ }
+
+ {{/hasOAuthMethods}}
+ using (RestClient client = new RestClient(clientOptions,
+ configureSerialization: serializerConfig => serializerConfig.UseSerializer(() => new CustomJsonCodec(SerializerSettings, configuration))))
+ {
+ InterceptRequest(request);
+
+ RestResponse response = await getResponse(client);
+
+ // if the response type is oneOf/anyOf, call FromJSON to deserialize the data
+ if (typeof(AbstractOpenAPISchema).IsAssignableFrom(typeof(T)))
+ {
+ try
+ {
+ response.Data = (T)typeof(T).GetMethod("FromJson").Invoke(null, new object[] { response.Content });
+ }
+ catch (Exception ex)
+ {
+ throw ex.InnerException != null ? ex.InnerException : ex;
+ }
+ }
+ else if (typeof(T).Name == "Stream") // for binary response
+ {
+ response.Data = (T)(object)new MemoryStream(response.RawBytes);
+ }
+ else if (typeof(T).Name == "Byte[]") // for byte response
+ {
+ response.Data = (T)(object)response.RawBytes;
+ }
+ else if (typeof(T).Name == "String") // for string response
+ {
+ response.Data = (T)(object)response.Content;
+ }
+
+ InterceptResponse(request, response);
+
+ var result = ToApiResponse(response);
+ if (response.ErrorMessage != null)
+ {
+ result.ErrorText = response.ErrorMessage;
+ }
+
+ if (response.Cookies != null && response.Cookies.Count > 0)
+ {
+ if (result.Cookies == null) result.Cookies = new List();
+ foreach (var restResponseCookie in response.Cookies.Cast())
+ {
+ var cookie = new Cookie(
+ restResponseCookie.Name,
+ restResponseCookie.Value,
+ restResponseCookie.Path,
+ restResponseCookie.Domain
+ )
+ {
+ Comment = restResponseCookie.Comment,
+ CommentUri = restResponseCookie.CommentUri,
+ Discard = restResponseCookie.Discard,
+ Expired = restResponseCookie.Expired,
+ Expires = restResponseCookie.Expires,
+ HttpOnly = restResponseCookie.HttpOnly,
+ Port = restResponseCookie.Port,
+ Secure = restResponseCookie.Secure,
+ Version = restResponseCookie.Version
+ };
+
+ result.Cookies.Add(cookie);
+ }
+ }
+ return result;
+ }
+ }
+
+ private async Task> DeserializeRestResponseFromPolicyAsync(RestClient client, RestRequest request, PolicyResult policyResult, CancellationToken cancellationToken = default)
+ {
+ if (policyResult.Outcome == OutcomeType.Successful)
+ {
+ return await client.Deserialize(policyResult.Result, cancellationToken);
+ }
+ else
+ {
+ return new RestResponse(request)
+ {
+ ErrorException = policyResult.FinalException
+ };
+ }
+ }
+
+ private ApiResponse Exec(RestRequest request, RequestOptions options, IReadableConfiguration configuration)
+ {
+ Action setOptions = (clientOptions) =>
+ {
+ var cookies = new CookieContainer();
+
+ if (options.Cookies != null && options.Cookies.Count > 0)
+ {
+ foreach (var cookie in options.Cookies)
+ {
+ cookies.Add(new Cookie(cookie.Name, cookie.Value));
+ }
+ }
+ clientOptions.CookieContainer = cookies;
+ };
+
+ Func>> getResponse = (client) =>
+ {
+ if (RetryConfiguration.RetryPolicy != null)
+ {
+ var policy = RetryConfiguration.RetryPolicy;
+ var policyResult = policy.ExecuteAndCapture(() => client.Execute(request));
+ return DeserializeRestResponseFromPolicyAsync(client, request, policyResult);
+ }
+ else
+ {
+ return Task.FromResult(client.Execute(request));
+ }
+ };
+
+ return ExecClientAsync(getResponse, setOptions, request, options, configuration).GetAwaiter().GetResult();
+ }
+
+ {{#supportsAsync}}
+ private Task> ExecAsync(RestRequest request, RequestOptions options, IReadableConfiguration configuration, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ Action setOptions = (clientOptions) =>
+ {
+ //no extra options
+ };
+
+ Func>> getResponse = async (client) =>
+ {
+ {{#supportsRetry}}
+ if (RetryConfiguration.AsyncRetryPolicy != null)
+ {
+ var policy = RetryConfiguration.AsyncRetryPolicy;
+ var policyResult = await policy.ExecuteAndCaptureAsync((ct) => client.ExecuteAsync(request, ct), cancellationToken).ConfigureAwait(false);
+ return await DeserializeRestResponseFromPolicyAsync(client, request, policyResult, cancellationToken);
+ }
+ else
+ {
+ {{/supportsRetry}}
+ return await client.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
+ {{#supportsRetry}}
+ }
+ {{/supportsRetry}}
+ };
+
+ return ExecClientAsync(getResponse, setOptions, request, options, configuration);
+ }
+
+ #region IAsynchronousClient
+ ///
+ /// Make a HTTP GET request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> GetAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Get, path, options, config), options, config, cancellationToken);
+ }
+
+ ///
+ /// Make a HTTP POST request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> PostAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Post, path, options, config), options, config, cancellationToken);
+ }
+
+ ///
+ /// Make a HTTP PUT request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> PutAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Put, path, options, config), options, config, cancellationToken);
+ }
+
+ ///
+ /// Make a HTTP DELETE request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> DeleteAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Delete, path, options, config), options, config, cancellationToken);
+ }
+
+ ///
+ /// Make a HTTP HEAD request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> HeadAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Head, path, options, config), options, config, cancellationToken);
+ }
+
+ ///
+ /// Make a HTTP OPTION request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> OptionsAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Options, path, options, config), options, config, cancellationToken);
+ }
+
+ ///
+ /// Make a HTTP PATCH request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> PatchAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Patch, path, options, config), options, config, cancellationToken);
+ }
+ #endregion IAsynchronousClient
+ {{/supportsAsync}}
+
+ #region ISynchronousClient
+ ///
+ /// Make a HTTP GET request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Get(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Get, path, options, config), options, config);
+ }
+
+ ///
+ /// Make a HTTP POST request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Post(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Post, path, options, config), options, config);
+ }
+
+ ///
+ /// Make a HTTP PUT request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Put(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Put, path, options, config), options, config);
+ }
+
+ ///
+ /// Make a HTTP DELETE request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Delete(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Delete, path, options, config), options, config);
+ }
+
+ ///
+ /// Make a HTTP HEAD request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Head(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Head, path, options, config), options, config);
+ }
+
+ ///
+ /// Make a HTTP OPTION request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Options(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Options, path, options, config), options, config);
+ }
+
+ ///
+ /// Make a HTTP PATCH request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Patch(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Patch, path, options, config), options, config);
+ }
+ #endregion ISynchronousClient
+ }
+}
diff --git a/templates-v7/csharp/ApiException.mustache b/templates-v7/csharp/ApiException.mustache
new file mode 100644
index 000000000..f7dadc557
--- /dev/null
+++ b/templates-v7/csharp/ApiException.mustache
@@ -0,0 +1,60 @@
+{{>partial_header}}
+
+using System;
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// API Exception
+ ///
+ {{>visibility}} class ApiException : Exception
+ {
+ ///
+ /// Gets or sets the error code (HTTP status code)
+ ///
+ /// The error code (HTTP status code).
+ public int ErrorCode { get; set; }
+
+ ///
+ /// Gets or sets the error content (body json object)
+ ///
+ /// The error content (Http response body).
+ public object ErrorContent { get; private set; }
+
+ ///
+ /// Gets or sets the HTTP headers
+ ///
+ /// HTTP headers
+ public Multimap Headers { get; private set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ApiException() { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// HTTP status code.
+ /// Error message.
+ public ApiException(int errorCode, string message) : base(message)
+ {
+ this.ErrorCode = errorCode;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// HTTP status code.
+ /// Error message.
+ /// Error content.
+ /// HTTP Headers.
+ public ApiException(int errorCode, string message, object errorContent = null, Multimap headers = null) : base(message)
+ {
+ this.ErrorCode = errorCode;
+ this.ErrorContent = errorContent;
+ this.Headers = headers;
+ }
+ }
+
+}
diff --git a/templates-v7/csharp/ApiResponse.mustache b/templates-v7/csharp/ApiResponse.mustache
new file mode 100644
index 000000000..161c2acd1
--- /dev/null
+++ b/templates-v7/csharp/ApiResponse.mustache
@@ -0,0 +1,158 @@
+{{>partial_header}}
+
+using System;
+using System.Collections.Generic;
+using System.Net;
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// Provides a non-generic contract for the ApiResponse wrapper.
+ ///
+ public interface IApiResponse
+ {
+ ///
+ /// The data type of
+ ///
+ Type ResponseType { get; }
+
+ ///
+ /// The content of this response
+ ///
+ Object Content { get; }
+
+ ///
+ /// Gets or sets the status code (HTTP status code)
+ ///
+ /// The status code.
+ HttpStatusCode StatusCode { get; }
+
+ ///
+ /// Gets or sets the HTTP headers
+ ///
+ /// HTTP headers
+ Multimap Headers { get; }
+
+ ///
+ /// Gets or sets any error text defined by the calling client.
+ ///
+ string ErrorText { get; set; }
+
+ ///
+ /// Gets or sets any cookies passed along on the response.
+ ///
+ List Cookies { get; set; }
+
+ ///
+ /// The raw content of this response
+ ///
+ string RawContent { get; }
+ }
+
+ ///
+ /// API Response
+ ///
+ public class ApiResponse : IApiResponse
+ {
+ #region Properties
+
+ ///
+ /// Gets or sets the status code (HTTP status code)
+ ///
+ /// The status code.
+ public HttpStatusCode StatusCode { get; }
+
+ ///
+ /// Gets or sets the HTTP headers
+ ///
+ /// HTTP headers
+ public Multimap Headers { get; }
+
+ ///
+ /// Gets or sets the data (parsed HTTP body)
+ ///
+ /// The data.
+ public T Data { get; }
+
+ ///
+ /// Gets or sets any error text defined by the calling client.
+ ///
+ public string ErrorText { get; set; }
+
+ ///
+ /// Gets or sets any cookies passed along on the response.
+ ///
+ public List Cookies { get; set; }
+
+ ///
+ /// The content of this response
+ ///
+ public Type ResponseType
+ {
+ get { return typeof(T); }
+ }
+
+ ///
+ /// The data type of
+ ///
+ public object Content
+ {
+ get { return Data; }
+ }
+
+ ///
+ /// The raw content
+ ///
+ public string RawContent { get; }
+
+ #endregion Properties
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// HTTP status code.
+ /// HTTP headers.
+ /// Data (parsed HTTP body)
+ /// Raw content.
+ public ApiResponse(HttpStatusCode statusCode, Multimap headers, T data, string rawContent)
+ {
+ StatusCode = statusCode;
+ Headers = headers;
+ Data = data;
+ RawContent = rawContent;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// HTTP status code.
+ /// HTTP headers.
+ /// Data (parsed HTTP body)
+ public ApiResponse(HttpStatusCode statusCode, Multimap headers, T data) : this(statusCode, headers, data, null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// HTTP status code.
+ /// Data (parsed HTTP body)
+ /// Raw content.
+ public ApiResponse(HttpStatusCode statusCode, T data, string rawContent) : this(statusCode, null, data, rawContent)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// HTTP status code.
+ /// Data (parsed HTTP body)
+ public ApiResponse(HttpStatusCode statusCode, T data) : this(statusCode, data, null)
+ {
+ }
+
+ #endregion Constructors
+ }
+}
diff --git a/templates-v7/csharp/AssemblyInfo.mustache b/templates-v7/csharp/AssemblyInfo.mustache
new file mode 100644
index 000000000..d5d937dc1
--- /dev/null
+++ b/templates-v7/csharp/AssemblyInfo.mustache
@@ -0,0 +1,40 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("{{packageTitle}}")]
+[assembly: AssemblyDescription("{{packageDescription}}")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("{{packageCompany}}")]
+[assembly: AssemblyProduct("{{packageProductName}}")]
+[assembly: AssemblyCopyright("{{packageCopyright}}")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("{{packageVersion}}")]
+[assembly: AssemblyFileVersion("{{packageVersion}}")]
+{{^supportsAsync}}
+// Settings which don't support asynchronous operations rely on non-public constructors
+// This is due to how RestSharp requires the type constraint `where T : new()` in places it probably shouldn't.
+[assembly: InternalsVisibleTo("RestSharp")]
+[assembly: InternalsVisibleTo("NewtonSoft.Json")]
+[assembly: InternalsVisibleTo("JsonSubTypes")]
+{{/supportsAsync}}
diff --git a/templates-v7/csharp/ClientUtils.mustache b/templates-v7/csharp/ClientUtils.mustache
new file mode 100644
index 000000000..5c42d2e4f
--- /dev/null
+++ b/templates-v7/csharp/ClientUtils.mustache
@@ -0,0 +1,269 @@
+{{>partial_header}}
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Text.RegularExpressions;
+{{#useCompareNetObjects}}
+using KellermanSoftware.CompareNetObjects;
+{{/useCompareNetObjects}}
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// Utility functions providing some benefit to API client consumers.
+ ///
+ public static class ClientUtils
+ {
+ {{#useCompareNetObjects}}
+ ///
+ /// An instance of CompareLogic.
+ ///
+ public static CompareLogic compareLogic;
+
+ ///
+ /// Static constructor to initialise compareLogic.
+ ///
+ static ClientUtils()
+ {
+ {{#equatable}}
+ ComparisonConfig comparisonConfig = new{{^net70OrLater}} ComparisonConfig{{/net70OrLater}}();
+ comparisonConfig.UseHashCodeIdentifier = true;
+ {{/equatable}}
+ compareLogic = new{{^net70OrLater}} CompareLogic{{/net70OrLater}}({{#equatable}}comparisonConfig{{/equatable}});
+ }
+
+ {{/useCompareNetObjects}}
+ ///
+ /// Sanitize filename by removing the path
+ ///
+ /// Filename
+ /// Filename
+ public static string SanitizeFilename(string filename)
+ {
+ Match match = Regex.Match(filename, @".*[/\\](.*)$");
+ return match.Success ? match.Groups[1].Value : filename;
+ }
+
+ ///
+ /// Convert params to key/value pairs.
+ /// Use collectionFormat to properly format lists and collections.
+ ///
+ /// The swagger-supported collection format, one of: csv, tsv, ssv, pipes, multi
+ /// Key name.
+ /// Value object.
+ /// A multimap of keys with 1..n associated values.
+ public static Multimap ParameterToMultiMap(string collectionFormat, string name, object value)
+ {
+ var parameters = new Multimap();
+
+ if (value is ICollection collection && collectionFormat == "multi")
+ {
+ foreach (var item in collection)
+ {
+ parameters.Add(name, ParameterToString(item));
+ }
+ }
+ else if (value is IDictionary dictionary)
+ {
+ if(collectionFormat == "deepObject") {
+ foreach (DictionaryEntry entry in dictionary)
+ {
+ parameters.Add(name + "[" + entry.Key + "]", ParameterToString(entry.Value));
+ }
+ }
+ else {
+ foreach (DictionaryEntry entry in dictionary)
+ {
+ parameters.Add(entry.Key.ToString(), ParameterToString(entry.Value));
+ }
+ }
+ }
+ else
+ {
+ parameters.Add(name, ParameterToString(value));
+ }
+
+ return parameters;
+ }
+
+ ///
+ /// If parameter is DateTime, output in a formatted string (default ISO 8601), customizable with Configuration.DateTime.
+ /// If parameter is a list, join the list with ",".
+ /// Otherwise just return the string.
+ ///
+ /// The parameter (header, path, query, form).
+ /// An optional configuration instance, providing formatting options used in processing.
+ /// Formatted string.
+ public static string ParameterToString(object obj, IReadableConfiguration configuration = null)
+ {
+ if (obj is DateTime dateTime)
+ // Return a formatted date string - Can be customized with Configuration.DateTimeFormat
+ // Defaults to an ISO 8601, using the known as a Round-trip date/time pattern ("o")
+ // https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx#Anchor_8
+ // For example: 2009-06-15T13:45:30.0000000
+ return dateTime.ToString((configuration ?? GlobalConfiguration.Instance).DateTimeFormat);
+ if (obj is DateTimeOffset dateTimeOffset)
+ // Return a formatted date string - Can be customized with Configuration.DateTimeFormat
+ // Defaults to an ISO 8601, using the known as a Round-trip date/time pattern ("o")
+ // https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx#Anchor_8
+ // For example: 2009-06-15T13:45:30.0000000
+ return dateTimeOffset.ToString((configuration ?? GlobalConfiguration.Instance).DateTimeFormat);
+{{#net60OrLater}}
+ if (obj is DateOnly dateOnly)
+ // Return a formatted date string - Can be customized with Configuration.DateTimeFormat
+ // Defaults to an ISO 8601, using the known as a Round-trip date/time pattern ("o")
+ // https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx#Anchor_8
+ // For example: 2009-06-15
+ return dateOnly.ToString((configuration ?? GlobalConfiguration.Instance).DateTimeFormat);
+{{/net60OrLater}}
+ if (obj is bool boolean)
+ return boolean ? "true" : "false";
+ if (obj is ICollection collection) {
+ List entries = new List();
+ foreach (var entry in collection)
+ entries.Add(ParameterToString(entry, configuration));
+ return string.Join(",", entries);
+ }
+ if (obj is Enum && HasEnumMemberAttrValue(obj))
+ return GetEnumMemberAttrValue(obj);
+
+ return Convert.ToString(obj, CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Serializes the given object when not null. Otherwise return null.
+ ///
+ /// The object to serialize.
+ /// Serialized string.
+ public static string Serialize(object obj)
+ {
+ return obj != null ? Newtonsoft.Json.JsonConvert.SerializeObject(obj) : null;
+ }
+
+ ///
+ /// Encode string in base64 format.
+ ///
+ /// string to be encoded.
+ /// Encoded string.
+ public static string Base64Encode(string text)
+ {
+ return Convert.ToBase64String(global::System.Text.Encoding.UTF8.GetBytes(text));
+ }
+
+ ///
+ /// Convert stream to byte array
+ ///
+ /// Input stream to be converted
+ /// Byte array
+ public static byte[] ReadAsBytes(Stream inputStream)
+ {
+ using (var ms = new MemoryStream())
+ {
+ inputStream.CopyTo(ms);
+ return ms.ToArray();
+ }
+ }
+
+ ///
+ /// Select the Content-Type header's value from the given content-type array:
+ /// if JSON type exists in the given array, use it;
+ /// otherwise use the first one defined in 'consumes'
+ ///
+ /// The Content-Type array to select from.
+ /// The Content-Type header to use.
+ public static string SelectHeaderContentType(string[] contentTypes)
+ {
+ if (contentTypes.Length == 0)
+ return null;
+
+ foreach (var contentType in contentTypes)
+ {
+ if (IsJsonMime(contentType))
+ return contentType;
+ }
+
+ return contentTypes[0]; // use the first content type specified in 'consumes'
+ }
+
+ ///
+ /// Select the Accept header's value from the given accepts array:
+ /// if JSON exists in the given array, use it;
+ /// otherwise use all of them (joining into a string)
+ ///
+ /// The accepts array to select from.
+ /// The Accept header to use.
+ public static string SelectHeaderAccept(string[] accepts)
+ {
+ if (accepts.Length == 0)
+ return null;
+
+ if (accepts.Contains("application/json", StringComparer.OrdinalIgnoreCase))
+ return "application/json";
+
+ return string.Join(",", accepts);
+ }
+
+ ///
+ /// Provides a case-insensitive check that a provided content type is a known JSON-like content type.
+ ///
+ public static readonly Regex JsonRegex = new Regex("(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$");
+
+ ///
+ /// Check if the given MIME is a JSON MIME.
+ /// JSON MIME examples:
+ /// application/json
+ /// application/json; charset=UTF8
+ /// APPLICATION/JSON
+ /// application/vnd.company+json
+ ///
+ /// MIME
+ /// Returns True if MIME type is json.
+ public static bool IsJsonMime(string mime)
+ {
+ if (string.IsNullOrWhiteSpace(mime)) return false;
+
+ return JsonRegex.IsMatch(mime) || mime.Equals("application/json-patch+json");
+ }
+
+ ///
+ /// Is the Enum decorated with EnumMember Attribute
+ ///
+ ///
+ /// true if found
+ private static bool HasEnumMemberAttrValue(object enumVal)
+ {
+ if (enumVal == null)
+ throw new ArgumentNullException(nameof(enumVal));
+ var enumType = enumVal.GetType();
+ var memInfo = enumType.GetMember(enumVal.ToString() ?? throw new InvalidOperationException());
+ var attr = memInfo.FirstOrDefault()?.GetCustomAttributes(false).OfType().FirstOrDefault();
+ if (attr != null) return true;
+ return false;
+ }
+
+ ///
+ /// Get the EnumMember value
+ ///
+ ///
+ /// EnumMember value as string otherwise null
+ private static string GetEnumMemberAttrValue(object enumVal)
+ {
+ if (enumVal == null)
+ throw new ArgumentNullException(nameof(enumVal));
+ var enumType = enumVal.GetType();
+ var memInfo = enumType.GetMember(enumVal.ToString() ?? throw new InvalidOperationException());
+ var attr = memInfo.FirstOrDefault()?.GetCustomAttributes(false).OfType().FirstOrDefault();
+ if (attr != null)
+ {
+ return attr.Value;
+ }
+ return null;
+ }
+ }
+}
diff --git a/templates-v7/csharp/Configuration.mustache b/templates-v7/csharp/Configuration.mustache
new file mode 100644
index 000000000..069758033
--- /dev/null
+++ b/templates-v7/csharp/Configuration.mustache
@@ -0,0 +1,737 @@
+{{>partial_header}}
+
+using System;
+{{^net35}}
+using System.Collections.Concurrent;
+{{/net35}}
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Reflection;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Net.Http;
+using System.Net.Security;
+{{#useRestSharp}}
+{{#hasOAuthMethods}}using {{packageName}}.Client.Auth;
+{{/hasOAuthMethods}}
+{{/useRestSharp}}
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// Represents a set of configuration settings
+ ///
+ {{>visibility}} class Configuration : IReadableConfiguration
+ {
+ #region Constants
+
+ ///
+ /// Version of the package.
+ ///
+ /// Version of the package.
+ public const string Version = "{{packageVersion}}";
+
+ ///
+ /// Identifier for ISO 8601 DateTime Format
+ ///
+ /// See https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx#Anchor_8 for more information.
+ // ReSharper disable once InconsistentNaming
+ public const string ISO8601_DATETIME_FORMAT = "o";
+
+ #endregion Constants
+
+ #region Static Members
+
+ ///
+ /// Default creation of exceptions for a given method name and response object
+ ///
+ public static readonly ExceptionFactory DefaultExceptionFactory = (methodName, response) =>
+ {
+ var status = (int)response.StatusCode;
+ if (status >= 400)
+ {
+ return new ApiException(status,
+ string.Format("Error calling {0}: {1}", methodName, response.RawContent),
+ response.RawContent, response.Headers);
+ }
+ {{^netStandard}}
+ if (status == 0)
+ {
+ return new ApiException(status,
+ string.Format("Error calling {0}: {1}", methodName, response.ErrorText), response.ErrorText);
+ }
+ {{/netStandard}}
+ return null;
+ };
+
+ #endregion Static Members
+
+ #region Private Members
+
+ ///
+ /// Defines the base path of the target API server.
+ /// Example: http://localhost:3000/v1/
+ ///
+ private string _basePath;
+
+ private bool _useDefaultCredentials = false;
+
+ ///
+ /// Gets or sets the API key based on the authentication name.
+ /// This is the key and value comprising the "secret" for accessing an API.
+ ///
+ /// The API key.
+ private IDictionary _apiKey;
+
+ ///
+ /// Gets or sets the prefix (e.g. Token) of the API key based on the authentication name.
+ ///
+ /// The prefix of the API key.
+ private IDictionary _apiKeyPrefix;
+
+ private string _dateTimeFormat = ISO8601_DATETIME_FORMAT;
+ private string _tempFolderPath = Path.GetTempPath();
+ {{#servers.0}}
+
+ ///
+ /// Gets or sets the servers defined in the OpenAPI spec.
+ ///
+ /// The servers
+ private IList> _servers;
+ {{/servers.0}}
+
+ ///
+ /// Gets or sets the operation servers defined in the OpenAPI spec.
+ ///
+ /// The operation servers
+ private IReadOnlyDictionary>> _operationServers;
+
+ {{#hasHttpSignatureMethods}}
+
+ ///
+ /// HttpSigning configuration
+ ///
+ private HttpSigningConfiguration _HttpSigningConfiguration = null;
+ {{/hasHttpSignatureMethods}}
+ #endregion Private Members
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class
+ ///
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")]
+ public Configuration()
+ {
+ Proxy = null;
+ UserAgent = WebUtility.UrlEncode("{{httpUserAgent}}{{^httpUserAgent}}OpenAPI-Generator/{{packageVersion}}/csharp{{/httpUserAgent}}");
+ BasePath = "{{{basePath}}}";
+ DefaultHeaders = new {{^net35}}Concurrent{{/net35}}Dictionary();
+ ApiKey = new {{^net35}}Concurrent{{/net35}}Dictionary();
+ ApiKeyPrefix = new {{^net35}}Concurrent{{/net35}}Dictionary();
+ {{#servers}}
+ {{#-first}}
+ Servers = new List>()
+ {
+ {{/-first}}
+ {
+ new Dictionary {
+ {"url", "{{{url}}}"},
+ {"description", "{{{description}}}{{^description}}No description provided{{/description}}"},
+ {{#variables}}
+ {{#-first}}
+ {
+ "variables", new Dictionary {
+ {{/-first}}
+ {
+ "{{{name}}}", new Dictionary {
+ {"description", "{{{description}}}{{^description}}No description provided{{/description}}"},
+ {"default_value", {{#isString}}{{^isEnum}}@{{/isEnum}}{{/isString}}"{{{defaultValue}}}"},
+ {{#enumValues}}
+ {{#-first}}
+ {
+ "enum_values", new List() {
+ {{/-first}}
+ "{{{.}}}"{{^-last}},{{/-last}}
+ {{#-last}}
+ }
+ }
+ {{/-last}}
+ {{/enumValues}}
+ }
+ }{{^-last}},{{/-last}}
+ {{#-last}}
+ }
+ }
+ {{/-last}}
+ {{/variables}}
+ }
+ }{{^-last}},{{/-last}}
+ {{#-last}}
+ };
+ {{/-last}}
+ {{/servers}}
+ OperationServers = new Dictionary>>()
+ {
+ {{#apiInfo}}
+ {{#apis}}
+ {{#operations}}
+ {{#operation}}
+ {{#servers.0}}
+ {
+ "{{{classname}}}.{{{nickname}}}", new List>
+ {
+ {{#servers}}
+ {
+ new Dictionary
+ {
+ {"url", "{{{url}}}"},
+ {"description", "{{{description}}}{{^description}}No description provided{{/description}}"}
+ }
+ },
+ {{/servers}}
+ }
+ },
+ {{/servers.0}}
+ {{/operation}}
+ {{/operations}}
+ {{/apis}}
+ {{/apiInfo}}
+ };
+
+ // Setting Timeout has side effects (forces ApiClient creation).
+ Timeout = TimeSpan.FromSeconds(100);
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ ///
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")]
+ public Configuration(
+ IDictionary defaultHeaders,
+ IDictionary apiKey,
+ IDictionary apiKeyPrefix,
+ string basePath = "{{{basePath}}}") : this()
+ {
+ if (string.{{^net35}}IsNullOrWhiteSpace{{/net35}}{{#net35}}IsNullOrEmpty{{/net35}}(basePath))
+ throw new ArgumentException("The provided basePath is invalid.", "basePath");
+ if (defaultHeaders == null)
+ throw new ArgumentNullException("defaultHeaders");
+ if (apiKey == null)
+ throw new ArgumentNullException("apiKey");
+ if (apiKeyPrefix == null)
+ throw new ArgumentNullException("apiKeyPrefix");
+
+ BasePath = basePath;
+
+ foreach (var keyValuePair in defaultHeaders)
+ {
+ DefaultHeaders.Add(keyValuePair);
+ }
+
+ foreach (var keyValuePair in apiKey)
+ {
+ ApiKey.Add(keyValuePair);
+ }
+
+ foreach (var keyValuePair in apiKeyPrefix)
+ {
+ ApiKeyPrefix.Add(keyValuePair);
+ }
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ ///
+ /// Gets or sets the base path for API access.
+ ///
+ public virtual string BasePath
+ {
+ get { return _basePath; }
+ set { _basePath = value; }
+ }
+
+ ///
+ /// Determine whether or not the "default credentials" (e.g. the user account under which the current process is running) will be sent along to the server. The default is false.
+ ///
+ public virtual bool UseDefaultCredentials
+ {
+ get { return _useDefaultCredentials; }
+ set { _useDefaultCredentials = value; }
+ }
+
+ ///
+ /// Gets or sets the default header.
+ ///
+ [Obsolete("Use DefaultHeaders instead.")]
+ public virtual IDictionary DefaultHeader
+ {
+ get
+ {
+ return DefaultHeaders;
+ }
+ set
+ {
+ DefaultHeaders = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the default headers.
+ ///
+ public virtual IDictionary DefaultHeaders { get; set; }
+
+ ///
+ /// Gets or sets the HTTP timeout of ApiClient. Defaults to 100 seconds.
+ ///
+ public virtual TimeSpan Timeout { get; set; }
+
+ ///
+ /// Gets or sets the proxy
+ ///
+ /// Proxy.
+ public virtual WebProxy Proxy { get; set; }
+
+ ///
+ /// Gets or sets the HTTP user agent.
+ ///
+ /// Http user agent.
+ public virtual string UserAgent { get; set; }
+
+ ///
+ /// Gets or sets the username (HTTP basic authentication).
+ ///
+ /// The username.
+ public virtual string Username { get; set; }
+
+ ///
+ /// Gets or sets the password (HTTP basic authentication).
+ ///
+ /// The password.
+ public virtual string Password { get; set; }
+
+ ///
+ /// Gets the API key with prefix.
+ ///
+ /// API key identifier (authentication scheme).
+ /// API key with prefix.
+ public string GetApiKeyWithPrefix(string apiKeyIdentifier)
+ {
+ string apiKeyValue;
+ ApiKey.TryGetValue(apiKeyIdentifier, out apiKeyValue);
+ string apiKeyPrefix;
+ if (ApiKeyPrefix.TryGetValue(apiKeyIdentifier, out apiKeyPrefix))
+ {
+ return apiKeyPrefix + " " + apiKeyValue;
+ }
+
+ return apiKeyValue;
+ }
+
+ ///
+ /// Gets or sets certificate collection to be sent with requests.
+ ///
+ /// X509 Certificate collection.
+ public X509CertificateCollection ClientCertificates { get; set; }
+
+ ///
+ /// Gets or sets the access token for OAuth2 authentication.
+ ///
+ /// This helper property simplifies code generation.
+ ///
+ /// The access token.
+ public virtual string AccessToken { get; set; }
+
+ {{#useRestSharp}}
+ {{#hasOAuthMethods}}
+ ///
+ /// Gets or sets the token URL for OAuth2 authentication.
+ ///
+ /// The OAuth Token URL.
+ public virtual string OAuthTokenUrl { get; set; }
+
+ ///
+ /// Gets or sets the client ID for OAuth2 authentication.
+ ///
+ /// The OAuth Client ID.
+ public virtual string OAuthClientId { get; set; }
+
+ ///
+ /// Gets or sets the client secret for OAuth2 authentication.
+ ///
+ /// The OAuth Client Secret.
+ public virtual string OAuthClientSecret { get; set; }
+
+ ///
+ /// Gets or sets the client scope for OAuth2 authentication.
+ ///
+ /// The OAuth Client Scope.
+ public virtual string{{nrt?}} OAuthScope { get; set; }
+
+ ///
+ /// Gets or sets the flow for OAuth2 authentication.
+ ///
+ /// The OAuth Flow.
+ public virtual OAuthFlow? OAuthFlow { get; set; }
+
+ {{/hasOAuthMethods}}
+ {{/useRestSharp}}
+ ///
+ /// Gets or sets the temporary folder path to store the files downloaded from the server.
+ ///
+ /// Folder path.
+ public virtual string TempFolderPath
+ {
+ get { return _tempFolderPath; }
+
+ set
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ _tempFolderPath = Path.GetTempPath();
+ return;
+ }
+
+ // create the directory if it does not exist
+ if (!Directory.Exists(value))
+ {
+ Directory.CreateDirectory(value);
+ }
+
+ // check if the path contains directory separator at the end
+ if (value[value.Length - 1] == Path.DirectorySeparatorChar)
+ {
+ _tempFolderPath = value;
+ }
+ else
+ {
+ _tempFolderPath = value + Path.DirectorySeparatorChar;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the date time format used when serializing in the ApiClient
+ /// By default, it's set to ISO 8601 - "o", for others see:
+ /// https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx
+ /// and https://msdn.microsoft.com/en-us/library/8kb3ddd4(v=vs.110).aspx
+ /// No validation is done to ensure that the string you're providing is valid
+ ///
+ /// The DateTimeFormat string
+ public virtual string DateTimeFormat
+ {
+ get { return _dateTimeFormat; }
+ set
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ // Never allow a blank or null string, go back to the default
+ _dateTimeFormat = ISO8601_DATETIME_FORMAT;
+ return;
+ }
+
+ // Caution, no validation when you choose date time format other than ISO 8601
+ // Take a look at the above links
+ _dateTimeFormat = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the prefix (e.g. Token) of the API key based on the authentication name.
+ ///
+ /// Whatever you set here will be prepended to the value defined in AddApiKey.
+ ///
+ /// An example invocation here might be:
+ ///
+ /// ApiKeyPrefix["Authorization"] = "Bearer";
+ ///
+ /// … where ApiKey["Authorization"] would then be used to set the value of your bearer token.
+ ///
+ ///
+ /// OAuth2 workflows should set tokens via AccessToken.
+ ///
+ ///
+ /// The prefix of the API key.
+ public virtual IDictionary ApiKeyPrefix
+ {
+ get { return _apiKeyPrefix; }
+ set
+ {
+ if (value == null)
+ {
+ throw new InvalidOperationException("ApiKeyPrefix collection may not be null.");
+ }
+ _apiKeyPrefix = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the API key based on the authentication name.
+ ///
+ /// The API key.
+ public virtual IDictionary ApiKey
+ {
+ get { return _apiKey; }
+ set
+ {
+ if (value == null)
+ {
+ throw new InvalidOperationException("ApiKey collection may not be null.");
+ }
+ _apiKey = value;
+ }
+ }
+ {{#servers.0}}
+
+ ///
+ /// Gets or sets the servers.
+ ///
+ /// The servers.
+ public virtual IList> Servers
+ {
+ get { return _servers; }
+ set
+ {
+ if (value == null)
+ {
+ throw new InvalidOperationException("Servers may not be null.");
+ }
+ _servers = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the operation servers.
+ ///
+ /// The operation servers.
+ public virtual IReadOnlyDictionary>> OperationServers
+ {
+ get { return _operationServers; }
+ set
+ {
+ if (value == null)
+ {
+ throw new InvalidOperationException("Operation servers may not be null.");
+ }
+ _operationServers = value;
+ }
+ }
+
+ ///
+ /// Returns URL based on server settings without providing values
+ /// for the variables
+ ///
+ /// Array index of the server settings.
+ /// The server URL.
+ public string GetServerUrl(int index)
+ {
+ return GetServerUrl(Servers, index, null);
+ }
+
+ ///
+ /// Returns URL based on server settings.
+ ///
+ /// Array index of the server settings.
+ /// Dictionary of the variables and the corresponding values.
+ /// The server URL.
+ public string GetServerUrl(int index, Dictionary inputVariables)
+ {
+ return GetServerUrl(Servers, index, inputVariables);
+ }
+
+ ///
+ /// Returns URL based on operation server settings.
+ ///
+ /// Operation associated with the request path.
+ /// Array index of the server settings.
+ /// The operation server URL.
+ public string GetOperationServerUrl(string operation, int index)
+ {
+ return GetOperationServerUrl(operation, index, null);
+ }
+
+ ///
+ /// Returns URL based on operation server settings.
+ ///
+ /// Operation associated with the request path.
+ /// Array index of the server settings.
+ /// Dictionary of the variables and the corresponding values.
+ /// The operation server URL.
+ public string GetOperationServerUrl(string operation, int index, Dictionary inputVariables)
+ {
+ if (operation != null && OperationServers.TryGetValue(operation, out var operationServer))
+ {
+ return GetServerUrl(operationServer, index, inputVariables);
+ }
+
+ return null;
+ }
+
+ ///
+ /// Returns URL based on server settings.
+ ///
+ /// Dictionary of server settings.
+ /// Array index of the server settings.
+ /// Dictionary of the variables and the corresponding values.
+ /// The server URL.
+ private string GetServerUrl(IList> servers, int index, Dictionary inputVariables)
+ {
+ if (index < 0 || index >= servers.Count)
+ {
+ throw new InvalidOperationException($"Invalid index {index} when selecting the server. Must be less than {servers.Count}.");
+ }
+
+ if (inputVariables == null)
+ {
+ inputVariables = new Dictionary();
+ }
+
+ IReadOnlyDictionary server = servers[index];
+ string url = (string)server["url"];
+
+ if (server.ContainsKey("variables"))
+ {
+ // go through each variable and assign a value
+ foreach (KeyValuePair variable in (IReadOnlyDictionary)server["variables"])
+ {
+
+ IReadOnlyDictionary serverVariables = (IReadOnlyDictionary)(variable.Value);
+
+ if (inputVariables.ContainsKey(variable.Key))
+ {
+ if (((List)serverVariables["enum_values"]).Contains(inputVariables[variable.Key]))
+ {
+ url = url.Replace("{" + variable.Key + "}", inputVariables[variable.Key]);
+ }
+ else
+ {
+ throw new InvalidOperationException($"The variable `{variable.Key}` in the server URL has invalid value #{inputVariables[variable.Key]}. Must be {(List)serverVariables["enum_values"]}");
+ }
+ }
+ else
+ {
+ // use default value
+ url = url.Replace("{" + variable.Key + "}", (string)serverVariables["default_value"]);
+ }
+ }
+ }
+
+ return url;
+ }
+ {{/servers.0}}
+ {{#hasHttpSignatureMethods}}
+
+ ///
+ /// Gets and Sets the HttpSigningConfiguration
+ ///
+ public HttpSigningConfiguration HttpSigningConfiguration
+ {
+ get { return _HttpSigningConfiguration; }
+ set { _HttpSigningConfiguration = value; }
+ }
+ {{/hasHttpSignatureMethods}}
+
+ ///
+ /// Gets and Sets the RemoteCertificateValidationCallback
+ ///
+ public RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; }
+
+ #endregion Properties
+
+ #region Methods
+
+ ///
+ /// Returns a string with essential information for debugging.
+ ///
+ public static string ToDebugReport()
+ {
+ string report = "C# SDK ({{{packageName}}}) Debug Report:\n";
+ report += " OS: " + System.Environment.OSVersion + "\n";
+ report += " .NET Framework Version: " + System.Environment.Version + "\n";
+ report += " Version of the API: {{{version}}}\n";
+ report += " SDK Package Version: {{{packageVersion}}}\n";
+
+ return report;
+ }
+
+ ///
+ /// Add Api Key Header.
+ ///
+ /// Api Key name.
+ /// Api Key value.
+ ///
+ public void AddApiKey(string key, string value)
+ {
+ ApiKey[key] = value;
+ }
+
+ ///
+ /// Sets the API key prefix.
+ ///
+ /// Api Key name.
+ /// Api Key value.
+ public void AddApiKeyPrefix(string key, string value)
+ {
+ ApiKeyPrefix[key] = value;
+ }
+
+ #endregion Methods
+
+ #region Static Members
+ ///
+ /// Merge configurations.
+ ///
+ /// First configuration.
+ /// Second configuration.
+ /// Merged configuration.
+ public static IReadableConfiguration MergeConfigurations(IReadableConfiguration first, IReadableConfiguration second)
+ {
+ if (second == null) return first ?? GlobalConfiguration.Instance;
+
+ Dictionary apiKey = first.ApiKey.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
+ Dictionary apiKeyPrefix = first.ApiKeyPrefix.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
+ Dictionary defaultHeaders = first.DefaultHeaders.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
+
+ foreach (var kvp in second.ApiKey) apiKey[kvp.Key] = kvp.Value;
+ foreach (var kvp in second.ApiKeyPrefix) apiKeyPrefix[kvp.Key] = kvp.Value;
+ foreach (var kvp in second.DefaultHeaders) defaultHeaders[kvp.Key] = kvp.Value;
+
+ var config = new Configuration
+ {
+ ApiKey = apiKey,
+ ApiKeyPrefix = apiKeyPrefix,
+ DefaultHeaders = defaultHeaders,
+ BasePath = second.BasePath ?? first.BasePath,
+ Timeout = second.Timeout,
+ Proxy = second.Proxy ?? first.Proxy,
+ UserAgent = second.UserAgent ?? first.UserAgent,
+ Username = second.Username ?? first.Username,
+ Password = second.Password ?? first.Password,
+ AccessToken = second.AccessToken ?? first.AccessToken,
+ {{#useRestSharp}}
+ {{#hasOAuthMethods}}
+ OAuthTokenUrl = second.OAuthTokenUrl ?? first.OAuthTokenUrl,
+ OAuthClientId = second.OAuthClientId ?? first.OAuthClientId,
+ OAuthClientSecret = second.OAuthClientSecret ?? first.OAuthClientSecret,
+ OAuthScope = second.OAuthScope ?? first.OAuthScope,
+ OAuthFlow = second.OAuthFlow ?? first.OAuthFlow,
+ {{/hasOAuthMethods}}
+ {{/useRestSharp}}
+ {{#hasHttpSignatureMethods}}
+ HttpSigningConfiguration = second.HttpSigningConfiguration ?? first.HttpSigningConfiguration,
+ {{/hasHttpSignatureMethods}}
+ TempFolderPath = second.TempFolderPath ?? first.TempFolderPath,
+ DateTimeFormat = second.DateTimeFormat ?? first.DateTimeFormat,
+ ClientCertificates = second.ClientCertificates ?? first.ClientCertificates,
+ UseDefaultCredentials = second.UseDefaultCredentials,
+ RemoteCertificateValidationCallback = second.RemoteCertificateValidationCallback ?? first.RemoteCertificateValidationCallback,
+ };
+ return config;
+ }
+ #endregion Static Members
+ }
+}
diff --git a/templates-v7/csharp/Configuration.v790.mustache b/templates-v7/csharp/Configuration.v790.mustache
new file mode 100644
index 000000000..2753aafb3
--- /dev/null
+++ b/templates-v7/csharp/Configuration.v790.mustache
@@ -0,0 +1,737 @@
+{{>partial_header}}
+
+using System;
+{{^net35}}
+using System.Collections.Concurrent;
+{{/net35}}
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Reflection;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Net.Http;
+using System.Net.Security;
+{{#useRestSharp}}
+{{#hasOAuthMethods}}using {{packageName}}.Client.Auth;
+{{/hasOAuthMethods}}
+{{/useRestSharp}}
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// Represents a set of configuration settings
+ ///
+ {{>visibility}} class Configuration : IReadableConfiguration
+ {
+ #region Constants
+
+ ///
+ /// Version of the package.
+ ///
+ /// Version of the package.
+ public const string Version = "{{packageVersion}}";
+
+ ///
+ /// Identifier for ISO 8601 DateTime Format
+ ///
+ /// See https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx#Anchor_8 for more information.
+ // ReSharper disable once InconsistentNaming
+ public const string ISO8601_DATETIME_FORMAT = "o";
+
+ #endregion Constants
+
+ #region Static Members
+
+ ///
+ /// Default creation of exceptions for a given method name and response object
+ ///
+ public static readonly ExceptionFactory DefaultExceptionFactory = (methodName, response) =>
+ {
+ var status = (int)response.StatusCode;
+ if (status >= 400)
+ {
+ return new ApiException(status,
+ string.Format("Error calling {0}: {1}", methodName, response.RawContent),
+ response.RawContent, response.Headers);
+ }
+ {{^netStandard}}
+ if (status == 0)
+ {
+ return new ApiException(status,
+ string.Format("Error calling {0}: {1}", methodName, response.ErrorText), response.ErrorText);
+ }
+ {{/netStandard}}
+ return null;
+ };
+
+ #endregion Static Members
+
+ #region Private Members
+
+ ///
+ /// Defines the base path of the target API server.
+ /// Example: http://localhost:3000/v1/
+ ///
+ private string _basePath;
+
+ private bool _useDefaultCredentials = false;
+
+ ///
+ /// Gets or sets the API key based on the authentication name.
+ /// This is the key and value comprising the "secret" for accessing an API.
+ ///
+ /// The API key.
+ private IDictionary _apiKey;
+
+ ///
+ /// Gets or sets the prefix (e.g. Token) of the API key based on the authentication name.
+ ///
+ /// The prefix of the API key.
+ private IDictionary _apiKeyPrefix;
+
+ private string _dateTimeFormat = ISO8601_DATETIME_FORMAT;
+ private string _tempFolderPath = Path.GetTempPath();
+ {{#servers.0}}
+
+ ///
+ /// Gets or sets the servers defined in the OpenAPI spec.
+ ///
+ /// The servers
+ private IList> _servers;
+ {{/servers.0}}
+
+ ///
+ /// Gets or sets the operation servers defined in the OpenAPI spec.
+ ///
+ /// The operation servers
+ private IReadOnlyDictionary>> _operationServers;
+
+ {{#hasHttpSignatureMethods}}
+
+ ///
+ /// HttpSigning configuration
+ ///
+ private HttpSigningConfiguration _HttpSigningConfiguration = null;
+ {{/hasHttpSignatureMethods}}
+ #endregion Private Members
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class
+ ///
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")]
+ public Configuration()
+ {
+ Proxy = null;
+ UserAgent = WebUtility.UrlEncode("{{httpUserAgent}}{{^httpUserAgent}}OpenAPI-Generator/{{packageVersion}}/csharp{{/httpUserAgent}}");
+ BasePath = "{{{basePath}}}";
+ DefaultHeaders = new {{^net35}}Concurrent{{/net35}}Dictionary();
+ ApiKey = new {{^net35}}Concurrent{{/net35}}Dictionary();
+ ApiKeyPrefix = new {{^net35}}Concurrent{{/net35}}Dictionary();
+ {{#servers}}
+ {{#-first}}
+ Servers = new List>()
+ {
+ {{/-first}}
+ {
+ new Dictionary {
+ {"url", "{{{url}}}"},
+ {"description", "{{{description}}}{{^description}}No description provided{{/description}}"},
+ {{#variables}}
+ {{#-first}}
+ {
+ "variables", new Dictionary {
+ {{/-first}}
+ {
+ "{{{name}}}", new Dictionary {
+ {"description", "{{{description}}}{{^description}}No description provided{{/description}}"},
+ {"default_value", {{#isString}}{{^isEnum}}@{{/isEnum}}{{/isString}}"{{{defaultValue}}}"},
+ {{#enumValues}}
+ {{#-first}}
+ {
+ "enum_values", new List() {
+ {{/-first}}
+ "{{{.}}}"{{^-last}},{{/-last}}
+ {{#-last}}
+ }
+ }
+ {{/-last}}
+ {{/enumValues}}
+ }
+ }{{^-last}},{{/-last}}
+ {{#-last}}
+ }
+ }
+ {{/-last}}
+ {{/variables}}
+ }
+ }{{^-last}},{{/-last}}
+ {{#-last}}
+ };
+ {{/-last}}
+ {{/servers}}
+ OperationServers = new Dictionary>>()
+ {
+ {{#apiInfo}}
+ {{#apis}}
+ {{#operations}}
+ {{#operation}}
+ {{#servers.0}}
+ {
+ "{{{classname}}}.{{{nickname}}}", new List>
+ {
+ {{#servers}}
+ {
+ new Dictionary
+ {
+ {"url", "{{{url}}}"},
+ {"description", "{{{description}}}{{^description}}No description provided{{/description}}"}
+ }
+ },
+ {{/servers}}
+ }
+ },
+ {{/servers.0}}
+ {{/operation}}
+ {{/operations}}
+ {{/apis}}
+ {{/apiInfo}}
+ };
+
+ // Setting Timeout has side effects (forces ApiClient creation).
+ Timeout = 100000;
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ ///
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")]
+ public Configuration(
+ IDictionary defaultHeaders,
+ IDictionary apiKey,
+ IDictionary apiKeyPrefix,
+ string basePath = "{{{basePath}}}") : this()
+ {
+ if (string.{{^net35}}IsNullOrWhiteSpace{{/net35}}{{#net35}}IsNullOrEmpty{{/net35}}(basePath))
+ throw new ArgumentException("The provided basePath is invalid.", "basePath");
+ if (defaultHeaders == null)
+ throw new ArgumentNullException("defaultHeaders");
+ if (apiKey == null)
+ throw new ArgumentNullException("apiKey");
+ if (apiKeyPrefix == null)
+ throw new ArgumentNullException("apiKeyPrefix");
+
+ BasePath = basePath;
+
+ foreach (var keyValuePair in defaultHeaders)
+ {
+ DefaultHeaders.Add(keyValuePair);
+ }
+
+ foreach (var keyValuePair in apiKey)
+ {
+ ApiKey.Add(keyValuePair);
+ }
+
+ foreach (var keyValuePair in apiKeyPrefix)
+ {
+ ApiKeyPrefix.Add(keyValuePair);
+ }
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ ///
+ /// Gets or sets the base path for API access.
+ ///
+ public virtual string BasePath
+ {
+ get { return _basePath; }
+ set { _basePath = value; }
+ }
+
+ ///
+ /// Determine whether or not the "default credentials" (e.g. the user account under which the current process is running) will be sent along to the server. The default is false.
+ ///
+ public virtual bool UseDefaultCredentials
+ {
+ get { return _useDefaultCredentials; }
+ set { _useDefaultCredentials = value; }
+ }
+
+ ///
+ /// Gets or sets the default header.
+ ///
+ [Obsolete("Use DefaultHeaders instead.")]
+ public virtual IDictionary DefaultHeader
+ {
+ get
+ {
+ return DefaultHeaders;
+ }
+ set
+ {
+ DefaultHeaders = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the default headers.
+ ///
+ public virtual IDictionary DefaultHeaders { get; set; }
+
+ ///
+ /// Gets or sets the HTTP timeout (milliseconds) of ApiClient. Default to 100000 milliseconds.
+ ///
+ public virtual int Timeout { get; set; }
+
+ ///
+ /// Gets or sets the proxy
+ ///
+ /// Proxy.
+ public virtual WebProxy Proxy { get; set; }
+
+ ///
+ /// Gets or sets the HTTP user agent.
+ ///
+ /// Http user agent.
+ public virtual string UserAgent { get; set; }
+
+ ///
+ /// Gets or sets the username (HTTP basic authentication).
+ ///
+ /// The username.
+ public virtual string Username { get; set; }
+
+ ///
+ /// Gets or sets the password (HTTP basic authentication).
+ ///
+ /// The password.
+ public virtual string Password { get; set; }
+
+ ///
+ /// Gets the API key with prefix.
+ ///
+ /// API key identifier (authentication scheme).
+ /// API key with prefix.
+ public string GetApiKeyWithPrefix(string apiKeyIdentifier)
+ {
+ string apiKeyValue;
+ ApiKey.TryGetValue(apiKeyIdentifier, out apiKeyValue);
+ string apiKeyPrefix;
+ if (ApiKeyPrefix.TryGetValue(apiKeyIdentifier, out apiKeyPrefix))
+ {
+ return apiKeyPrefix + " " + apiKeyValue;
+ }
+
+ return apiKeyValue;
+ }
+
+ ///
+ /// Gets or sets certificate collection to be sent with requests.
+ ///
+ /// X509 Certificate collection.
+ public X509CertificateCollection ClientCertificates { get; set; }
+
+ ///
+ /// Gets or sets the access token for OAuth2 authentication.
+ ///
+ /// This helper property simplifies code generation.
+ ///
+ /// The access token.
+ public virtual string AccessToken { get; set; }
+
+ {{#useRestSharp}}
+ {{#hasOAuthMethods}}
+ ///
+ /// Gets or sets the token URL for OAuth2 authentication.
+ ///
+ /// The OAuth Token URL.
+ public virtual string OAuthTokenUrl { get; set; }
+
+ ///
+ /// Gets or sets the client ID for OAuth2 authentication.
+ ///
+ /// The OAuth Client ID.
+ public virtual string OAuthClientId { get; set; }
+
+ ///
+ /// Gets or sets the client secret for OAuth2 authentication.
+ ///
+ /// The OAuth Client Secret.
+ public virtual string OAuthClientSecret { get; set; }
+
+ ///
+ /// Gets or sets the client scope for OAuth2 authentication.
+ ///
+ /// The OAuth Client Scope.
+ public virtual string{{nrt?}} OAuthScope { get; set; }
+
+ ///
+ /// Gets or sets the flow for OAuth2 authentication.
+ ///
+ /// The OAuth Flow.
+ public virtual OAuthFlow? OAuthFlow { get; set; }
+
+ {{/hasOAuthMethods}}
+ {{/useRestSharp}}
+ ///
+ /// Gets or sets the temporary folder path to store the files downloaded from the server.
+ ///
+ /// Folder path.
+ public virtual string TempFolderPath
+ {
+ get { return _tempFolderPath; }
+
+ set
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ _tempFolderPath = Path.GetTempPath();
+ return;
+ }
+
+ // create the directory if it does not exist
+ if (!Directory.Exists(value))
+ {
+ Directory.CreateDirectory(value);
+ }
+
+ // check if the path contains directory separator at the end
+ if (value[value.Length - 1] == Path.DirectorySeparatorChar)
+ {
+ _tempFolderPath = value;
+ }
+ else
+ {
+ _tempFolderPath = value + Path.DirectorySeparatorChar;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the date time format used when serializing in the ApiClient
+ /// By default, it's set to ISO 8601 - "o", for others see:
+ /// https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx
+ /// and https://msdn.microsoft.com/en-us/library/8kb3ddd4(v=vs.110).aspx
+ /// No validation is done to ensure that the string you're providing is valid
+ ///
+ /// The DateTimeFormat string
+ public virtual string DateTimeFormat
+ {
+ get { return _dateTimeFormat; }
+ set
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ // Never allow a blank or null string, go back to the default
+ _dateTimeFormat = ISO8601_DATETIME_FORMAT;
+ return;
+ }
+
+ // Caution, no validation when you choose date time format other than ISO 8601
+ // Take a look at the above links
+ _dateTimeFormat = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the prefix (e.g. Token) of the API key based on the authentication name.
+ ///
+ /// Whatever you set here will be prepended to the value defined in AddApiKey.
+ ///
+ /// An example invocation here might be:
+ ///
+ /// ApiKeyPrefix["Authorization"] = "Bearer";
+ ///
+ /// … where ApiKey["Authorization"] would then be used to set the value of your bearer token.
+ ///
+ ///
+ /// OAuth2 workflows should set tokens via AccessToken.
+ ///
+ ///
+ /// The prefix of the API key.
+ public virtual IDictionary ApiKeyPrefix
+ {
+ get { return _apiKeyPrefix; }
+ set
+ {
+ if (value == null)
+ {
+ throw new InvalidOperationException("ApiKeyPrefix collection may not be null.");
+ }
+ _apiKeyPrefix = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the API key based on the authentication name.
+ ///
+ /// The API key.
+ public virtual IDictionary ApiKey
+ {
+ get { return _apiKey; }
+ set
+ {
+ if (value == null)
+ {
+ throw new InvalidOperationException("ApiKey collection may not be null.");
+ }
+ _apiKey = value;
+ }
+ }
+ {{#servers.0}}
+
+ ///
+ /// Gets or sets the servers.
+ ///
+ /// The servers.
+ public virtual IList> Servers
+ {
+ get { return _servers; }
+ set
+ {
+ if (value == null)
+ {
+ throw new InvalidOperationException("Servers may not be null.");
+ }
+ _servers = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the operation servers.
+ ///
+ /// The operation servers.
+ public virtual IReadOnlyDictionary>> OperationServers
+ {
+ get { return _operationServers; }
+ set
+ {
+ if (value == null)
+ {
+ throw new InvalidOperationException("Operation servers may not be null.");
+ }
+ _operationServers = value;
+ }
+ }
+
+ ///
+ /// Returns URL based on server settings without providing values
+ /// for the variables
+ ///
+ /// Array index of the server settings.
+ /// The server URL.
+ public string GetServerUrl(int index)
+ {
+ return GetServerUrl(Servers, index, null);
+ }
+
+ ///
+ /// Returns URL based on server settings.
+ ///
+ /// Array index of the server settings.
+ /// Dictionary of the variables and the corresponding values.
+ /// The server URL.
+ public string GetServerUrl(int index, Dictionary inputVariables)
+ {
+ return GetServerUrl(Servers, index, inputVariables);
+ }
+
+ ///
+ /// Returns URL based on operation server settings.
+ ///
+ /// Operation associated with the request path.
+ /// Array index of the server settings.
+ /// The operation server URL.
+ public string GetOperationServerUrl(string operation, int index)
+ {
+ return GetOperationServerUrl(operation, index, null);
+ }
+
+ ///
+ /// Returns URL based on operation server settings.
+ ///
+ /// Operation associated with the request path.
+ /// Array index of the server settings.
+ /// Dictionary of the variables and the corresponding values.
+ /// The operation server URL.
+ public string GetOperationServerUrl(string operation, int index, Dictionary inputVariables)
+ {
+ if (operation != null && OperationServers.TryGetValue(operation, out var operationServer))
+ {
+ return GetServerUrl(operationServer, index, inputVariables);
+ }
+
+ return null;
+ }
+
+ ///
+ /// Returns URL based on server settings.
+ ///
+ /// Dictionary of server settings.
+ /// Array index of the server settings.
+ /// Dictionary of the variables and the corresponding values.
+ /// The server URL.
+ private string GetServerUrl(IList> servers, int index, Dictionary inputVariables)
+ {
+ if (index < 0 || index >= servers.Count)
+ {
+ throw new InvalidOperationException($"Invalid index {index} when selecting the server. Must be less than {servers.Count}.");
+ }
+
+ if (inputVariables == null)
+ {
+ inputVariables = new Dictionary();
+ }
+
+ IReadOnlyDictionary server = servers[index];
+ string url = (string)server["url"];
+
+ if (server.ContainsKey("variables"))
+ {
+ // go through each variable and assign a value
+ foreach (KeyValuePair variable in (IReadOnlyDictionary)server["variables"])
+ {
+
+ IReadOnlyDictionary serverVariables = (IReadOnlyDictionary)(variable.Value);
+
+ if (inputVariables.ContainsKey(variable.Key))
+ {
+ if (((List)serverVariables["enum_values"]).Contains(inputVariables[variable.Key]))
+ {
+ url = url.Replace("{" + variable.Key + "}", inputVariables[variable.Key]);
+ }
+ else
+ {
+ throw new InvalidOperationException($"The variable `{variable.Key}` in the server URL has invalid value #{inputVariables[variable.Key]}. Must be {(List)serverVariables["enum_values"]}");
+ }
+ }
+ else
+ {
+ // use default value
+ url = url.Replace("{" + variable.Key + "}", (string)serverVariables["default_value"]);
+ }
+ }
+ }
+
+ return url;
+ }
+ {{/servers.0}}
+ {{#hasHttpSignatureMethods}}
+
+ ///
+ /// Gets and Sets the HttpSigningConfiguration
+ ///
+ public HttpSigningConfiguration HttpSigningConfiguration
+ {
+ get { return _HttpSigningConfiguration; }
+ set { _HttpSigningConfiguration = value; }
+ }
+ {{/hasHttpSignatureMethods}}
+
+ ///
+ /// Gets and Sets the RemoteCertificateValidationCallback
+ ///
+ public RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; }
+
+ #endregion Properties
+
+ #region Methods
+
+ ///
+ /// Returns a string with essential information for debugging.
+ ///
+ public static string ToDebugReport()
+ {
+ string report = "C# SDK ({{{packageName}}}) Debug Report:\n";
+ report += " OS: " + System.Environment.OSVersion + "\n";
+ report += " .NET Framework Version: " + System.Environment.Version + "\n";
+ report += " Version of the API: {{{version}}}\n";
+ report += " SDK Package Version: {{{packageVersion}}}\n";
+
+ return report;
+ }
+
+ ///
+ /// Add Api Key Header.
+ ///
+ /// Api Key name.
+ /// Api Key value.
+ ///
+ public void AddApiKey(string key, string value)
+ {
+ ApiKey[key] = value;
+ }
+
+ ///
+ /// Sets the API key prefix.
+ ///
+ /// Api Key name.
+ /// Api Key value.
+ public void AddApiKeyPrefix(string key, string value)
+ {
+ ApiKeyPrefix[key] = value;
+ }
+
+ #endregion Methods
+
+ #region Static Members
+ ///
+ /// Merge configurations.
+ ///
+ /// First configuration.
+ /// Second configuration.
+ /// Merged configuration.
+ public static IReadableConfiguration MergeConfigurations(IReadableConfiguration first, IReadableConfiguration second)
+ {
+ if (second == null) return first ?? GlobalConfiguration.Instance;
+
+ Dictionary apiKey = first.ApiKey.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
+ Dictionary apiKeyPrefix = first.ApiKeyPrefix.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
+ Dictionary defaultHeaders = first.DefaultHeaders.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
+
+ foreach (var kvp in second.ApiKey) apiKey[kvp.Key] = kvp.Value;
+ foreach (var kvp in second.ApiKeyPrefix) apiKeyPrefix[kvp.Key] = kvp.Value;
+ foreach (var kvp in second.DefaultHeaders) defaultHeaders[kvp.Key] = kvp.Value;
+
+ var config = new Configuration
+ {
+ ApiKey = apiKey,
+ ApiKeyPrefix = apiKeyPrefix,
+ DefaultHeaders = defaultHeaders,
+ BasePath = second.BasePath ?? first.BasePath,
+ Timeout = second.Timeout,
+ Proxy = second.Proxy ?? first.Proxy,
+ UserAgent = second.UserAgent ?? first.UserAgent,
+ Username = second.Username ?? first.Username,
+ Password = second.Password ?? first.Password,
+ AccessToken = second.AccessToken ?? first.AccessToken,
+ {{#useRestSharp}}
+ {{#hasOAuthMethods}}
+ OAuthTokenUrl = second.OAuthTokenUrl ?? first.OAuthTokenUrl,
+ OAuthClientId = second.OAuthClientId ?? first.OAuthClientId,
+ OAuthClientSecret = second.OAuthClientSecret ?? first.OAuthClientSecret,
+ OAuthScope = second.OAuthScope ?? first.OAuthScope,
+ OAuthFlow = second.OAuthFlow ?? first.OAuthFlow,
+ {{/hasOAuthMethods}}
+ {{/useRestSharp}}
+ {{#hasHttpSignatureMethods}}
+ HttpSigningConfiguration = second.HttpSigningConfiguration ?? first.HttpSigningConfiguration,
+ {{/hasHttpSignatureMethods}}
+ TempFolderPath = second.TempFolderPath ?? first.TempFolderPath,
+ DateTimeFormat = second.DateTimeFormat ?? first.DateTimeFormat,
+ ClientCertificates = second.ClientCertificates ?? first.ClientCertificates,
+ UseDefaultCredentials = second.UseDefaultCredentials,
+ RemoteCertificateValidationCallback = second.RemoteCertificateValidationCallback ?? first.RemoteCertificateValidationCallback,
+ };
+ return config;
+ }
+ #endregion Static Members
+ }
+}
diff --git a/templates-v7/csharp/ExceptionFactory.mustache b/templates-v7/csharp/ExceptionFactory.mustache
new file mode 100644
index 000000000..4a141f6f1
--- /dev/null
+++ b/templates-v7/csharp/ExceptionFactory.mustache
@@ -0,0 +1,14 @@
+{{>partial_header}}
+
+using System;
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// A delegate to ExceptionFactory method
+ ///
+ /// Method name
+ /// Response
+ /// Exceptions
+ {{>visibility}} delegate Exception ExceptionFactory(string methodName, IApiResponse response);
+}
diff --git a/templates-v7/csharp/GlobalConfiguration.mustache b/templates-v7/csharp/GlobalConfiguration.mustache
new file mode 100644
index 000000000..93a9ab8aa
--- /dev/null
+++ b/templates-v7/csharp/GlobalConfiguration.mustache
@@ -0,0 +1,59 @@
+{{>partial_header}}
+
+using System.Collections.Generic;
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// provides a compile-time extension point for globally configuring
+ /// API Clients.
+ ///
+ ///
+ /// A customized implementation via partial class may reside in another file and may
+ /// be excluded from automatic generation via a .openapi-generator-ignore file.
+ ///
+ {{>visibility}} partial class GlobalConfiguration : Configuration
+ {
+ #region Private Members
+
+ private static readonly object GlobalConfigSync = new { };
+ private static IReadableConfiguration _globalConfiguration;
+
+ #endregion Private Members
+
+ #region Constructors
+
+ ///
+ private GlobalConfiguration()
+ {
+ }
+
+ ///
+ public GlobalConfiguration(IDictionary defaultHeader, IDictionary apiKey, IDictionary apiKeyPrefix, string basePath = "http://localhost:3000/api") : base(defaultHeader, apiKey, apiKeyPrefix, basePath)
+ {
+ }
+
+ static GlobalConfiguration()
+ {
+ Instance = new GlobalConfiguration();
+ }
+
+ #endregion Constructors
+
+ ///
+ /// Gets or sets the default Configuration.
+ ///
+ /// Configuration.
+ public static IReadableConfiguration Instance
+ {
+ get { return _globalConfiguration; }
+ set
+ {
+ lock (GlobalConfigSync)
+ {
+ _globalConfiguration = value;
+ }
+ }
+ }
+ }
+}
diff --git a/templates-v7/csharp/HttpMethod.mustache b/templates-v7/csharp/HttpMethod.mustache
new file mode 100644
index 000000000..904a042a9
--- /dev/null
+++ b/templates-v7/csharp/HttpMethod.mustache
@@ -0,0 +1,25 @@
+{{>partial_header}}
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// Http methods supported by swagger
+ ///
+ public enum HttpMethod
+ {
+ /// HTTP GET request.
+ Get,
+ /// HTTP POST request.
+ Post,
+ /// HTTP PUT request.
+ Put,
+ /// HTTP DELETE request.
+ Delete,
+ /// HTTP HEAD request.
+ Head,
+ /// HTTP OPTIONS request.
+ Options,
+ /// HTTP PATCH request.
+ Patch
+ }
+}
diff --git a/templates-v7/csharp/HttpSigningConfiguration.mustache b/templates-v7/csharp/HttpSigningConfiguration.mustache
new file mode 100644
index 000000000..97b855dc5
--- /dev/null
+++ b/templates-v7/csharp/HttpSigningConfiguration.mustache
@@ -0,0 +1,805 @@
+{{>partial_header}}
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Security.Cryptography;
+using System.Text;
+using System.Web;
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// Class for HttpSigning auth related parameter and methods
+ ///
+ public class HttpSigningConfiguration
+ {
+ ///
+ /// Initialize the HashAlgorithm and SigningAlgorithm to default value
+ ///
+ public HttpSigningConfiguration()
+ {
+ HashAlgorithm = HashAlgorithmName.SHA256;
+ SigningAlgorithm = "PKCS1-v15";
+ }
+
+ ///
+ ///Gets the Api keyId
+ ///
+ public string KeyId { get; set; }
+
+ ///
+ /// Gets the Key file path
+ ///
+ public string KeyFilePath { get; set; }
+
+ ///
+ /// Specify the API key in the form of a string, either configure the KeyString property or configure the KeyFilePath property.
+ ///
+ public string KeyString { get; set; }
+
+ ///
+ /// Gets the key pass phrase for password protected key
+ ///
+ public SecureString KeyPassPhrase { get; set; }
+
+ ///
+ /// Gets the HTTP signing header
+ ///
+ public List HttpSigningHeader { get; set; }
+
+ ///
+ /// Gets the hash algorithm sha256 or sha512
+ ///
+ public HashAlgorithmName HashAlgorithm { get; set; }
+
+ ///
+ /// Gets the signing algorithm
+ ///
+ public string SigningAlgorithm { get; set; }
+
+ ///
+ /// Gets the Signature validity period in seconds
+ ///
+ public int SignatureValidityPeriod { get; set; }
+
+ private enum PrivateKeyType
+ {
+ None = 0,
+ RSA = 1,
+ ECDSA = 2,
+ }
+
+ ///
+ /// Gets the Headers for HttpSigning
+ ///
+ /// Base path
+ /// HTTP method
+ /// Path
+ /// Request options
+ /// Http signed headers
+ public Dictionary GetHttpSignedHeader(string basePath,string method, string path, RequestOptions requestOptions)
+ {
+ const string HEADER_REQUEST_TARGET = "(request-target)";
+ //The time when the HTTP signature expires. The API server should reject HTTP requests
+ //that have expired.
+ const string HEADER_EXPIRES = "(expires)";
+ //The 'Date' header.
+ const string HEADER_DATE = "Date";
+ //The 'Host' header.
+ const string HEADER_HOST = "Host";
+ //The time when the HTTP signature was generated.
+ const string HEADER_CREATED = "(created)";
+ //When the 'Digest' header is included in the HTTP signature, the client automatically
+ //computes the digest of the HTTP request body, per RFC 3230.
+ const string HEADER_DIGEST = "Digest";
+ //The 'Authorization' header is automatically generated by the client. It includes
+ //the list of signed headers and a base64-encoded signature.
+ const string HEADER_AUTHORIZATION = "Authorization";
+
+ //Read the api key from the file
+ if(File.Exists(KeyFilePath))
+ {
+ this.KeyString = ReadApiKeyFromFile(KeyFilePath);
+ }
+ else if(string.IsNullOrEmpty(KeyString))
+ {
+ throw new Exception("No API key has been provided. Supply it using either KeyFilePath or KeyString");
+ }
+
+ //Hash table to store singed headers
+ var HttpSignedRequestHeader = new Dictionary();
+ var HttpSignatureHeader = new Dictionary();
+
+ if (HttpSigningHeader.Count == 0)
+ {
+ HttpSigningHeader.Add("(created)");
+ }
+
+ if (requestOptions.PathParameters != null)
+ {
+ foreach (var pathParam in requestOptions.PathParameters)
+ {
+ var tempPath = path.Replace(pathParam.Key, "0");
+ path = string.Format(tempPath, pathParam.Value);
+ }
+ }
+
+ var httpValues = HttpUtility.ParseQueryString(string.Empty);
+ foreach (var parameter in requestOptions.QueryParameters)
+ {
+#if (NETCOREAPP)
+ string framework = RuntimeInformation.FrameworkDescription;
+ string key = framework.StartsWith(".NET 9")?parameter.Key:HttpUtility.UrlEncode(parameter.Key);
+ if (parameter.Value.Count > 1)
+ { // array
+ foreach (var value in parameter.Value)
+ {
+ httpValues.Add(key + "[]", value);
+ }
+ }
+ else
+ {
+ httpValues.Add(key, parameter.Value[0]);
+ }
+#else
+ if (parameter.Value.Count > 1)
+ { // array
+ foreach (var value in parameter.Value)
+ {
+ httpValues.Add(parameter.Key + "[]", value);
+ }
+ }
+ else
+ {
+ httpValues.Add(parameter.Key, parameter.Value[0]);
+ }
+#endif
+ }
+ var uriBuilder = new UriBuilder(string.Concat(basePath, path));
+ uriBuilder.Query = httpValues.ToString().Replace("+", "%20");
+
+ var dateTime = DateTime.Now;
+ string Digest = string.Empty;
+
+ //get the body
+ string requestBody = string.Empty;
+ if (requestOptions.Data != null)
+ {
+ var serializerSettings = new JsonSerializerSettings();
+ requestBody = JsonConvert.SerializeObject(requestOptions.Data, serializerSettings);
+ }
+
+ if (HashAlgorithm == HashAlgorithmName.SHA256)
+ {
+ var bodyDigest = GetStringHash(HashAlgorithm, requestBody);
+ Digest = string.Format("SHA-256={0}", Convert.ToBase64String(bodyDigest));
+ }
+ else if (HashAlgorithm == HashAlgorithmName.SHA512)
+ {
+ var bodyDigest = GetStringHash(HashAlgorithm, requestBody);
+ Digest = string.Format("SHA-512={0}", Convert.ToBase64String(bodyDigest));
+ }
+ else
+ {
+ throw new Exception(string.Format("{0} not supported", HashAlgorithm));
+ }
+
+ foreach (var header in HttpSigningHeader)
+ {
+ if (header.Equals(HEADER_REQUEST_TARGET))
+ {
+ var targetUrl = string.Format("{0} {1}{2}", method.ToLower(), uriBuilder.Path, uriBuilder.Query);
+ HttpSignatureHeader.Add(header.ToLower(), targetUrl);
+ }
+ else if (header.Equals(HEADER_EXPIRES))
+ {
+ var expireDateTime = dateTime.AddSeconds(SignatureValidityPeriod);
+ HttpSignatureHeader.Add(header.ToLower(), GetUnixTime(expireDateTime).ToString());
+ }
+ else if (header.Equals(HEADER_DATE))
+ {
+ var utcDateTime = dateTime.ToUniversalTime().ToString("r");
+ HttpSignatureHeader.Add(header.ToLower(), utcDateTime);
+ HttpSignedRequestHeader.Add(HEADER_DATE, utcDateTime);
+ }
+ else if (header.Equals(HEADER_HOST))
+ {
+ HttpSignatureHeader.Add(header.ToLower(), uriBuilder.Host);
+ HttpSignedRequestHeader.Add(HEADER_HOST, uriBuilder.Host);
+ }
+ else if (header.Equals(HEADER_CREATED))
+ {
+ HttpSignatureHeader.Add(header.ToLower(), GetUnixTime(dateTime).ToString());
+ }
+ else if (header.Equals(HEADER_DIGEST))
+ {
+ HttpSignedRequestHeader.Add(HEADER_DIGEST, Digest);
+ HttpSignatureHeader.Add(header.ToLower(), Digest);
+ }
+ else
+ {
+ bool isHeaderFound = false;
+ foreach (var item in requestOptions.HeaderParameters)
+ {
+ if (string.Equals(item.Key, header, StringComparison.OrdinalIgnoreCase))
+ {
+ HttpSignatureHeader.Add(header.ToLower(), item.Value.ToString());
+ isHeaderFound = true;
+ break;
+ }
+ }
+ if (!isHeaderFound)
+ {
+ throw new Exception(string.Format("Cannot sign HTTP request.Request does not contain the {0} header.",header));
+ }
+ }
+
+ }
+ var headersKeysString = string.Join(" ", HttpSignatureHeader.Keys);
+ var headerValuesList = new List();
+
+ foreach (var keyVal in HttpSignatureHeader)
+ {
+ headerValuesList.Add(string.Format("{0}: {1}", keyVal.Key, keyVal.Value));
+ }
+ //Concatenate headers value separated by new line
+ var headerValuesString = string.Join("\n", headerValuesList);
+ var signatureStringHash = GetStringHash(HashAlgorithm, headerValuesString);
+ string headerSignatureStr = null;
+ var keyType = GetKeyType(KeyString);
+
+ if (keyType == PrivateKeyType.RSA)
+ {
+ headerSignatureStr = GetRSASignature(signatureStringHash);
+ }
+ else if (keyType == PrivateKeyType.ECDSA)
+ {
+ headerSignatureStr = GetECDSASignature(signatureStringHash);
+ }
+ else
+ {
+ throw new Exception(string.Format("Private key type {0} not supported", keyType));
+ }
+ const string cryptographicScheme = "hs2019";
+ var authorizationHeaderValue = string.Format("Signature keyId=\"{0}\",algorithm=\"{1}\"",
+ KeyId, cryptographicScheme);
+
+ if (HttpSignatureHeader.ContainsKey(HEADER_CREATED))
+ {
+ authorizationHeaderValue += string.Format(",created={0}", HttpSignatureHeader[HEADER_CREATED]);
+ }
+
+ if (HttpSignatureHeader.ContainsKey(HEADER_EXPIRES))
+ {
+ authorizationHeaderValue += string.Format(",expires={0}", HttpSignatureHeader[HEADER_EXPIRES]);
+ }
+
+ authorizationHeaderValue += string.Format(",headers=\"{0}\",signature=\"{1}\"",
+ headersKeysString, headerSignatureStr);
+ HttpSignedRequestHeader.Add(HEADER_AUTHORIZATION, authorizationHeaderValue);
+ return HttpSignedRequestHeader;
+ }
+
+ private byte[] GetStringHash(HashAlgorithmName hashAlgorithmName, string stringToBeHashed)
+ {
+ HashAlgorithm{{nrt?}} hashAlgorithm = null;
+
+ if (hashAlgorithmName == HashAlgorithmName.SHA1)
+ hashAlgorithm = SHA1.Create();
+
+ if (hashAlgorithmName == HashAlgorithmName.SHA256)
+ hashAlgorithm = SHA256.Create();
+
+ if (hashAlgorithmName == HashAlgorithmName.SHA512)
+ hashAlgorithm = SHA512.Create();
+
+ if (hashAlgorithmName == HashAlgorithmName.MD5)
+ hashAlgorithm = MD5.Create();
+
+ if (hashAlgorithm == null)
+ throw new NullReferenceException($"{ nameof(hashAlgorithm) } was null.");
+
+ byte[] bytes = Encoding.UTF8.GetBytes(stringToBeHashed);
+ byte[] stringHash = hashAlgorithm.ComputeHash(bytes);
+ return stringHash;
+ }
+
+ private int GetUnixTime(DateTime date2)
+ {
+ DateTime date1 = new DateTime(1970, 01, 01);
+ TimeSpan timeSpan = date2 - date1;
+ return (int)timeSpan.TotalSeconds;
+ }
+
+ private string GetRSASignature(byte[] stringToSign)
+ {
+ if (string.IsNullOrEmpty(KeyString))
+ {
+ throw new Exception("No API key has been provided.");
+ }
+ RSA rsa = GetRSAProviderFromPemFile(KeyString, KeyPassPhrase);
+ if (SigningAlgorithm == "RSASSA-PSS")
+ {
+ var signedbytes = rsa.SignHash(stringToSign, HashAlgorithm, RSASignaturePadding.Pss);
+ return Convert.ToBase64String(signedbytes);
+ }
+ else if (SigningAlgorithm == "PKCS1-v15")
+ {
+ var signedbytes = rsa.SignHash(stringToSign, HashAlgorithm, RSASignaturePadding.Pkcs1);
+ return Convert.ToBase64String(signedbytes);
+ }
+ else
+ {
+ return string.Empty;
+ }
+ }
+
+ ///
+ /// Gets the ECDSA signature
+ ///
+ ///
+ /// ECDSA signature
+ private string GetECDSASignature(byte[] dataToSign)
+ {
+ {{#net60OrLater}}
+ if (!File.Exists(KeyFilePath) && string.IsNullOrEmpty(KeyString))
+ {
+ throw new Exception("No API key has been provided.");
+ }
+
+ var keyStr = KeyString;
+ const string ecKeyHeader = "-----BEGIN EC PRIVATE KEY-----";
+ const string ecKeyFooter = "-----END EC PRIVATE KEY-----";
+ var ecKeyBase64String = keyStr.Replace(ecKeyHeader, "").Replace(ecKeyFooter, "").Trim();
+ var keyBytes = System.Convert.FromBase64String(ecKeyBase64String);
+ var ecdsa = ECDsa.Create();
+
+ var byteCount = 0;
+ if (KeyPassPhrase != null)
+ {
+ IntPtr unmanagedString = IntPtr.Zero;
+ try
+ {
+ // convert secure string to byte array
+ unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(KeyPassPhrase);
+ ecdsa.ImportEncryptedPkcs8PrivateKey(Encoding.UTF8.GetBytes(Marshal.PtrToStringUni(unmanagedString)), keyBytes, out byteCount);
+ }
+ finally
+ {
+ if (unmanagedString != IntPtr.Zero)
+ {
+ Marshal.ZeroFreeBSTR(unmanagedString);
+ }
+ }
+ }
+ else
+ ecdsa.ImportPkcs8PrivateKey(keyBytes, out byteCount);
+
+ var derBytes = ecdsa.SignHash(dataToSign, DSASignatureFormat.Rfc3279DerSequence);
+ var signedString = System.Convert.ToBase64String(derBytes);
+
+ return signedString;
+ {{/net60OrLater}}
+ {{^net60OrLater}}
+ throw new Exception("ECDSA signing is supported only on NETCOREAPP3_0 and above");
+ {{/net60OrLater}}
+ }
+
+ ///
+ /// Convert ANS1 format to DER format. Not recommended to use because it generate invalid signature occasionally.
+ ///
+ ///
+ ///
+ private byte[] ConvertToECDSAANS1Format(byte[] signedBytes)
+ {
+ var derBytes = new List();
+ byte derLength = 68; //default length for ECDSA code signing bit 0x44
+ byte rbytesLength = 32; //R length 0x20
+ byte sbytesLength = 32; //S length 0x20
+ var rBytes = new List();
+ var sBytes = new List();
+ for (int i = 0; i < 32; i++)
+ {
+ rBytes.Add(signedBytes[i]);
+ }
+ for (int i = 32; i < 64; i++)
+ {
+ sBytes.Add(signedBytes[i]);
+ }
+
+ if (rBytes[0] > 0x7F)
+ {
+ derLength++;
+ rbytesLength++;
+ var tempBytes = new List();
+ tempBytes.AddRange(rBytes);
+ rBytes.Clear();
+ rBytes.Add(0x00);
+ rBytes.AddRange(tempBytes);
+ }
+
+ if (sBytes[0] > 0x7F)
+ {
+ derLength++;
+ sbytesLength++;
+ var tempBytes = new List();
+ tempBytes.AddRange(sBytes);
+ sBytes.Clear();
+ sBytes.Add(0x00);
+ sBytes.AddRange(tempBytes);
+
+ }
+
+ derBytes.Add(48); //start of the sequence 0x30
+ derBytes.Add(derLength); //total length r length, type and r bytes
+
+ derBytes.Add(2); //tag for integer
+ derBytes.Add(rbytesLength); //length of r
+ derBytes.AddRange(rBytes);
+
+ derBytes.Add(2); //tag for integer
+ derBytes.Add(sbytesLength); //length of s
+ derBytes.AddRange(sBytes);
+ return derBytes.ToArray();
+ }
+
+ private RSACryptoServiceProvider GetRSAProviderFromPemFile(string keyString, SecureString keyPassPhrase = null)
+ {
+ if (string.IsNullOrEmpty(KeyString))
+ {
+ throw new Exception("No API key has been provided.");
+ }
+
+ const string pempubheader = "-----BEGIN PUBLIC KEY-----";
+ const string pempubfooter = "-----END PUBLIC KEY-----";
+ bool isPrivateKeyFile = true;
+ byte[] pemkey = null;
+ string pemstr = keyString;
+
+ if (pemstr.StartsWith(pempubheader) && pemstr.EndsWith(pempubfooter))
+ {
+ isPrivateKeyFile = false;
+ }
+
+ if (isPrivateKeyFile)
+ {
+ pemkey = ConvertPrivateKeyToBytes(pemstr, keyPassPhrase);
+ if (pemkey == null)
+ {
+ return null;
+ }
+ return DecodeRSAPrivateKey(pemkey);
+ }
+ return null;
+ }
+
+ private byte[] ConvertPrivateKeyToBytes(string instr, SecureString keyPassPhrase = null)
+ {
+ const string pemprivheader = "-----BEGIN RSA PRIVATE KEY-----";
+ const string pemprivfooter = "-----END RSA PRIVATE KEY-----";
+ string pemstr = instr.Trim();
+ byte[] binkey;
+
+ if (!pemstr.StartsWith(pemprivheader) || !pemstr.EndsWith(pemprivfooter))
+ {
+ return null;
+ }
+
+ StringBuilder sb = new StringBuilder(pemstr);
+ sb.Replace(pemprivheader, "");
+ sb.Replace(pemprivfooter, "");
+ string pvkstr = sb.ToString().Trim();
+
+ try
+ { // if there are no PEM encryption info lines, this is an UNencrypted PEM private key
+ binkey = Convert.FromBase64String(pvkstr);
+ return binkey;
+ }
+ catch (global::System.FormatException)
+ {
+ StringReader str = new StringReader(pvkstr);
+
+ //-------- read PEM encryption info. lines and extract salt -----
+ if (!str.ReadLine().StartsWith("Proc-Type: 4,ENCRYPTED"))
+ {
+ return null;
+ }
+ string saltline = str.ReadLine();
+ if (!saltline.StartsWith("DEK-Info: DES-EDE3-CBC,"))
+ {
+ return null;
+ }
+ string saltstr = saltline.Substring(saltline.IndexOf(",") + 1).Trim();
+ byte[] salt = new byte[saltstr.Length / 2];
+ for (int i = 0; i < salt.Length; i++)
+ salt[i] = Convert.ToByte(saltstr.Substring(i * 2, 2), 16);
+ if (str.ReadLine() != "")
+ {
+ return null;
+ }
+
+ //------ remaining b64 data is encrypted RSA key ----
+ string encryptedstr = str.ReadToEnd();
+
+ try
+ { //should have b64 encrypted RSA key now
+ binkey = Convert.FromBase64String(encryptedstr);
+ }
+ catch (global::System.FormatException)
+ { //data is not in base64 format
+ return null;
+ }
+
+ byte[] deskey = GetEncryptedKey(salt, keyPassPhrase, 1, 2); // count=1 (for OpenSSL implementation); 2 iterations to get at least 24 bytes
+ if (deskey == null)
+ {
+ return null;
+ }
+
+ //------ Decrypt the encrypted 3des-encrypted RSA private key ------
+ byte[] rsakey = DecryptKey(binkey, deskey, salt); //OpenSSL uses salt value in PEM header also as 3DES IV
+ return rsakey;
+ }
+ }
+
+ private RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
+ {
+ byte[] bytesModulus, bytesE, bytesD, bytesP, bytesQ, bytesDP, bytesDQ, bytesIQ;
+
+ // --------- Set up stream to decode the asn.1 encoded RSA private key ------
+ MemoryStream mem = new MemoryStream(privkey);
+ BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
+ byte bt = 0;
+ ushort twobytes = 0;
+ int elems = 0;
+ try
+ {
+ twobytes = binr.ReadUInt16();
+ if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
+ {
+ binr.ReadByte(); //advance 1 byte
+ }
+ else if (twobytes == 0x8230)
+ {
+ binr.ReadInt16(); //advance 2 bytes
+ }
+ else
+ {
+ return null;
+ }
+
+ twobytes = binr.ReadUInt16();
+ if (twobytes != 0x0102) //version number
+ {
+ return null;
+ }
+ bt = binr.ReadByte();
+ if (bt != 0x00)
+ {
+ return null;
+ }
+
+ //------ all private key components are Integer sequences ----
+ elems = GetIntegerSize(binr);
+ bytesModulus = binr.ReadBytes(elems);
+
+ elems = GetIntegerSize(binr);
+ bytesE = binr.ReadBytes(elems);
+
+ elems = GetIntegerSize(binr);
+ bytesD = binr.ReadBytes(elems);
+
+ elems = GetIntegerSize(binr);
+ bytesP = binr.ReadBytes(elems);
+
+ elems = GetIntegerSize(binr);
+ bytesQ = binr.ReadBytes(elems);
+
+ elems = GetIntegerSize(binr);
+ bytesDP = binr.ReadBytes(elems);
+
+ elems = GetIntegerSize(binr);
+ bytesDQ = binr.ReadBytes(elems);
+
+ elems = GetIntegerSize(binr);
+ bytesIQ = binr.ReadBytes(elems);
+
+ // ------- create RSACryptoServiceProvider instance and initialize with public key -----
+ RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
+ RSAParameters RSAparams = new RSAParameters();
+ RSAparams.Modulus = bytesModulus;
+ RSAparams.Exponent = bytesE;
+ RSAparams.D = bytesD;
+ RSAparams.P = bytesP;
+ RSAparams.Q = bytesQ;
+ RSAparams.DP = bytesDP;
+ RSAparams.DQ = bytesDQ;
+ RSAparams.InverseQ = bytesIQ;
+ RSA.ImportParameters(RSAparams);
+ return RSA;
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ finally
+ {
+ binr.Close();
+ }
+ }
+
+ private int GetIntegerSize(BinaryReader binr)
+ {
+ byte bt = 0;
+ byte lowbyte = 0x00;
+ byte highbyte = 0x00;
+ int count = 0;
+ bt = binr.ReadByte();
+ if (bt != 0x02) //expect integer
+ {
+ return 0;
+ }
+ bt = binr.ReadByte();
+
+ if (bt == 0x81)
+ {
+ count = binr.ReadByte(); // data size in next byte
+ }
+ else if (bt == 0x82)
+ {
+ highbyte = binr.ReadByte(); // data size in next 2 bytes
+ lowbyte = binr.ReadByte();
+ byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
+ count = BitConverter.ToInt32(modint, 0);
+ }
+ else
+ {
+ count = bt; // we already have the data size
+ }
+ while (binr.ReadByte() == 0x00)
+ {
+ //remove high order zeros in data
+ count -= 1;
+ }
+ binr.BaseStream.Seek(-1, SeekOrigin.Current);
+ //last ReadByte wasn't a removed zero, so back up a byte
+ return count;
+ }
+
+ private byte[] GetEncryptedKey(byte[] salt, SecureString secpswd, int count, int miter)
+ {
+ IntPtr unmanagedPswd = IntPtr.Zero;
+ const int HASHLENGTH = 16; //MD5 bytes
+ byte[] keymaterial = new byte[HASHLENGTH * miter]; //to store concatenated Mi hashed results
+
+ byte[] psbytes = new byte[secpswd.Length];
+ unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
+ Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length);
+ Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);
+
+ // --- concatenate salt and pswd bytes into fixed data array ---
+ byte[] data00 = new byte[psbytes.Length + salt.Length];
+ Array.Copy(psbytes, data00, psbytes.Length); //copy the pswd bytes
+ Array.Copy(salt, 0, data00, psbytes.Length, salt.Length); //concatenate the salt bytes
+
+ // ---- do multi-hashing and concatenate results D1, D2 ... into keymaterial bytes ----
+ MD5 md5 = MD5.Create();
+ byte[] result = null;
+ byte[] hashtarget = new byte[HASHLENGTH + data00.Length]; //fixed length initial hashtarget
+
+ for (int j = 0; j < miter; j++)
+ {
+ // ---- Now hash consecutively for count times ------
+ if (j == 0)
+ {
+ result = data00; //initialize
+ }
+ else
+ {
+ Array.Copy(result, hashtarget, result.Length);
+ Array.Copy(data00, 0, hashtarget, result.Length, data00.Length);
+ result = hashtarget;
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ result = md5.ComputeHash(result);
+ }
+ Array.Copy(result, 0, keymaterial, j * HASHLENGTH, result.Length); //concatenate to keymaterial
+ }
+ byte[] deskey = new byte[24];
+ Array.Copy(keymaterial, deskey, deskey.Length);
+
+ Array.Clear(psbytes, 0, psbytes.Length);
+ Array.Clear(data00, 0, data00.Length);
+ Array.Clear(result, 0, result.Length);
+ Array.Clear(hashtarget, 0, hashtarget.Length);
+ Array.Clear(keymaterial, 0, keymaterial.Length);
+ return deskey;
+ }
+
+ private byte[] DecryptKey(byte[] cipherData, byte[] desKey, byte[] IV)
+ {
+ MemoryStream memst = new MemoryStream();
+ TripleDES alg = TripleDES.Create();
+ alg.Key = desKey;
+ alg.IV = IV;
+ try
+ {
+ CryptoStream cs = new CryptoStream(memst, alg.CreateDecryptor(), CryptoStreamMode.Write);
+ cs.Write(cipherData, 0, cipherData.Length);
+ cs.Close();
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ byte[] decryptedData = memst.ToArray();
+ return decryptedData;
+ }
+
+ ///