diff --git a/sdks/dotnet/.github/workflows/github-actions.yml b/sdks/dotnet/.github/workflows/github-actions.yml index 1046c5fd5..d906a4852 100644 --- a/sdks/dotnet/.github/workflows/github-actions.yml +++ b/sdks/dotnet/.github/workflows/github-actions.yml @@ -40,7 +40,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Restore dependencies run: dotnet restore ${SOLUTION}.sln diff --git a/sdks/dotnet/.gitignore b/sdks/dotnet/.gitignore index 02e0c345b..78294d932 100644 --- a/sdks/dotnet/.gitignore +++ b/sdks/dotnet/.gitignore @@ -361,6 +361,8 @@ MigrationBackup/ # Fody - auto-generated XML schema FodyWeavers.xsd +git_push.sh +global.json vendor /api .openapi-generator diff --git a/sdks/dotnet/.openapi-generator-ignore b/sdks/dotnet/.openapi-generator-ignore index 77ed52efa..7484ee590 100644 --- a/sdks/dotnet/.openapi-generator-ignore +++ b/sdks/dotnet/.openapi-generator-ignore @@ -21,7 +21,3 @@ #docs/*.md # Then explicitly reverse the ignore rule for a single file: #!docs/README.md - -git_push.sh -**/Model/*AllOf* -docs/*AllOf* diff --git a/sdks/dotnet/Dropbox.Sign.sln b/sdks/dotnet/Dropbox.Sign.sln old mode 100755 new mode 100644 diff --git a/sdks/dotnet/README.md b/sdks/dotnet/README.md index 4f715b358..e1c6c1734 100644 --- a/sdks/dotnet/README.md +++ b/sdks/dotnet/README.md @@ -23,8 +23,8 @@ directory that corresponds to the file you want updated. This C# SDK is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: - API version: 3.0.0 -- SDK version: 1.8-dev -- Generator version: 7.8.0 +- SDK version: 2.0-dev +- Generator version: 7.12.0 - Build package: org.openapitools.codegen.languages.CSharpClientCodegen ### Building @@ -49,7 +49,7 @@ this command. ## Dependencies -- [RestSharp](https://www.nuget.org/packages/RestSharp) - 106.13.0 or later +- [RestSharp](https://www.nuget.org/packages/RestSharp) - 112.0.0 or later - [Json.NET](https://www.nuget.org/packages/Newtonsoft.Json/) - 13.0.2 or later - [JsonSubTypes](https://www.nuget.org/packages/JsonSubTypes/) - 1.8.0 or later - [System.ComponentModel.Annotations](https://www.nuget.org/packages/System.ComponentModel.Annotations) - 5.0.0 or later diff --git a/sdks/dotnet/VERSION b/sdks/dotnet/VERSION index d82db9132..c3bb52e88 100644 --- a/sdks/dotnet/VERSION +++ b/sdks/dotnet/VERSION @@ -1 +1 @@ -1.8-dev +2.0-dev diff --git a/sdks/dotnet/bin/copy-constants.php b/sdks/dotnet/bin/copy-constants.php new file mode 100755 index 000000000..885380501 --- /dev/null +++ b/sdks/dotnet/bin/copy-constants.php @@ -0,0 +1,64 @@ +#!/usr/bin/env php +run(); \ No newline at end of file diff --git a/sdks/dotnet/bin/dotnet b/sdks/dotnet/bin/dotnet index f1b73acd7..b3e5c445d 100755 --- a/sdks/dotnet/bin/dotnet +++ b/sdks/dotnet/bin/dotnet @@ -12,4 +12,4 @@ docker run --rm \ -v "${ROOT_DIR}:${WORKING_DIR}" \ -w "${WORKING_DIR}" \ -u root:root \ - mcr.microsoft.com/dotnet/sdk:6.0 "$@" + mcr.microsoft.com/dotnet/sdk:8.0 "$@" diff --git a/sdks/dotnet/openapi-config.yaml b/sdks/dotnet/openapi-config.yaml index a33ad8805..986c99a1f 100644 --- a/sdks/dotnet/openapi-config.yaml +++ b/sdks/dotnet/openapi-config.yaml @@ -6,11 +6,12 @@ additionalProperties: packageCompany: Dropbox Sign API Team packageCopyright: Dropbox 2024 packageDescription: Client library for using the Dropbox Sign API - packageVersion: 1.8-dev + packageVersion: 2.0-dev packageTitle: Dropbox Sign .Net SDK sortModelPropertiesByRequiredFlag: true optionalEmitDefaultValues: true - targetFramework: net6.0 + targetFramework: net8.0 + library: restsharp packageGuid: "{F8C8232D-7020-4603-8E04-18D060AE530B}" legacyDiscriminatorBehavior: true useCustomTemplateCode: true diff --git a/sdks/dotnet/run-build b/sdks/dotnet/run-build index 8a2332ad6..d65fff536 100755 --- a/sdks/dotnet/run-build +++ b/sdks/dotnet/run-build @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# see https://github.com/OpenAPITools/openapi-generator/tree/v7.8.0/modules/openapi-generator/src/main/resources/csharp +# see https://github.com/OpenAPITools/openapi-generator/tree/v7.12.0/modules/openapi-generator/src/main/resources/csharp set -e @@ -18,7 +18,7 @@ rm -f "${DIR}/src/Dropbox.Sign/Model/"*.cs docker run --rm \ -v "${DIR}/:/local" \ - openapitools/openapi-generator-cli:v7.8.0 generate \ + openapitools/openapi-generator-cli:v7.12.0 generate \ -i "/local/openapi-sdk.yaml" \ -c "/local/openapi-config.yaml" \ -t "/local/templates" \ @@ -46,6 +46,9 @@ docker run --rm \ -w "${WORKING_DIR}" \ perl bash ./bin/scan_for +printf "Adding old-style constant names ...\n" +bash "${DIR}/bin/php" ./bin/copy-constants.php + # avoid docker messing with permissions if [[ -z "$GITHUB_ACTIONS" ]]; then chmod 644 "${DIR}/README.md" diff --git a/sdks/dotnet/src/Dropbox.Sign.Test/Dropbox.Sign.Test.csproj b/sdks/dotnet/src/Dropbox.Sign.Test/Dropbox.Sign.Test.csproj index 50c2bf70c..3ea5d9d8c 100644 --- a/sdks/dotnet/src/Dropbox.Sign.Test/Dropbox.Sign.Test.csproj +++ b/sdks/dotnet/src/Dropbox.Sign.Test/Dropbox.Sign.Test.csproj @@ -3,7 +3,7 @@ Dropbox.Sign.Test Dropbox.Sign.Test - net6.0 + net8.0 false Dropbox.Sign.Test diff --git a/sdks/dotnet/src/Dropbox.Sign/Client/ApiClient.cs b/sdks/dotnet/src/Dropbox.Sign/Client/ApiClient.cs index d43d6f7ec..72c26631b 100644 --- a/sdks/dotnet/src/Dropbox.Sign/Client/ApiClient.cs +++ b/sdks/dotnet/src/Dropbox.Sign/Client/ApiClient.cs @@ -110,7 +110,7 @@ internal object Deserialize(RestResponse response, Type type) if (response.Headers != null) { var filePath = string.IsNullOrEmpty(_configuration.TempFolderPath) - ? Path.GetTempPath() + ? global::System.IO.Path.GetTempPath() : _configuration.TempFolderPath; var regex = new Regex(@"Content-Disposition=.*filename=['""]?([^'""\s]+)['""]?$"); foreach (var header in response.Headers) @@ -334,7 +334,7 @@ private RestRequest NewRequest( { foreach (var value in headerParam.Value) { - request.AddHeader(headerParam.Key, value); + request.AddOrUpdateHeader(headerParam.Key, value); } } } @@ -394,13 +394,21 @@ private RestRequest NewRequest( var bytes = ClientUtils.ReadAsBytes(file); var fileStream = file as FileStream; if (fileStream != null) - request.AddFile(fileParam.Key, bytes, Path.GetFileName(fileStream.Name)); + 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; } @@ -472,7 +480,7 @@ private async Task> ExecClientAsync(Func> ExecClientAsync(Func DeserializeRestResponseFromPolicy(RestClient client, RestRequest request, PolicyResult policyResult) + private async Task> DeserializeRestResponseFromPolicyAsync(RestClient client, RestRequest request, PolicyResult policyResult, CancellationToken cancellationToken = default) { if (policyResult.Outcome == OutcomeType.Successful) { - return client.Deserialize(policyResult.Result); + return await client.Deserialize(policyResult.Result, cancellationToken); } else { @@ -594,7 +602,7 @@ private ApiResponse Exec(RestRequest request, RequestOptions options, IRea { var policy = RetryConfiguration.RetryPolicy; var policyResult = policy.ExecuteAndCapture(() => client.Execute(request)); - return Task.FromResult(DeserializeRestResponseFromPolicy(client, request, policyResult)); + return DeserializeRestResponseFromPolicyAsync(client, request, policyResult); } else { @@ -618,7 +626,7 @@ private ApiResponse Exec(RestRequest request, RequestOptions options, IRea { var policy = RetryConfiguration.AsyncRetryPolicy; var policyResult = await policy.ExecuteAndCaptureAsync((ct) => client.ExecuteAsync(request, ct), cancellationToken).ConfigureAwait(false); - return DeserializeRestResponseFromPolicy(client, request, policyResult); + return await DeserializeRestResponseFromPolicyAsync(client, request, policyResult, cancellationToken); } else { diff --git a/sdks/dotnet/src/Dropbox.Sign/Client/ClientUtils.cs b/sdks/dotnet/src/Dropbox.Sign/Client/ClientUtils.cs index 8fb7773d6..ec20198ca 100644 --- a/sdks/dotnet/src/Dropbox.Sign/Client/ClientUtils.cs +++ b/sdks/dotnet/src/Dropbox.Sign/Client/ClientUtils.cs @@ -105,6 +105,12 @@ public static string ParameterToString(object obj, IReadableConfiguration config // 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); + 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); if (obj is bool boolean) return boolean ? "true" : "false"; if (obj is ICollection collection) diff --git a/sdks/dotnet/src/Dropbox.Sign/Client/Configuration.cs b/sdks/dotnet/src/Dropbox.Sign/Client/Configuration.cs index 127056ed0..8316e9803 100644 --- a/sdks/dotnet/src/Dropbox.Sign/Client/Configuration.cs +++ b/sdks/dotnet/src/Dropbox.Sign/Client/Configuration.cs @@ -36,7 +36,7 @@ public class Configuration : IReadableConfiguration /// Version of the package. /// /// Version of the package. - public const string Version = "1.8-dev"; + public const string Version = "2.0-dev"; /// /// Identifier for ISO 8601 DateTime Format @@ -120,7 +120,7 @@ public class Configuration : IReadableConfiguration public Configuration() { Proxy = null; - UserAgent = WebUtility.UrlEncode("OpenAPI-Generator/1.8-dev/csharp"); + UserAgent = WebUtility.UrlEncode("OpenAPI-Generator/2.0-dev/csharp"); BasePath = "https://api.hellosign.com/v3"; DefaultHeaders = new ConcurrentDictionary(); ApiKey = new ConcurrentDictionary(); @@ -163,7 +163,7 @@ public Configuration() }; // Setting Timeout has side effects (forces ApiClient creation). - Timeout = 100000; + Timeout = TimeSpan.FromSeconds(100); } /// @@ -247,9 +247,9 @@ public virtual IDictionary DefaultHeader public virtual IDictionary DefaultHeaders { get; set; } /// - /// Gets or sets the HTTP timeout (milliseconds) of ApiClient. Default to 100000 milliseconds. + /// Gets or sets the HTTP timeout of ApiClient. Defaults to 100 seconds. /// - public virtual int Timeout { get; set; } + public virtual TimeSpan Timeout { get; set; } /// /// Gets or sets the proxy @@ -567,7 +567,7 @@ public static string ToDebugReport() report += " OS: " + System.Environment.OSVersion + "\n"; report += " .NET Framework Version: " + System.Environment.Version + "\n"; report += " Version of the API: 3.0.0\n"; - report += " SDK Package Version: 1.8-dev\n"; + report += " SDK Package Version: 2.0-dev\n"; return report; } diff --git a/sdks/dotnet/src/Dropbox.Sign/Client/IReadableConfiguration.cs b/sdks/dotnet/src/Dropbox.Sign/Client/IReadableConfiguration.cs index a4e493e1c..14469bb07 100644 --- a/sdks/dotnet/src/Dropbox.Sign/Client/IReadableConfiguration.cs +++ b/sdks/dotnet/src/Dropbox.Sign/Client/IReadableConfiguration.cs @@ -72,10 +72,10 @@ public interface IReadableConfiguration string TempFolderPath { get; } /// - /// Gets the HTTP connection timeout (in milliseconds) + /// Gets the HTTP connection timeout. /// /// HTTP connection timeout. - int Timeout { get; } + TimeSpan Timeout { get; } /// /// Gets the proxy. diff --git a/sdks/dotnet/src/Dropbox.Sign/Dropbox.Sign.csproj b/sdks/dotnet/src/Dropbox.Sign/Dropbox.Sign.csproj index db65ddba1..fd6e98aaa 100644 --- a/sdks/dotnet/src/Dropbox.Sign/Dropbox.Sign.csproj +++ b/sdks/dotnet/src/Dropbox.Sign/Dropbox.Sign.csproj @@ -2,7 +2,7 @@ false - net6.0 + net8.0 Dropbox.Sign Dropbox.Sign Library @@ -12,7 +12,7 @@ Client library for using the Dropbox Sign API Dropbox 2024 Dropbox.Sign - 1.8-dev + 2.0-dev bin\$(Configuration)\$(TargetFramework)\Dropbox.Sign.xml https://github.com/hellosign/dropbox-sign-dotnet.git git diff --git a/sdks/dotnet/src/Dropbox.Sign/Model/SubFormFieldRuleAction.cs b/sdks/dotnet/src/Dropbox.Sign/Model/SubFormFieldRuleAction.cs index a15a63f13..b6af36b3d 100644 --- a/sdks/dotnet/src/Dropbox.Sign/Model/SubFormFieldRuleAction.cs +++ b/sdks/dotnet/src/Dropbox.Sign/Model/SubFormFieldRuleAction.cs @@ -40,16 +40,18 @@ public partial class SubFormFieldRuleAction : IEquatable public enum TypeEnum { /// - /// Enum FieldVisibility for value: change-field-visibility + /// Enum ChangeFieldVisibility for value: change-field-visibility /// [EnumMember(Value = "change-field-visibility")] - FieldVisibility = 1, + ChangeFieldVisibility = 1, + FieldVisibility = ChangeFieldVisibility, /// - /// Enum GroupVisibility for value: change-group-visibility + /// Enum ChangeGroupVisibility for value: change-group-visibility /// [EnumMember(Value = "change-group-visibility")] - GroupVisibility = 2 + ChangeGroupVisibility = 2, + GroupVisibility = ChangeGroupVisibility } diff --git a/sdks/dotnet/templates/ApiClient.mustache b/sdks/dotnet/templates/ApiClient.mustache index f8798bd23..bd0a31747 100644 --- a/sdks/dotnet/templates/ApiClient.mustache +++ b/sdks/dotnet/templates/ApiClient.mustache @@ -109,7 +109,7 @@ namespace {{packageName}}.Client if (response.Headers != null) { var filePath = string.IsNullOrEmpty(_configuration.TempFolderPath) - ? Path.GetTempPath() + ? global::System.IO.Path.GetTempPath() : _configuration.TempFolderPath; var regex = new Regex(@"Content-Disposition=.*filename=['""]?([^'""\s]+)['""]?$"); foreach (var header in response.Headers) @@ -338,7 +338,7 @@ namespace {{packageName}}.Client { foreach (var value in headerParam.Value) { - request.AddHeader(headerParam.Key, value); + request.AddOrUpdateHeader(headerParam.Key, value); } } } @@ -398,13 +398,21 @@ namespace {{packageName}}.Client var bytes = ClientUtils.ReadAsBytes(file); var fileStream = file as FileStream; if (fileStream != null) - request.AddFile(fileParam.Key, bytes, Path.GetFileName(fileStream.Name)); + 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; } @@ -476,7 +484,7 @@ namespace {{packageName}}.Client var clientOptions = new RestClientOptions(baseUrl) { ClientCertificates = configuration.ClientCertificates, - MaxTimeout = configuration.Timeout, + Timeout = configuration.Timeout, Proxy = configuration.Proxy, UserAgent = configuration.UserAgent, UseDefaultCredentials = configuration.UseDefaultCredentials, @@ -585,11 +593,11 @@ namespace {{packageName}}.Client } } - private RestResponse DeserializeRestResponseFromPolicy(RestClient client, RestRequest request, PolicyResult policyResult) + private async Task> DeserializeRestResponseFromPolicyAsync(RestClient client, RestRequest request, PolicyResult policyResult, CancellationToken cancellationToken = default) { if (policyResult.Outcome == OutcomeType.Successful) { - return client.Deserialize(policyResult.Result); + return await client.Deserialize(policyResult.Result, cancellationToken); } else { @@ -622,7 +630,7 @@ namespace {{packageName}}.Client { var policy = RetryConfiguration.RetryPolicy; var policyResult = policy.ExecuteAndCapture(() => client.Execute(request)); - return Task.FromResult(DeserializeRestResponseFromPolicy(client, request, policyResult)); + return DeserializeRestResponseFromPolicyAsync(client, request, policyResult); } else { @@ -648,7 +656,7 @@ namespace {{packageName}}.Client { var policy = RetryConfiguration.AsyncRetryPolicy; var policyResult = await policy.ExecuteAndCaptureAsync((ct) => client.ExecuteAsync(request, ct), cancellationToken).ConfigureAwait(false); - return DeserializeRestResponseFromPolicy(client, request, policyResult); + return await DeserializeRestResponseFromPolicyAsync(client, request, policyResult, cancellationToken); } else { diff --git a/sdks/dotnet/templates/ApiClient.v790.mustache b/sdks/dotnet/templates/ApiClient.v790.mustache new file mode 100644 index 000000000..a63d1c2be --- /dev/null +++ b/sdks/dotnet/templates/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/sdks/dotnet/templates/ClientUtils.mustache b/sdks/dotnet/templates/ClientUtils.mustache index cacf67e42..f8481e37f 100644 --- a/sdks/dotnet/templates/ClientUtils.mustache +++ b/sdks/dotnet/templates/ClientUtils.mustache @@ -118,6 +118,14 @@ namespace {{packageName}}.Client // 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) { diff --git a/sdks/dotnet/templates/Configuration.mustache b/sdks/dotnet/templates/Configuration.mustache index 64441d8de..c81ca2623 100644 --- a/sdks/dotnet/templates/Configuration.mustache +++ b/sdks/dotnet/templates/Configuration.mustache @@ -216,7 +216,7 @@ namespace {{packageName}}.Client }; // Setting Timeout has side effects (forces ApiClient creation). - Timeout = 100000; + Timeout = TimeSpan.FromSeconds(100); } /// @@ -300,9 +300,9 @@ namespace {{packageName}}.Client public virtual IDictionary DefaultHeaders { get; set; } /// - /// Gets or sets the HTTP timeout (milliseconds) of ApiClient. Default to 100000 milliseconds. + /// Gets or sets the HTTP timeout of ApiClient. Defaults to 100 seconds. /// - public virtual int Timeout { get; set; } + public virtual TimeSpan Timeout { get; set; } /// /// Gets or sets the proxy diff --git a/sdks/dotnet/templates/Configuration.v790.mustache b/sdks/dotnet/templates/Configuration.v790.mustache new file mode 100644 index 000000000..2753aafb3 --- /dev/null +++ b/sdks/dotnet/templates/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/sdks/dotnet/templates/HttpSigningConfiguration.mustache b/sdks/dotnet/templates/HttpSigningConfiguration.mustache index faca67594..97b855dc5 100644 --- a/sdks/dotnet/templates/HttpSigningConfiguration.mustache +++ b/sdks/dotnet/templates/HttpSigningConfiguration.mustache @@ -133,16 +133,18 @@ namespace {{packageName}}.Client 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(HttpUtility.UrlEncode(parameter.Key) + "[]", value); + httpValues.Add(key + "[]", value); } } else { - httpValues.Add(HttpUtility.UrlEncode(parameter.Key), parameter.Value[0]); + httpValues.Add(key, parameter.Value[0]); } #else if (parameter.Value.Count > 1) @@ -389,7 +391,7 @@ namespace {{packageName}}.Client } /// - /// Convert ANS1 format to DER format. Not recommended to use because it generate inavlid signature occationally. + /// Convert ANS1 format to DER format. Not recommended to use because it generate invalid signature occasionally. /// /// /// diff --git a/sdks/dotnet/templates/IReadableConfiguration.mustache b/sdks/dotnet/templates/IReadableConfiguration.mustache index 5981728b4..6712aa632 100644 --- a/sdks/dotnet/templates/IReadableConfiguration.mustache +++ b/sdks/dotnet/templates/IReadableConfiguration.mustache @@ -101,10 +101,10 @@ namespace {{packageName}}.Client string TempFolderPath { get; } /// - /// Gets the HTTP connection timeout (in milliseconds) + /// Gets the HTTP connection timeout. /// /// HTTP connection timeout. - int Timeout { get; } + TimeSpan Timeout { get; } /// /// Gets the proxy. diff --git a/sdks/dotnet/templates/IReadableConfiguration.v790.mustache b/sdks/dotnet/templates/IReadableConfiguration.v790.mustache new file mode 100644 index 000000000..5981728b4 --- /dev/null +++ b/sdks/dotnet/templates/IReadableConfiguration.v790.mustache @@ -0,0 +1,178 @@ +{{>partial_header}} + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +{{#useRestSharp}} +{{#hasOAuthMethods}}using {{packageName}}.Client.Auth; +{{/hasOAuthMethods}} +{{/useRestSharp}} + +namespace {{packageName}}.Client +{ + /// + /// Represents a readable-only configuration contract. + /// + public interface IReadableConfiguration + { + /// + /// Gets the access token. + /// + /// Access token. + string AccessToken { get; } + + {{#useRestSharp}} + {{#hasOAuthMethods}} + /// + /// Gets the OAuth token URL. + /// + /// OAuth Token URL. + string OAuthTokenUrl { get; } + + /// + /// Gets the OAuth client ID. + /// + /// OAuth Client ID. + string OAuthClientId { get; } + + /// + /// Gets the OAuth client secret. + /// + /// OAuth Client Secret. + string OAuthClientSecret { get; } + + /// + /// Gets the OAuth token scope. + /// + /// OAuth Token scope. + string{{nrt?}} OAuthScope { get; } + + /// + /// Gets the OAuth flow. + /// + /// OAuth Flow. + OAuthFlow? OAuthFlow { get; } + + {{/hasOAuthMethods}} + {{/useRestSharp}} + /// + /// Gets the API key. + /// + /// API key. + IDictionary ApiKey { get; } + + /// + /// Gets the API key prefix. + /// + /// API key prefix. + IDictionary ApiKeyPrefix { get; } + + /// + /// Gets the base path. + /// + /// Base path. + string BasePath { get; } + + /// + /// Gets the date time format. + /// + /// Date time format. + string DateTimeFormat { get; } + + /// + /// Gets the default header. + /// + /// Default header. + [Obsolete("Use DefaultHeaders instead.")] + IDictionary DefaultHeader { get; } + + /// + /// Gets the default headers. + /// + /// Default headers. + IDictionary DefaultHeaders { get; } + + /// + /// Gets the temp folder path. + /// + /// Temp folder path. + string TempFolderPath { get; } + + /// + /// Gets the HTTP connection timeout (in milliseconds) + /// + /// HTTP connection timeout. + int Timeout { get; } + + /// + /// Gets the proxy. + /// + /// Proxy. + WebProxy Proxy { get; } + + /// + /// Gets the user agent. + /// + /// User agent. + string UserAgent { get; } + + /// + /// Gets the username. + /// + /// Username. + string Username { get; } + + /// + /// Gets the password. + /// + /// Password. + string Password { get; } + + /// + /// 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. + /// + bool UseDefaultCredentials { get; } + + /// + /// Get the servers associated with the operation. + /// + /// Operation servers. + IReadOnlyDictionary>> OperationServers { get; } + + /// + /// Gets the API key with prefix. + /// + /// API key identifier (authentication scheme). + /// API key with prefix. + string GetApiKeyWithPrefix(string apiKeyIdentifier); + + /// + /// Gets the Operation server url at the provided index. + /// + /// Operation server name. + /// Index of the operation server settings. + /// + string GetOperationServerUrl(string operation, int index); + + /// + /// Gets certificate collection to be sent with requests. + /// + /// X509 Certificate collection. + X509CertificateCollection ClientCertificates { get; } + {{#hasHttpSignatureMethods}} + + /// + /// Gets the HttpSigning configuration + /// + HttpSigningConfiguration HttpSigningConfiguration { get; } + {{/hasHttpSignatureMethods}} + + /// + /// Callback function for handling the validation of remote certificates. Useful for certificate pinning and + /// overriding certificate errors in the scope of a request. + /// + RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; } + } +} diff --git a/sdks/dotnet/templates/README.mustache b/sdks/dotnet/templates/README.mustache index f70a5dae2..7455a8f95 100644 --- a/sdks/dotnet/templates/README.mustache +++ b/sdks/dotnet/templates/README.mustache @@ -67,7 +67,7 @@ this command. ## Dependencies {{#useRestSharp}} -- [RestSharp](https://www.nuget.org/packages/RestSharp) - 106.13.0 or later +- [RestSharp](https://www.nuget.org/packages/RestSharp) - 112.0.0 or later {{/useRestSharp}} - [Json.NET](https://www.nuget.org/packages/Newtonsoft.Json/) - 13.0.2 or later - [JsonSubTypes](https://www.nuget.org/packages/JsonSubTypes/) - 1.8.0 or later diff --git a/sdks/dotnet/templates/Solution.mustache b/sdks/dotnet/templates/Solution.mustache index f5589670a..268f8c7f8 100644 --- a/sdks/dotnet/templates/Solution.mustache +++ b/sdks/dotnet/templates/Solution.mustache @@ -2,14 +2,16 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio {{^netStandard}}2012{{/netStandard}}{{#netStandard}}14{{/netStandard}} VisualStudioVersion = {{^netStandard}}12.0.0.0{{/netStandard}}{{#netStandard}}14.0.25420.1{{/netStandard}} MinimumVisualStudioVersion = {{^netStandard}}10.0.0.1{{/netStandard}}{{#netStandard}}10.0.40219.1{{/netStandard}} -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "{{packageName}}", "src\{{packageName}}\{{packageName}}.csproj", "{{packageGuid}}" -EndProject {{^useCustomTemplateCode}} -{{^excludeTests}}Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "{{testPackageName}}", "src\{{testPackageName}}\{{testPackageName}}.csproj", "{19F1DEBC-DE5E-4517-8062-F000CD499087}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "{{packageName}}", "{{sourceFolder}}\{{packageName}}\{{packageName}}.csproj", "{{packageGuid}}" +EndProject +{{^excludeTests}}Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "{{testPackageName}}", "{{sourceFolder}}\{{testPackageName}}\{{testPackageName}}.csproj", "{19F1DEBC-DE5E-4517-8062-F000CD499087}" EndProject {{/excludeTests}}Global {{/useCustomTemplateCode}} {{#useCustomTemplateCode}} +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "{{packageName}}", "src\{{packageName}}\{{packageName}}.csproj", "{{packageGuid}}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dropbox.Sign.Test", "src\Dropbox.Sign.Test\Dropbox.Sign.Test.csproj", "{C305EB17-93FE-4BDA-89A4-120BD8C8A88A}" EndProject Global diff --git a/sdks/dotnet/templates/api.mustache b/sdks/dotnet/templates/api.mustache index 632833ab9..c0b29af2e 100644 --- a/sdks/dotnet/templates/api.mustache +++ b/sdks/dotnet/templates/api.mustache @@ -317,6 +317,7 @@ namespace {{packageName}}.{{apiPackage}} {{^useCustomTemplateCode}} var localVarContentType = {{packageName}}.Client.ClientUtils.SelectHeaderContentType(_contentTypes); + var localVarMultipartFormData = localVarContentType == "multipart/form-data"; {{/useCustomTemplateCode}} if (localVarContentType != null) { @@ -403,7 +404,7 @@ namespace {{packageName}}.{{apiPackage}} {{/isArray}} {{/isFile}} {{^isFile}} - localVarRequestOptions.FormParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.{{#isPrimitiveType}}ParameterToString{{/isPrimitiveType}}{{^isPrimitiveType}}Serialize{{/isPrimitiveType}}({{paramName}})); // form parameter + localVarRequestOptions.FormParameters.Add("{{baseName}}", {{#isPrimitiveType}}{{packageName}}.Client.ClientUtils.ParameterToString({{paramName}}){{/isPrimitiveType}}{{^isPrimitiveType}}localVarMultipartFormData ? {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}}) : {{packageName}}.Client.ClientUtils.Serialize({{paramName}}){{/isPrimitiveType}}); // form parameter {{/isFile}} {{/required}} {{^required}} @@ -425,7 +426,7 @@ namespace {{packageName}}.{{apiPackage}} {{/isArray}} {{/isFile}} {{^isFile}} - localVarRequestOptions.FormParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.{{#isPrimitiveType}}ParameterToString{{/isPrimitiveType}}{{^isPrimitiveType}}Serialize{{/isPrimitiveType}}({{paramName}})); // form parameter + localVarRequestOptions.FormParameters.Add("{{baseName}}", {{#isPrimitiveType}}{{packageName}}.Client.ClientUtils.ParameterToString({{paramName}}){{/isPrimitiveType}}{{^isPrimitiveType}}localVarMultipartFormData ? {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}}) : {{packageName}}.Client.ClientUtils.Serialize({{paramName}}){{/isPrimitiveType}}); // form parameter {{/isFile}} } {{/required}} diff --git a/sdks/dotnet/templates/auth/OAuthAuthenticator.mustache b/sdks/dotnet/templates/auth/OAuthAuthenticator.mustache index d71f262a8..111270ebf 100644 --- a/sdks/dotnet/templates/auth/OAuthAuthenticator.mustache +++ b/sdks/dotnet/templates/auth/OAuthAuthenticator.mustache @@ -11,8 +11,25 @@ namespace {{packageName}}.Client.Auth /// /// An authenticator for OAuth2 authentication flows /// - public class OAuthAuthenticator : AuthenticatorBase + public class OAuthAuthenticator : IAuthenticator { + private TokenResponse{{nrt?}} _token; + + /// + /// Returns the current authentication token. Can return null if there is no authentication token, or it has expired. + /// + public string{{nrt?}} Token + { + get + { + if (_token == null) return null; + if (_token.ExpiresIn == null) return _token.AccessToken; + if (_token.ExpiresAt < DateTime.Now) return null; + + return _token.AccessToken; + } + } + readonly string _tokenUrl; readonly string _clientId; readonly string _clientSecret; @@ -31,7 +48,7 @@ namespace {{packageName}}.Client.Auth string{{nrt?}} scope, OAuthFlow? flow, JsonSerializerSettings serializerSettings, - IReadableConfiguration configuration) : base("") + IReadableConfiguration configuration) { _tokenUrl = tokenUrl; _clientId = clientId; @@ -62,9 +79,13 @@ namespace {{packageName}}.Client.Auth /// /// Creates an authentication parameter from an access token. /// - /// Access token to create a parameter from. /// An authentication parameter. - protected override async ValueTask GetAuthenticationParameter(string accessToken) +{{^useCustomTemplateCode}} + protected async ValueTask GetAuthenticationParameter() +{{/useCustomTemplateCode}} +{{#useCustomTemplateCode}} + protected async ValueTask GetAuthenticationParameter(string accessToken) +{{/useCustomTemplateCode}} { var token = string.IsNullOrEmpty(Token) ? await GetToken().ConfigureAwait(false) : Token; return new HeaderParameter(KnownHeaders.Authorization, token); @@ -76,31 +97,45 @@ namespace {{packageName}}.Client.Auth /// An authentication token. async Task GetToken() { - var client = new RestClient(_tokenUrl, - configureSerialization: serializerConfig => serializerConfig.UseSerializer(() => new CustomJsonCodec(_serializerSettings, _configuration))); - - var request = new RestRequest() - .AddParameter("grant_type", _grantType) - .AddParameter("client_id", _clientId) - .AddParameter("client_secret", _clientSecret); + var client = new RestClient(_tokenUrl, configureSerialization: serializerConfig => serializerConfig.UseSerializer(() => new CustomJsonCodec(_serializerSettings, _configuration))); + var request = new RestRequest(); + if (!string.IsNullOrWhiteSpace(_token?.RefreshToken)) + { + request.AddParameter("grant_type", "refresh_token") + .AddParameter("refresh_token", _token.RefreshToken); + } + else + { + request + .AddParameter("grant_type", _grantType) + .AddParameter("client_id", _clientId) + .AddParameter("client_secret", _clientSecret); + } if (!string.IsNullOrEmpty(_scope)) { request.AddParameter("scope", _scope); } - - var response = await client.PostAsync(request).ConfigureAwait(false); - + _token = await client.PostAsync(request).ConfigureAwait(false); // RFC6749 - token_type is case insensitive. // RFC6750 - In Authorization header Bearer should be capitalized. // Fix the capitalization irrespective of token_type casing. - switch (response.TokenType?.ToLower()) + switch (_token?.TokenType?.ToLower()) { case "bearer": - return $"Bearer {response.AccessToken}"; + return $"Bearer {_token.AccessToken}"; default: - return $"{response.TokenType} {response.AccessToken}"; + return $"{_token?.TokenType} {_token?.AccessToken}"; } } + + /// + /// Retrieves the authentication token (creating a new one if necessary) and adds it to the current request + /// + /// + /// + /// + public async ValueTask Authenticate(IRestClient client, RestRequest request) + => request.AddOrUpdateParameter(await GetAuthenticationParameter().ConfigureAwait(false)); } } diff --git a/sdks/dotnet/templates/auth/TokenResponse.mustache b/sdks/dotnet/templates/auth/TokenResponse.mustache index f118b97a9..7a72e04c1 100644 --- a/sdks/dotnet/templates/auth/TokenResponse.mustache +++ b/sdks/dotnet/templates/auth/TokenResponse.mustache @@ -1,5 +1,6 @@ {{>partial_header}} +using System; using Newtonsoft.Json; namespace {{packageName}}.Client.Auth @@ -10,5 +11,14 @@ namespace {{packageName}}.Client.Auth public string TokenType { get; set; } [JsonProperty("access_token")] public string AccessToken { get; set; } + [JsonProperty("expires_in")] + public int? ExpiresIn { get; set; } + [JsonProperty("created")] + public DateTime? Created { get; set; } + + [JsonProperty("refresh_token")] + public string{{nrt?}} RefreshToken { get; set; } + + public DateTime? ExpiresAt => ExpiresIn == null ? null : Created?.AddSeconds(ExpiresIn.Value); } } \ No newline at end of file diff --git a/sdks/dotnet/templates/gitignore.mustache b/sdks/dotnet/templates/gitignore.mustache index a41122f00..da28f4ecd 100644 --- a/sdks/dotnet/templates/gitignore.mustache +++ b/sdks/dotnet/templates/gitignore.mustache @@ -364,6 +364,8 @@ MigrationBackup/ FodyWeavers.xsd {{#useCustomTemplateCode}} +git_push.sh +global.json vendor /api .openapi-generator diff --git a/sdks/dotnet/templates/libraries/generichost/ApiTestsBase.mustache b/sdks/dotnet/templates/libraries/generichost/ApiTestsBase.mustache index 3292a1e86..c71bc79b9 100644 --- a/sdks/dotnet/templates/libraries/generichost/ApiTestsBase.mustache +++ b/sdks/dotnet/templates/libraries/generichost/ApiTestsBase.mustache @@ -31,7 +31,7 @@ namespace {{packageName}}.Test.{{apiPackage}} {{#lambda.trimTrailingWithNewLine}} {{#apiKeyMethods}} string apiKeyTokenValue{{-index}} = context.Configuration[""] ?? throw new Exception("Token not found."); - ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}(apiKeyTokenValue{{-index}}, ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{keyParamName}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); + ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}(apiKeyTokenValue{{-index}}, ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); options.AddTokens(apiKeyToken{{-index}}); {{/apiKeyMethods}} diff --git a/sdks/dotnet/templates/libraries/generichost/ClientUtils.mustache b/sdks/dotnet/templates/libraries/generichost/ClientUtils.mustache index 269d20c4d..357d2197c 100644 --- a/sdks/dotnet/templates/libraries/generichost/ClientUtils.mustache +++ b/sdks/dotnet/templates/libraries/generichost/ClientUtils.mustache @@ -12,7 +12,11 @@ using System.Text; using System.Text.Json; using System.Text.RegularExpressions;{{#useCompareNetObjects}} using KellermanSoftware.CompareNetObjects;{{/useCompareNetObjects}} +{{#models}} +{{#-first}} using {{packageName}}.{{modelPackage}}; +{{/-first}} +{{/models}} using System.Runtime.CompilerServices; {{>Assembly}}namespace {{packageName}}.{{clientPackage}} @@ -20,7 +24,7 @@ using System.Runtime.CompilerServices; /// /// Utility functions providing some benefit to API client consumers. /// - {{>visibility}} static class ClientUtils + {{>visibility}} static {{#net70OrLater}}partial {{/net70OrLater}}class ClientUtils { {{#useCompareNetObjects}} /// @@ -60,7 +64,7 @@ using System.Runtime.CompilerServices; /// /// The {{keyParamName}} header /// - {{#lambda.titlecase}}{{keyParamName}}{{/lambda.titlecase}}{{^-last}},{{/-last}} + {{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}}{{^-last}},{{/-last}} {{/apiKeyMethods}} } @@ -76,7 +80,7 @@ using System.Runtime.CompilerServices; return value switch { {{#apiKeyMethods}} - ApiKeyHeader.{{#lambda.titlecase}}{{keyParamName}}{{/lambda.titlecase}} => "{{keyParamName}}", + ApiKeyHeader.{{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}} => "{{keyParamName}}", {{/apiKeyMethods}} _ => throw new System.ComponentModel.InvalidEnumArgumentException(nameof(value), (int)value, typeof(ApiKeyHeader)), }; @@ -85,7 +89,7 @@ using System.Runtime.CompilerServices; switch(value) { {{#apiKeyMethods}} - case ApiKeyHeader.{{#lambda.titlecase}}{{keyParamName}}{{/lambda.titlecase}}: + case ApiKeyHeader.{{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}}: return "{{keyParamName}}"; {{/apiKeyMethods}} default: @@ -139,17 +143,6 @@ using System.Runtime.CompilerServices; } } - /// - /// 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; - } - /// /// 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 ",". @@ -172,6 +165,10 @@ using System.Runtime.CompilerServices; // 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(format); + {{#net60OrLater}} + if (obj is DateOnly dateOnly) + return dateOnly.ToString(format); + {{/net60OrLater}} if (obj is bool boolean) return boolean ? "true" @@ -317,7 +314,13 @@ using System.Runtime.CompilerServices; /// /// 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]*(;.*)?$"); + {{#net70OrLater}} + [GeneratedRegex("(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$")] + private static partial Regex JsonRegex(); + {{/net70OrLater}} + {{^net70OrLater}} + private static readonly Regex JsonRegex = new Regex("(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$"); + {{/net70OrLater}} /// /// Check if the given MIME is a JSON MIME. @@ -333,7 +336,7 @@ using System.Runtime.CompilerServices; { if (string.IsNullOrWhiteSpace(mime)) return false; - return JsonRegex.IsMatch(mime) || mime.Equals("application/json-patch+json"); + return {{#net70OrLater}}JsonRegex(){{/net70OrLater}}{{^net70OrLater}}JsonRegex{{/net70OrLater}}.IsMatch(mime) || mime.Equals("application/json-patch+json"); } /// diff --git a/sdks/dotnet/templates/libraries/generichost/DependencyInjectionTests.mustache b/sdks/dotnet/templates/libraries/generichost/DependencyInjectionTests.mustache index aadf2c731..6085b51e5 100644 --- a/sdks/dotnet/templates/libraries/generichost/DependencyInjectionTests.mustache +++ b/sdks/dotnet/templates/libraries/generichost/DependencyInjectionTests.mustache @@ -21,7 +21,7 @@ namespace {{packageName}}.Test.{{apiPackage}} { {{#lambda.trimTrailingWithNewLine}} {{#apiKeyMethods}} - ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}("", ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{keyParamName}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); + ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}("", ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); options.AddTokens(apiKeyToken{{-index}}); {{/apiKeyMethods}} @@ -55,7 +55,7 @@ namespace {{packageName}}.Test.{{apiPackage}} { {{#lambda.trimTrailingWithNewLine}} {{#apiKeyMethods}} - ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}("", ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{keyParamName}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); + ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}("", ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); options.AddTokens(apiKeyToken{{-index}}); {{/apiKeyMethods}} @@ -92,7 +92,7 @@ namespace {{packageName}}.Test.{{apiPackage}} { {{#lambda.trimTrailingWithNewLine}} {{#apiKeyMethods}} - ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}("", ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{keyParamName}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); + ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}("", ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); options.AddTokens(apiKeyToken{{-index}}); {{/apiKeyMethods}} @@ -129,7 +129,7 @@ namespace {{packageName}}.Test.{{apiPackage}} { {{#lambda.trimTrailingWithNewLine}} {{#apiKeyMethods}} - ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}("", ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{keyParamName}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); + ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}("", ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{#lambda.alphabet_or_underscore}}{{keyParamName}}{{/lambda.alphabet_or_underscore}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1)); options.AddTokens(apiKeyToken{{-index}}); {{/apiKeyMethods}} diff --git a/sdks/dotnet/templates/libraries/generichost/ExceptionEventArgs.mustache b/sdks/dotnet/templates/libraries/generichost/ExceptionEventArgs.mustache index 016ef7c69..b74fcfa0a 100644 --- a/sdks/dotnet/templates/libraries/generichost/ExceptionEventArgs.mustache +++ b/sdks/dotnet/templates/libraries/generichost/ExceptionEventArgs.mustache @@ -13,7 +13,7 @@ namespace {{packageName}}.{{clientPackage}} public Exception Exception { get; } /// - /// The ExcepetionEventArgs + /// The ExceptionEventArgs /// /// public ExceptionEventArgs(Exception exception) diff --git a/sdks/dotnet/templates/libraries/generichost/HostConfiguration.mustache b/sdks/dotnet/templates/libraries/generichost/HostConfiguration.mustache index d7d1e3bf3..1333f0e67 100644 --- a/sdks/dotnet/templates/libraries/generichost/HostConfiguration.mustache +++ b/sdks/dotnet/templates/libraries/generichost/HostConfiguration.mustache @@ -11,7 +11,11 @@ using System.Text.Json.Serialization; using System.Net.Http; using Microsoft.Extensions.DependencyInjection; using {{packageName}}.{{apiPackage}}; +{{#models}} +{{#-first}} using {{packageName}}.{{modelPackage}}; +{{/-first}} +{{/models}} namespace {{packageName}}.{{clientPackage}} { diff --git a/sdks/dotnet/templates/libraries/generichost/HttpSigningConfiguration.mustache b/sdks/dotnet/templates/libraries/generichost/HttpSigningConfiguration.mustache index 5e0f7739d..357626fcc 100644 --- a/sdks/dotnet/templates/libraries/generichost/HttpSigningConfiguration.mustache +++ b/sdks/dotnet/templates/libraries/generichost/HttpSigningConfiguration.mustache @@ -65,7 +65,7 @@ namespace {{packageName}}.{{clientPackage}} public string SigningAlgorithm { get; set; } /// - /// Gets the Signature validaty period in seconds + /// Gets the Signature validity period in seconds /// public int SignatureValidityPeriod { get; set; } diff --git a/sdks/dotnet/templates/libraries/generichost/JsonConverter.mustache b/sdks/dotnet/templates/libraries/generichost/JsonConverter.mustache index 189acfd74..0ff2753e3 100644 --- a/sdks/dotnet/templates/libraries/generichost/JsonConverter.mustache +++ b/sdks/dotnet/templates/libraries/generichost/JsonConverter.mustache @@ -51,7 +51,7 @@ {{/-first}} if (discriminator != null && discriminator.Equals("{{name}}")) - return JsonSerializer.Deserialize<{{{name}}}>(ref utf8JsonReader, jsonSerializerOptions) ?? throw new JsonException("The result was an unexpected value."); + return JsonSerializer.Deserialize<{{{classname}}}>(ref utf8JsonReader, jsonSerializerOptions) ?? throw new JsonException("The result was an unexpected value."); {{/children}} {{/discriminator}} @@ -342,13 +342,13 @@ public override void Write(Utf8JsonWriter writer, {{classname}} {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}, JsonSerializerOptions jsonSerializerOptions) { {{#lambda.trimLineBreaks}} - {{#lambda.copy}} + {{#lambda.copyText}} {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}} - {{/lambda.copy}} + {{/lambda.copyText}} {{#discriminator}} {{#children}} - if ({{#lambda.pasteLine}}{{/lambda.pasteLine}} is {{classname}} {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}){ - JsonSerializer.Serialize<{{{name}}}>(writer, {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}, jsonSerializerOptions); + if ({{#lambda.paste}}{{/lambda.paste}} is {{classname}} {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}){ + JsonSerializer.Serialize<{{{classname}}}>(writer, {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}, jsonSerializerOptions); return; } @@ -439,7 +439,7 @@ {{#isDiscriminator}} {{^model.composedSchemas.anyOf}} {{^model.composedSchemas.oneOf}} - writer.WriteString("{{baseName}}", {{^isEnum}}{{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{/isEnum}}{{#isNew}}{{#isEnum}}{{#isInnerEnum}}{{classname}}.{{{datatypeWithEnum}}}ToJsonValue{{/isInnerEnum}}{{^isInnerEnum}}{{{datatypeWithEnum}}}ValueConverter.ToJsonValue{{/isInnerEnum}}({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{^required}}.Value{{/required}}){{/isEnum}}{{/isNew}}); + writer.WriteString("{{baseName}}", {{^isEnum}}{{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{/isEnum}}{{#isEnum}}{{#isInnerEnum}}{{classname}}.{{{datatypeWithEnum}}}ToJsonValue{{/isInnerEnum}}{{^isInnerEnum}}{{{datatypeWithEnum}}}ValueConverter.ToJsonValue{{/isInnerEnum}}({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{^required}}.Value{{/required}}){{/isEnum}}); {{/model.composedSchemas.oneOf}} {{/model.composedSchemas.anyOf}} @@ -449,9 +449,9 @@ {{^isMap}} {{^isEnum}} {{^isUuid}} - {{#lambda.copy}} + {{#lambda.copyText}} writer.WriteString("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}); - {{/lambda.copy}} + {{/lambda.copyText}} {{#lambda.indent3}} {{>WriteProperty}} {{/lambda.indent3}} @@ -460,44 +460,44 @@ {{/isMap}} {{/isString}} {{#isBoolean}} - {{#lambda.copy}} + {{#lambda.copyText}} writer.WriteBoolean("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{^required}}Option.Value{{#vendorExtensions.x-is-value-type}}{{nrt!}}.Value{{/vendorExtensions.x-is-value-type}}{{/required}}{{#required}}{{#isNullable}}.Value{{/isNullable}}{{/required}}); - {{/lambda.copy}} + {{/lambda.copyText}} {{#lambda.indent3}} {{>WriteProperty}} {{/lambda.indent3}} {{/isBoolean}} {{^isEnum}} {{#isNumeric}} - {{#lambda.copy}} + {{#lambda.copyText}} writer.WriteNumber("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{^required}}Option.Value{{#vendorExtensions.x-is-value-type}}{{nrt!}}.Value{{/vendorExtensions.x-is-value-type}}{{/required}}{{#required}}{{#isNullable}}.Value{{/isNullable}}{{/required}}); - {{/lambda.copy}} + {{/lambda.copyText}} {{#lambda.indent3}} {{>WriteProperty}} {{/lambda.indent3}} {{/isNumeric}} {{/isEnum}} {{#isDate}} - {{#lambda.copy}} + {{#lambda.copyText}} writer.WriteString("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{^required}}Option.Value{{#vendorExtensions.x-is-value-type}}{{nrt!}}.Value{{/vendorExtensions.x-is-value-type}}{{/required}}{{#required}}{{#isNullable}}.Value{{/isNullable}}{{/required}}.ToString({{name}}Format)); - {{/lambda.copy}} + {{/lambda.copyText}} {{#lambda.indent3}} {{>WriteProperty}} {{/lambda.indent3}} {{/isDate}} {{#isDateTime}} - {{#lambda.copy}} + {{#lambda.copyText}} writer.WriteString("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{^required}}Option.Value{{#vendorExtensions.x-is-value-type}}{{nrt!}}.Value{{/vendorExtensions.x-is-value-type}}{{/required}}{{#required}}{{#isNullable}}.Value{{/isNullable}}{{/required}}.ToString({{name}}Format)); - {{/lambda.copy}} + {{/lambda.copyText}} {{#lambda.indent3}} {{>WriteProperty}} {{/lambda.indent3}} {{/isDateTime}} {{#isEnum}} {{#isNumeric}} - {{#lambda.copy}} + {{#lambda.copyText}} writer.WriteNumber("{{baseName}}", {{#isInnerEnum}}{{classname}}.{{/isInnerEnum}}{{{datatypeWithEnum}}}ToJsonValue({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{^required}}Option.Value{{#vendorExtensions.x-is-value-type}}{{nrt!}}.Value{{/vendorExtensions.x-is-value-type}}{{/required}}{{#required}}{{#isNullable}}.Value{{/isNullable}}{{/required}})); - {{/lambda.copy}} + {{/lambda.copyText}} {{#lambda.indent3}} {{>WriteProperty}} {{/lambda.indent3}} @@ -519,9 +519,9 @@ {{/isNullable}} {{/isInnerEnum}} {{^isInnerEnum}} - {{#lambda.copy}} + {{#lambda.copyText}} {{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}} - {{/lambda.copy}} + {{/lambda.copyText}} {{#required}} {{#isNullable}} if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}} == null) @@ -533,7 +533,7 @@ {{#enumVars}} {{#-first}} {{#isString}} - if ({{#lambda.pasteLine}}{{/lambda.pasteLine}}RawValue != null){{! we cant use name here because enumVar also has a name property, so use the paste lambda instead }} + if ({{#lambda.paste}}{{/lambda.paste}}RawValue != null){{! we cant use name here because enumVar also has a name property, so use the paste lambda instead }} writer.WriteString("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{nameInPascalCase}}{{/lambda.camelcase_sanitize_param}}RawValue); else writer.WriteNull("{{baseName}}"); @@ -552,10 +552,10 @@ {{#enumVars}} {{#-first}} {{^isNumeric}} - writer.WriteString("{{baseName}}", {{#lambda.pasteLine}}{{/lambda.pasteLine}}RawValue); + writer.WriteString("{{baseName}}", {{#lambda.paste}}{{/lambda.paste}}RawValue); {{/isNumeric}} {{#isNumeric}} - writer.WriteNumber("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{#lambda.pasteLine}}{{/lambda.pasteLine}}{{/lambda.camelcase_sanitize_param}}RawValue); + writer.WriteNumber("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{#lambda.paste}}{{/lambda.paste}}{{/lambda.camelcase_sanitize_param}}RawValue); {{/isNumeric}} {{/-first}} {{/enumVars}} @@ -568,16 +568,16 @@ {{#isNullable}} if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}Option{{nrt!}}.Value != null) { - var {{#lambda.pasteLine}}{{/lambda.pasteLine}}RawValue = {{{datatypeWithEnum}}}ValueConverter.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}Option.Value{{nrt!}}.Value); - writer.{{#lambda.first}}{{#allowableValues}}{{#enumVars}}{{^isNumeric}}WriteString {{/isNumeric}}{{#isNumeric}}WriteNumber {{/isNumeric}}{{/enumVars}}{{/allowableValues}}{{/lambda.first}}("{{baseName}}", {{#lambda.pasteLine}}{{/lambda.pasteLine}}RawValue); + var {{#lambda.paste}}{{/lambda.paste}}RawValue = {{{datatypeWithEnum}}}ValueConverter.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}Option.Value{{nrt!}}.Value); + writer.{{#lambda.first}}{{#allowableValues}}{{#enumVars}}{{^isNumeric}}WriteString {{/isNumeric}}{{#isNumeric}}WriteNumber {{/isNumeric}}{{/enumVars}}{{/allowableValues}}{{/lambda.first}}("{{baseName}}", {{#lambda.paste}}{{/lambda.paste}}RawValue); } else writer.WriteNull("{{baseName}}"); {{/isNullable}} {{^isNullable}} { - var {{#lambda.pasteLine}}{{/lambda.pasteLine}}RawValue = {{{datatypeWithEnum}}}ValueConverter.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{nrt!}}.Value); - writer.{{#lambda.first}}{{#allowableValues}}{{#enumVars}}{{^isNumeric}}WriteString {{/isNumeric}}{{#isNumeric}}WriteNumber {{/isNumeric}}{{/enumVars}}{{/allowableValues}}{{/lambda.first}}("{{baseName}}", {{#lambda.pasteLine}}{{/lambda.pasteLine}}RawValue); + var {{#lambda.paste}}{{/lambda.paste}}RawValue = {{{datatypeWithEnum}}}ValueConverter.ToJsonValue({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{nrt!}}.Value); + writer.{{#lambda.first}}{{#allowableValues}}{{#enumVars}}{{^isNumeric}}WriteString {{/isNumeric}}{{#isNumeric}}WriteNumber {{/isNumeric}}{{/enumVars}}{{/allowableValues}}{{/lambda.first}}("{{baseName}}", {{#lambda.paste}}{{/lambda.paste}}RawValue); } {{/isNullable}} {{/required}} @@ -586,9 +586,9 @@ {{/isMap}} {{/isEnum}} {{#isUuid}} - {{#lambda.copy}} + {{#lambda.copyText}} writer.WriteString("{{baseName}}", {{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{^required}}Option.Value{{#vendorExtensions.x-is-value-type}}{{nrt!}}.Value{{/vendorExtensions.x-is-value-type}}{{/required}}{{#required}}{{#isNullable}}.Value{{/isNullable}}{{/required}}); - {{/lambda.copy}} + {{/lambda.copyText}} {{#lambda.indent3}} {{>WriteProperty}} {{/lambda.indent3}} diff --git a/sdks/dotnet/templates/libraries/generichost/OnErrorDefaultImplementation.mustache b/sdks/dotnet/templates/libraries/generichost/OnErrorDefaultImplementation.mustache index 7af8e0760..a1a9fa976 100644 --- a/sdks/dotnet/templates/libraries/generichost/OnErrorDefaultImplementation.mustache +++ b/sdks/dotnet/templates/libraries/generichost/OnErrorDefaultImplementation.mustache @@ -1,2 +1,2 @@ - if (!suppressDefaultLog) - Logger.LogError(exception, "An error occurred while sending the request to the server."); \ No newline at end of file + if (!suppressDefaultLogLocalVar) + Logger.LogError(exceptionLocalVar, "An error occurred while sending the request to the server."); \ No newline at end of file diff --git a/sdks/dotnet/templates/libraries/generichost/RateLimitProvider`1.mustache b/sdks/dotnet/templates/libraries/generichost/RateLimitProvider`1.mustache index 857a505ab..bafe525c0 100644 --- a/sdks/dotnet/templates/libraries/generichost/RateLimitProvider`1.mustache +++ b/sdks/dotnet/templates/libraries/generichost/RateLimitProvider`1.mustache @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Channels; namespace {{packageName}}.{{clientPackage}} { @@ -17,7 +16,7 @@ namespace {{packageName}}.{{clientPackage}} /// {{>visibility}} class RateLimitProvider : TokenProvider where TTokenBase : TokenBase { - internal Dictionary> AvailableTokens { get; } = new{{^net70OrLater}} Dictionary>{{/net70OrLater}}(); + internal Dictionary> AvailableTokens { get; } = new{{^net70OrLater}} Dictionary>{{/net70OrLater}}(); /// /// Instantiates a ThrottledTokenProvider. Your tokens will be rate limited based on the token's timeout. @@ -29,12 +28,12 @@ namespace {{packageName}}.{{clientPackage}} token.StartTimer(token.Timeout ?? TimeSpan.FromMilliseconds(40)); {{#lambda.copy}} - BoundedChannelOptions options = new BoundedChannelOptions(_tokens.Length) + global::System.Threading.Channels.BoundedChannelOptions options = new global::System.Threading.Channels.BoundedChannelOptions(_tokens.Length) { - FullMode = BoundedChannelFullMode.DropWrite + FullMode = global::System.Threading.Channels.BoundedChannelFullMode.DropWrite }; - AvailableTokens.Add(string.Empty, Channel.CreateBounded(options)); + AvailableTokens.Add(string.Empty, global::System.Threading.Channels.Channel.CreateBounded(options)); {{/lambda.copy}} {{#hasApiKeyMethods}} if (container is TokenContainer apiKeyTokenContainer) @@ -43,31 +42,35 @@ namespace {{packageName}}.{{clientPackage}} foreach (string header in headers) { - BoundedChannelOptions options = new BoundedChannelOptions(apiKeyTokenContainer.Tokens.Count(t => ClientUtils.ApiKeyHeaderToString(t.Header).Equals(header))) + global::System.Threading.Channels.BoundedChannelOptions options = new global::System.Threading.Channels.BoundedChannelOptions(apiKeyTokenContainer.Tokens.Count(t => ClientUtils.ApiKeyHeaderToString(t.Header).Equals(header))) { - FullMode = BoundedChannelFullMode.DropWrite + FullMode = global::System.Threading.Channels.BoundedChannelFullMode.DropWrite }; - AvailableTokens.Add(header, Channel.CreateBounded(options)); + AvailableTokens.Add(header, global::System.Threading.Channels.Channel.CreateBounded(options)); } } else { - {{#lambda.indent1}}{{#lambda.pasteLine}}{{/lambda.pasteLine}}{{/lambda.indent1}} + {{#lambda.indentAll1}} + {{#lambda.paste}} + {{/lambda.paste}} + {{/lambda.indentAll1}} } {{/hasApiKeyMethods}} {{^hasApiKeyMethods}} - {{#lambda.pasteLine}}{{/lambda.pasteLine}} + {{#lambda.paste}} + {{/lambda.paste}} {{/hasApiKeyMethods}} - foreach(Channel tokens in AvailableTokens.Values) + foreach(global::System.Threading.Channels.Channel tokens in AvailableTokens.Values) for (int i = 0; i < _tokens.Length; i++) _tokens[i].TokenBecameAvailable += ((sender) => tokens.Writer.TryWrite((TTokenBase) sender)); } internal override async System.Threading.Tasks.ValueTask GetAsync(string header = "", System.Threading.CancellationToken cancellation = default{{^netstandard20OrLater}}(global::System.Threading.CancellationToken){{/netstandard20OrLater}}) { - if (!AvailableTokens.TryGetValue(header, out Channel{{nrt?}} tokens)) + if (!AvailableTokens.TryGetValue(header, out global::System.Threading.Channels.Channel{{nrt?}} tokens)) throw new KeyNotFoundException($"Could not locate a token for header '{header}'."); return await tokens.Reader.ReadAsync(cancellation).ConfigureAwait(false); diff --git a/sdks/dotnet/templates/libraries/generichost/ValidateRegex.mustache b/sdks/dotnet/templates/libraries/generichost/ValidateRegex.mustache index 78aba8fb3..8918b8cf4 100644 --- a/sdks/dotnet/templates/libraries/generichost/ValidateRegex.mustache +++ b/sdks/dotnet/templates/libraries/generichost/ValidateRegex.mustache @@ -1,7 +1,7 @@ // {{{name}}} ({{{dataType}}}) pattern Regex regex{{{name}}} = new Regex(@"{{{vendorExtensions.x-regex}}}"{{#vendorExtensions.x-modifiers}}{{#-first}}, {{/-first}}RegexOptions.{{{.}}}{{^-last}} | {{/-last}}{{/vendorExtensions.x-modifiers}}); {{#lambda.copy}}this.{{{name}}}{{#useGenericHost}}{{^required}}Option.Value{{/required}}{{/useGenericHost}} != null && {{/lambda.copy}} -if ({{#lambda.first}}{{^required}}{{#lambda.pasteLine}}{{/lambda.pasteLine}} {{/required}}{{#isNullable}}{{#lambda.pasteLine}}{{/lambda.pasteLine}}{{/isNullable}}{{/lambda.first}}!regex{{{name}}}.Match(this.{{{name}}}{{#useGenericHost}}{{^required}}Option.Value{{/required}}{{/useGenericHost}}{{#isUuid}}.ToString(){{#lambda.first}}{{^required}}{{nrt!}} {{/required}}{{#isNullable}}! {{/isNullable}}{{/lambda.first}}{{/isUuid}}).Success) +if ({{#lambda.first}}{{^required}}{{#lambda.paste}}{{/lambda.paste}} {{/required}}{{#isNullable}}{{#lambda.paste}}{{/lambda.paste}}{{/isNullable}}{{/lambda.first}}!regex{{{name}}}.Match(this.{{{name}}}{{#useGenericHost}}{{^required}}Option.Value{{/required}}{{/useGenericHost}}{{#isUuid}}.ToString(){{#lambda.first}}{{^required}}{{nrt!}} {{/required}}{{#isNullable}}! {{/isNullable}}{{/lambda.first}}{{/isUuid}}).Success) { yield return new System.ComponentModel.DataAnnotations.ValidationResult("Invalid value for {{{name}}}, must match a pattern of " + regex{{{name}}}, new [] { "{{{name}}}" }); } \ No newline at end of file diff --git a/sdks/dotnet/templates/libraries/generichost/WritePropertyHelper.mustache b/sdks/dotnet/templates/libraries/generichost/WritePropertyHelper.mustache index 183946cf7..616993bce 100644 --- a/sdks/dotnet/templates/libraries/generichost/WritePropertyHelper.mustache +++ b/sdks/dotnet/templates/libraries/generichost/WritePropertyHelper.mustache @@ -1,9 +1,10 @@ {{#isNullable}} if ({{#lambda.camelcase_sanitize_param}}{{classname}}{{/lambda.camelcase_sanitize_param}}.{{name}}{{^required}}Option.Value{{/required}} != null) - {{#lambda.pasteLine}}{{/lambda.pasteLine}} + {{#lambda.paste}}{{/lambda.paste}} else writer.WriteNull("{{baseName}}"); {{/isNullable}} {{^isNullable}} -{{#lambda.pasteLine}}{{/lambda.pasteLine}} +{{#lambda.paste}} +{{/lambda.paste}} {{/isNullable}} \ No newline at end of file diff --git a/sdks/dotnet/templates/libraries/generichost/api.mustache b/sdks/dotnet/templates/libraries/generichost/api.mustache index 8c02da8f1..b33115f0a 100644 --- a/sdks/dotnet/templates/libraries/generichost/api.mustache +++ b/sdks/dotnet/templates/libraries/generichost/api.mustache @@ -85,6 +85,7 @@ namespace {{packageName}}.{{apiPackage}} {{/operation}} } {{#operation}} + {{^vendorExtensions.x-duplicates}} {{#responses}} {{#-first}} @@ -115,6 +116,7 @@ namespace {{packageName}}.{{apiPackage}} } {{/-first}} {{/responses}} + {{/vendorExtensions.x-duplicates}} {{/operation}} /// @@ -134,7 +136,7 @@ namespace {{packageName}}.{{apiPackage}} /// public event EventHandler{{nrt?}} OnError{{operationId}}; - internal void ExecuteOn{{operationId}}({{classname}}.{{operationId}}ApiResponse apiResponse) + internal void ExecuteOn{{operationId}}({{#vendorExtensions.x-duplicates}}{{.}}{{/vendorExtensions.x-duplicates}}{{^vendorExtensions.x-duplicates}}{{classname}}{{/vendorExtensions.x-duplicates}}.{{operationId}}ApiResponse apiResponse) { On{{operationId}}?.Invoke(this, new ApiResponseEventArgs(apiResponse)); } @@ -303,30 +305,30 @@ namespace {{packageName}}.{{apiPackage}} /// /// Logs exceptions that occur while retrieving the server response /// - /// - /// - /// + /// + /// + /// {{#allParams}} /// {{/allParams}} - private void OnError{{operationId}}DefaultImplementation({{#lambda.joinWithComma}}Exception exception string pathFormat string path {{#allParams}}{{^required}}Option<{{/required}}{{{dataType}}}{{>NullConditionalParameter}}{{^required}}>{{/required}} {{paramName}} {{/allParams}}{{/lambda.joinWithComma}}) + private void OnError{{operationId}}DefaultImplementation({{#lambda.joinWithComma}}Exception exceptionLocalVar string pathFormatLocalVar string pathLocalVar {{#allParams}}{{^required}}Option<{{/required}}{{{dataType}}}{{>NullConditionalParameter}}{{^required}}>{{/required}} {{paramName}} {{/allParams}}{{/lambda.joinWithComma}}) { - bool suppressDefaultLog = false; - OnError{{operationId}}({{#lambda.joinWithComma}}ref suppressDefaultLog exception pathFormat path {{#allParams}}{{paramName}} {{/allParams}}{{/lambda.joinWithComma}}); + bool suppressDefaultLogLocalVar = false; + OnError{{operationId}}({{#lambda.joinWithComma}}ref suppressDefaultLogLocalVar exceptionLocalVar pathFormatLocalVar pathLocalVar {{#allParams}}{{paramName}} {{/allParams}}{{/lambda.joinWithComma}}); {{>OnErrorDefaultImplementation}} } /// /// A partial method that gives developers a way to provide customized exception handling /// - /// - /// - /// - /// + /// + /// + /// + /// {{#allParams}} /// {{/allParams}} - partial void OnError{{operationId}}({{#lambda.joinWithComma}}ref bool suppressDefaultLog Exception exception string pathFormat string path {{#allParams}}{{^required}}Option<{{/required}}{{{dataType}}}{{>NullConditionalParameter}}{{^required}}>{{/required}} {{paramName}} {{/allParams}}{{/lambda.joinWithComma}}); + partial void OnError{{operationId}}({{#lambda.joinWithComma}}ref bool suppressDefaultLogLocalVar Exception exceptionLocalVar string pathFormatLocalVar string pathLocalVar {{#allParams}}{{^required}}Option<{{/required}}{{{dataType}}}{{>NullConditionalParameter}}{{^required}}>{{/required}} {{paramName}} {{/allParams}}{{/lambda.joinWithComma}}); /// /// {{summary}} {{notes}} @@ -380,7 +382,7 @@ namespace {{packageName}}.{{apiPackage}} uriBuilderLocalVar.Host = HttpClient.BaseAddress{{nrt!}}.Host; uriBuilderLocalVar.Port = HttpClient.BaseAddress.Port; uriBuilderLocalVar.Scheme = HttpClient.BaseAddress.Scheme; - uriBuilderLocalVar.Path = ClientUtils.CONTEXT_PATH + "{{path}}"; + uriBuilderLocalVar.Path = ClientUtils.CONTEXT_PATH + "{{{path}}}"; {{/servers}} {{#servers}} {{#-first}} @@ -619,9 +621,9 @@ namespace {{packageName}}.{{apiPackage}} { string responseContentLocalVar = await httpResponseMessageLocalVar.Content.ReadAsStringAsync({{#net60OrLater}}cancellationToken{{/net60OrLater}}).ConfigureAwait(false); - ILogger<{{operationId}}ApiResponse> apiResponseLoggerLocalVar = LoggerFactory.CreateLogger<{{operationId}}ApiResponse>(); + ILogger<{{#vendorExtensions.x-duplicates}}{{.}}.{{/vendorExtensions.x-duplicates}}{{operationId}}ApiResponse> apiResponseLoggerLocalVar = LoggerFactory.CreateLogger<{{#vendorExtensions.x-duplicates}}{{.}}.{{/vendorExtensions.x-duplicates}}{{operationId}}ApiResponse>(); - {{operationId}}ApiResponse apiResponseLocalVar = new{{^net60OrLater}} {{operationId}}ApiResponse{{/net60OrLater}}(apiResponseLoggerLocalVar, httpRequestMessageLocalVar, httpResponseMessageLocalVar, responseContentLocalVar, "{{path}}", requestedAtLocalVar, _jsonSerializerOptions); + {{#vendorExtensions.x-duplicates}}{{.}}.{{/vendorExtensions.x-duplicates}}{{operationId}}ApiResponse apiResponseLocalVar = new{{^net60OrLater}} {{operationId}}ApiResponse{{/net60OrLater}}(apiResponseLoggerLocalVar, httpRequestMessageLocalVar, httpResponseMessageLocalVar, responseContentLocalVar, "{{{path}}}", requestedAtLocalVar, _jsonSerializerOptions); After{{operationId}}DefaultImplementation({{#lambda.joinWithComma}}apiResponseLocalVar {{#allParams}}{{paramName}} {{/allParams}}{{/lambda.joinWithComma}}); @@ -674,12 +676,13 @@ namespace {{packageName}}.{{apiPackage}} } catch(Exception e) { - OnError{{operationId}}DefaultImplementation({{#lambda.joinWithComma}}e "{{path}}" uriBuilderLocalVar.Path {{#allParams}}{{paramName}} {{/allParams}}{{/lambda.joinWithComma}}); + OnError{{operationId}}DefaultImplementation({{#lambda.joinWithComma}}e "{{{path}}}" uriBuilderLocalVar.Path {{#allParams}}{{paramName}} {{/allParams}}{{/lambda.joinWithComma}}); Events.ExecuteOnError{{operationId}}(e); throw; } {{/lambda.trimLineBreaks}} } + {{^vendorExtensions.x-duplicates}} {{#responses}} {{#-first}} @@ -792,6 +795,7 @@ namespace {{packageName}}.{{apiPackage}} } {{/-first}} {{/responses}} + {{/vendorExtensions.x-duplicates}} {{/operation}} } {{/operations}} diff --git a/sdks/dotnet/templates/libraries/generichost/modelGeneric.mustache b/sdks/dotnet/templates/libraries/generichost/modelGeneric.mustache index 6bf210bbc..04fb37138 100644 --- a/sdks/dotnet/templates/libraries/generichost/modelGeneric.mustache +++ b/sdks/dotnet/templates/libraries/generichost/modelGeneric.mustache @@ -34,6 +34,13 @@ {{/isNew}} {{/isInherited}} {{/isDiscriminator}} + {{#vendorExtensions.x-is-base-or-new-discriminator}} + {{^model.composedSchemas.anyOf}} + {{^model.composedSchemas.oneOf}} + {{name}} = {{^isEnum}}this.GetType().Name{{/isEnum}}{{#isEnum}}({{datatypeWithEnum}})Enum.Parse(typeof({{datatypeWithEnum}}), this.GetType().Name){{/isEnum}}; + {{/model.composedSchemas.oneOf}} + {{/model.composedSchemas.anyOf}} + {{/vendorExtensions.x-is-base-or-new-discriminator}} {{/allVars}} OnCreated(); } @@ -71,6 +78,13 @@ {{/isNew}} {{/isInherited}} {{/isDiscriminator}} + {{#vendorExtensions.x-is-base-or-new-discriminator}} + {{^model.composedSchemas.anyOf}} + {{^model.composedSchemas.oneOf}} + {{name}} = {{^isEnum}}this.GetType().Name{{/isEnum}}{{#isEnum}}({{datatypeWithEnum}})Enum.Parse(typeof({{datatypeWithEnum}}), this.GetType().Name){{/isEnum}}; + {{/model.composedSchemas.oneOf}} + {{/model.composedSchemas.anyOf}} + {{/vendorExtensions.x-is-base-or-new-discriminator}} {{/allVars}} OnCreated(); } @@ -109,7 +123,7 @@ /// {{.}} {{/description}} {{#example}} - /// {{.}} + /* {{.}} */ {{/example}} [JsonPropertyName("{{baseName}}")] {{#deprecated}} @@ -136,7 +150,7 @@ /// {{#description}} /// {{.}}{{/description}} {{#example}} - /// {{.}} + /* {{.}} */ {{/example}} {{#deprecated}} [Obsolete] @@ -152,7 +166,7 @@ /// {{#description}} /// {{.}}{{/description}} {{#example}} - /// {{.}} + /* {{.}} */ {{/example}} {{#deprecated}} [Obsolete] @@ -162,7 +176,7 @@ {{/vendorExtensions.x-duplicated-data-type}} {{/composedSchemas.oneOf}} {{#allVars}} - {{#isDiscriminator}} + {{#vendorExtensions.x-is-base-or-new-discriminator}} {{^model.composedSchemas.anyOf}} {{^model.composedSchemas.oneOf}} /// @@ -170,12 +184,12 @@ /// [JsonIgnore] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - public {{#isNew}}new {{/isNew}}{{datatypeWithEnum}} {{name}} { get; } = {{^isNew}}"{{classname}}"{{/isNew}}{{#isNew}}{{^isEnum}}"{{classname}}"{{/isEnum}}{{#isEnum}}({{datatypeWithEnum}})Enum.Parse(typeof({{datatypeWithEnum}}), "{{classname}}"){{/isEnum}}{{/isNew}}; + public {{#isNew}}new {{/isNew}}{{datatypeWithEnum}} {{name}} { get; } {{/model.composedSchemas.oneOf}} {{/model.composedSchemas.anyOf}} - {{/isDiscriminator}} - {{^isDiscriminator}} + {{/vendorExtensions.x-is-base-or-new-discriminator}} + {{^vendorExtensions.x-is-base-or-new-discriminator}} {{^isEnum}} {{#isInherited}} {{#isNew}} @@ -193,7 +207,7 @@ /// {{#description}} /// {{.}}{{/description}} {{#example}} - /// {{.}} + /* {{.}} */ {{/example}} [JsonPropertyName("{{baseName}}")] {{#deprecated}} @@ -218,7 +232,7 @@ /// {{#description}} /// {{.}}{{/description}} {{#example}} - /// {{.}} + /* {{.}} */ {{/example}} [JsonPropertyName("{{baseName}}")] {{#deprecated}} @@ -228,7 +242,7 @@ {{/isInherited}} {{/isEnum}} - {{/isDiscriminator}} + {{/vendorExtensions.x-is-base-or-new-discriminator}} {{/allVars}} {{#isAdditionalPropertiesTrue}} {{^parentModel}} @@ -345,15 +359,17 @@ {{/readOnlyVars}} {{#readOnlyVars}} {{#lambda.copy}} - if ({{name}} != null) hashCode = (hashCode * 59) + {{name}}.GetHashCode(); + {{/lambda.copy}} {{#isNullable}} - {{#lambda.pasteOnce}}{{/lambda.pasteOnce}} + {{#lambda.pasteOnce}} + {{/lambda.pasteOnce}} {{/isNullable}} {{^required}} - {{#lambda.pasteOnce}}{{/lambda.pasteOnce}} + {{#lambda.pasteOnce}} + {{/lambda.pasteOnce}} {{/required}} {{/readOnlyVars}} {{#isAdditionalPropertiesTrue}} diff --git a/sdks/dotnet/templates/libraries/httpclient/ApiClient.mustache b/sdks/dotnet/templates/libraries/httpclient/ApiClient.mustache index cefe6be9f..603284acb 100644 --- a/sdks/dotnet/templates/libraries/httpclient/ApiClient.mustache +++ b/sdks/dotnet/templates/libraries/httpclient/ApiClient.mustache @@ -481,7 +481,7 @@ namespace {{packageName}}.Client try { - if (configuration.Timeout > 0) + if (configuration.Timeout > TimeSpan.Zero) { timeoutTokenSource = new CancellationTokenSource(configuration.Timeout); finalTokenSource = CancellationTokenSource.CreateLinkedTokenSource(finalToken, timeoutTokenSource.Token); diff --git a/sdks/dotnet/templates/modelGeneric.mustache b/sdks/dotnet/templates/modelGeneric.mustache index 7392b272f..5075f0e64 100644 --- a/sdks/dotnet/templates/modelGeneric.mustache +++ b/sdks/dotnet/templates/modelGeneric.mustache @@ -10,7 +10,9 @@ [DataContract(Name = "{{{name}}}")] {{^useUnityWebRequest}} {{#discriminator}} + {{#mappedModels.size}} [JsonConverter(typeof(JsonSubtypes), "{{{discriminatorName}}}")] + {{/mappedModels.size}} {{#mappedModels}} [JsonSubtypes.KnownSubType(typeof({{{modelName}}}), "{{^vendorExtensions.x-discriminator-value}}{{{mappingName}}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{.}}}{{/vendorExtensions.x-discriminator-value}}")] {{/mappedModels}} @@ -46,7 +48,14 @@ /// {{.}} {{/description}} {{#example}} +{{^useCustomTemplateCode}} + /* + {{.}} + */ +{{/useCustomTemplateCode}} +{{#useCustomTemplateCode}} /// {{.}} +{{/useCustomTemplateCode}} {{/example}} {{^conditionalSerialization}} [DataMember(Name = "{{baseName}}"{{#required}}, IsRequired = true{{/required}}, EmitDefaultValue = {{#vendorExtensions.x-emit-default-value}}true{{/vendorExtensions.x-emit-default-value}}{{^vendorExtensions.x-emit-default-value}}{{#required}}true{{/required}}{{^required}}{{#isBoolean}}true{{/isBoolean}}{{^isBoolean}}{{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/isBoolean}}{{/required}}{{/vendorExtensions.x-emit-default-value}})] @@ -290,7 +299,14 @@ /// {{#description}} /// {{.}}{{/description}} {{#example}} +{{^useCustomTemplateCode}} + /* + {{.}} + */ +{{/useCustomTemplateCode}} +{{#useCustomTemplateCode}} /// {{.}} +{{/useCustomTemplateCode}} {{/example}} {{^conditionalSerialization}} [DataMember(Name = "{{baseName}}"{{#required}}, IsRequired = true{{/required}}, EmitDefaultValue = {{#vendorExtensions.x-emit-default-value}}true{{/vendorExtensions.x-emit-default-value}}{{^vendorExtensions.x-emit-default-value}}{{#required}}true{{/required}}{{^required}}{{#isBoolean}}true{{/isBoolean}}{{^isBoolean}}{{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/isBoolean}}{{/required}}{{/vendorExtensions.x-emit-default-value}})] diff --git a/sdks/dotnet/templates/netcore_project.mustache b/sdks/dotnet/templates/netcore_project.mustache index 01717e65a..06243449e 100644 --- a/sdks/dotnet/templates/netcore_project.mustache +++ b/sdks/dotnet/templates/netcore_project.mustache @@ -38,21 +38,16 @@ {{/useGenericHost}} {{#useRestSharp}} -{{^useCustomTemplateCode}} - -{{/useCustomTemplateCode}} -{{#useCustomTemplateCode}} -{{/useCustomTemplateCode}} {{/useRestSharp}} {{#useGenericHost}} - - + + {{#supportsRetry}} - + {{/supportsRetry}} {{#net80OrLater}} - + {{/net80OrLater}} {{^net60OrLater}} diff --git a/sdks/dotnet/templates/netcore_testproject.mustache b/sdks/dotnet/templates/netcore_testproject.mustache index 90d11eb82..90434b77e 100644 --- a/sdks/dotnet/templates/netcore_testproject.mustache +++ b/sdks/dotnet/templates/netcore_testproject.mustache @@ -9,9 +9,9 @@ - - - + + + diff --git a/sdks/dotnet/templates/nuspec.mustache b/sdks/dotnet/templates/nuspec.mustache index b473cbd64..680ee2fe4 100644 --- a/sdks/dotnet/templates/nuspec.mustache +++ b/sdks/dotnet/templates/nuspec.mustache @@ -32,12 +32,7 @@ {{#useRestSharp}} -{{^useCustomTemplateCode}} - -{{/useCustomTemplateCode}} -{{#useCustomTemplateCode}} -{{/useCustomTemplateCode}} {{/useRestSharp}} {{#useCompareNetObjects}}