Repository: ory/sdk (language: python)

Issue:
Dotnet generator is broken
**Describe the bug**

The compiled package is not found for kratos


Discussion / hints:
https://app.circleci.com/pipelines/github/ory/sdk/509/workflows/1af3d872-efb5-48bb-ada6-a8dca9ccdf79/jobs/483

Fix patch:
diff --git a/openapi/templates/csharp-netcore/AbstractOpenAPISchema.mustache b/openapi/templates/csharp-netcore/AbstractOpenAPISchema.mustache
new file mode 100644
index 00000000000..05e78204959
--- /dev/null
+++ b/openapi/templates/csharp-netcore/AbstractOpenAPISchema.mustache
@@ -0,0 +1,68 @@
+{{>partial_header}}
+
+using System;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace {{packageName}}.{{modelPackage}}
+{
+    /// <summary>
+    ///  Abstract base class for oneOf, anyOf schemas in the OpenAPI specification
+    /// </summary>
+    {{>visibility}} abstract partial class AbstractOpenAPISchema
+    {
+        /// <summary>
+        ///  Custom JSON serializer
+        /// </summary>
+        static public readonly JsonSerializerSettings SerializerSettings = new JsonSerializerSettings
+        {
+            // OpenAPI generated types generally hide default constructors.
+            ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
+            MissingMemberHandling = MissingMemberHandling.Error,
+            ContractResolver = new DefaultContractResolver
+            {
+                NamingStrategy = new CamelCaseNamingStrategy
+                {
+                    OverrideSpecifiedNames = false
+                }
+            }
+        };
+
+        /// <summary>
+        ///  Custom JSON serializer for objects with additional properties
+        /// </summary>
+        static public readonly JsonSerializerSettings AdditionalPropertiesSerializerSettings = new JsonSerializerSettings
+        {
+            // OpenAPI generated types generally hide default constructors.
+            ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
+            MissingMemberHandling = MissingMemberHandling.Ignore,
+            ContractResolver = new DefaultContractResolver
+            {
+                NamingStrategy = new CamelCaseNamingStrategy
+                {
+                    OverrideSpecifiedNames = false
+                }
+            }
+        };
+
+        /// <summary>
+        /// Gets or Sets the actual instance
+        /// </summary>
+        public abstract Object ActualInstance { get; set; }
+
+        /// <summary>
+        /// Gets or Sets IsNullable to indicate whether the instance is nullable
+        /// </summary>
+        public bool IsNullable { get; protected set; }
+
+        /// <summary>
+        /// Gets or Sets the schema type, which can be either `oneOf` or `anyOf`
+        /// </summary>
+        public string SchemaType { get; protected set; }
+
+        /// <summary>
+        /// Converts the instance into JSON string.
+        /// </summary>
+        public abstract string ToJson();
+    }
+}
diff --git a/openapi/templates/csharp-netcore/ApiClient.mustache b/openapi/templates/csharp-netcore/ApiClient.mustache
new file mode 100644
index 00000000000..d533d30f901
--- /dev/null
+++ b/openapi/templates/csharp-netcore/ApiClient.mustache
@@ -0,0 +1,874 @@
+{{>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;
+{{^netStandard}}
+using System.Web;
+{{/netStandard}}
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
+using RestSharp;
+using RestSharp.Deserializers;
+using RestSharpMethod = RestSharp.Method;
+{{#useWebRequest}}
+using System.Net.Http;
+{{/useWebRequest}}
+{{#supportsRetry}}
+using Polly;
+{{/supportsRetry}}
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// Allows RestSharp to Serialize/Deserialize JSON using our custom logic, but only when ContentType is JSON.
+    /// </summary>
+    internal class CustomJsonCodec : RestSharp.Serializers.ISerializer, RestSharp.Deserializers.IDeserializer
+    {
+        private readonly IReadableConfiguration _configuration;
+        private static readonly string _contentType = "application/json";
+        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;
+        }
+
+        /// <summary>
+        /// Serialize the object into a JSON string.
+        /// </summary>
+        /// <param name="obj">Object to be serialized.</param>
+        /// <returns>A JSON string.</returns>
+        public string Serialize(object obj)
+        {
+            if (obj != null && obj is {{{packageName}}}.{{modelPackage}}.AbstractOpenAPISchema)
+            {
+                // the object to be serialized is an oneOf/anyOf schema
+                return (({{{packageName}}}.{{modelPackage}}.AbstractOpenAPISchema)obj).ToJson();
+            }
+            else
+            {
+                return JsonConvert.SerializeObject(obj, _serializerSettings);
+            }
+        }
+
+        public T Deserialize<T>(IRestResponse response)
+        {
+            var result = (T)Deserialize(response, typeof(T));
+            return result;
+        }
+
+        /// <summary>
+        /// Deserialize the JSON string into a proper object.
+        /// </summary>
+        /// <param name="response">The HTTP response.</param>
+        /// <param name="type">Object type.</param>
+        /// <returns>Object representation of the JSON string.</returns>
+        internal object Deserialize(IRestResponse 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)
+                        ? 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("'", ""));
+                            File.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, System.Globalization.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 string RootElement { get; set; }
+        public string Namespace { get; set; }
+        public string DateFormat { get; set; }
+
+        public string ContentType
+        {
+            get { return _contentType; }
+            set { throw new InvalidOperationException("Not allowed to set content type."); }
+        }
+    }
+    {{! NOTE: Any changes related to RestSharp should be done in this class. All other client classes are for extensibility by consumers.}}
+    /// <summary>
+    /// Provides a default implementation of an Api client (both synchronous and asynchronous implementations),
+    /// encapsulating general REST accessor use cases.
+    /// </summary>
+    {{>visibility}} partial class ApiClient : ISynchronousClient{{#supportsAsync}}, IAsynchronousClient{{/supportsAsync}}
+    {
+        private readonly string _baseUrl;
+
+        /// <summary>
+        /// Specifies the settings on a <see cref="JsonSerializer" /> object.
+        /// These settings can be adjusted to accommodate custom serialization rules.
+        /// </summary>
+        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
+                }
+            }
+        };
+
+        /// <summary>
+        /// Allows for extending request processing for <see cref="ApiClient"/> generated code.
+        /// </summary>
+        /// <param name="request">The RestSharp request object</param>
+        partial void InterceptRequest(IRestRequest request);
+
+        /// <summary>
+        /// Allows for extending response processing for <see cref="ApiClient"/> generated code.
+        /// </summary>
+        /// <param name="request">The RestSharp request object</param>
+        /// <param name="response">The RestSharp response object</param>
+        partial void InterceptResponse(IRestRequest request, IRestResponse response);
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ApiClient" />, defaulting to the global configurations' base url.
+        /// </summary>
+        public ApiClient()
+        {
+            _baseUrl = {{packageName}}.Client.GlobalConfiguration.Instance.BasePath;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ApiClient" />
+        /// </summary>
+        /// <param name="basePath">The target service's base path in URL format.</param>
+        /// <exception cref="ArgumentException"></exception>
+        public ApiClient(string basePath)
+        {
+            if (string.IsNullOrEmpty(basePath))
+                throw new ArgumentException("basePath cannot be empty");
+
+            _baseUrl = basePath;
+        }
+
+        /// <summary>
+        /// Constructs the RestSharp version of an http method
+        /// </summary>
+        /// <param name="method">Swagger Client Custom HttpMethod</param>
+        /// <returns>RestSharp's HttpMethod instance.</returns>
+        /// <exception cref="ArgumentOutOfRangeException"></exception>
+        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;
+        }
+
+        /// <summary>
+        /// Provides all logic for constructing a new RestSharp <see cref="RestRequest"/>.
+        /// At this point, all information for querying the service is known. Here, it is simply
+        /// mapped into the RestSharp request.
+        /// </summary>
+        /// <param name="method">The http verb.</param>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <returns>[private] A new RestRequest instance.</returns>
+        /// <exception cref="ArgumentNullException"></exception>
+        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(Method(method))
+            {
+                Resource = path,
+                JsonSerializer = new CustomJsonCodec(SerializerSettings, configuration)
+            };
+
+            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)
+                {
+                    var bytes = ClientUtils.ReadAsBytes(fileParam.Value);
+                    var fileStream = fileParam.Value as FileStream;
+                    if (fileStream != null)
+                        request.Files.Add(FileParameter.Create(fileParam.Key, bytes, System.IO.Path.GetFileName(fileStream.Name)));
+                    else
+                        request.Files.Add(FileParameter.Create(fileParam.Key, bytes, "no_file_name_provided"));
+                }
+            }
+
+            if (options.Cookies != null && options.Cookies.Count > 0)
+            {
+                foreach (var cookie in options.Cookies)
+                {
+                    request.AddCookie(cookie.Name, cookie.Value);
+                }
+            }
+
+            return request;
+        }
+
+        private ApiResponse<T> ToApiResponse<T>(IRestResponse<T> response)
+        {
+            T result = response.Data;
+            string rawContent = response.Content;
+
+            var transformed = new ApiResponse<T>(response.StatusCode, new Multimap<string, string>({{#caseInsensitiveResponseHeaders}}StringComparer.OrdinalIgnoreCase{{/caseInsensitiveResponseHeaders}}), result, rawContent)
+            {
+                ErrorText = response.ErrorMessage,
+                Cookies = new List<Cookie>()
+            };
+
+            if (response.Headers != null)
+            {
+                foreach (var responseHeader in response.Headers)
+                {
+                    transformed.Headers.Add(responseHeader.Name, ClientUtils.ParameterToString(responseHeader.Value));
+                }
+            }
+
+            if (response.Cookies != null)
+            {
+                foreach (var responseCookies in response.Cookies)
+                {
+                    transformed.Cookies.Add(
+                        new Cookie(
+                            responseCookies.Name,
+                            responseCookies.Value,
+                            responseCookies.Path,
+                            responseCookies.Domain)
+                        );
+                }
+            }
+
+            return transformed;
+        }
+
+        private ApiResponse<T> Exec<T>(RestRequest req, IReadableConfiguration configuration)
+        {
+            RestClient client = new RestClient(_baseUrl);
+
+            client.ClearHandlers();
+            var existingDeserializer = req.JsonSerializer as IDeserializer;
+            if (existingDeserializer != null)
+            {
+                client.AddHandler("application/json", () => existingDeserializer);
+                client.AddHandler("text/json", () => existingDeserializer);
+                client.AddHandler("text/x-json", () => existingDeserializer);
+                client.AddHandler("text/javascript", () => existingDeserializer);
+                client.AddHandler("*+json", () => existingDeserializer);
+            }
+            else
+            {
+                var customDeserializer = new CustomJsonCodec(SerializerSettings, configuration);
+                client.AddHandler("application/json", () => customDeserializer);
+                client.AddHandler("text/json", () => customDeserializer);
+                client.AddHandler("text/x-json", () => customDeserializer);
+                client.AddHandler("text/javascript", () => customDeserializer);
+                client.AddHandler("*+json", () => customDeserializer);
+            }
+
+            var xmlDeserializer = new XmlDeserializer();
+            client.AddHandler("application/xml", () => xmlDeserializer);
+            client.AddHandler("text/xml", () => xmlDeserializer);
+            client.AddHandler("*+xml", () => xmlDeserializer);
+            client.AddHandler("*", () => xmlDeserializer);
+
+            client.Timeout = configuration.Timeout;
+
+            if (configuration.Proxy != null)
+            {
+                client.Proxy = configuration.Proxy;
+            }
+
+            if (configuration.UserAgent != null)
+            {
+                client.UserAgent = configuration.UserAgent;
+            }
+
+            if (configuration.ClientCertificates != null)
+            {
+                client.ClientCertificates = configuration.ClientCertificates;
+            }
+
+            InterceptRequest(req);
+
+            IRestResponse<T> response;
+            if (RetryConfiguration.RetryPolicy != null)
+            {
+                var policy = RetryConfiguration.RetryPolicy;
+                var policyResult = policy.ExecuteAndCapture(() => client.Execute(req));
+                response = (policyResult.Outcome == OutcomeType.Successful) ? client.Deserialize<T>(policyResult.Result) : new RestResponse<T>
+                {
+                    Request = req,
+                    ErrorException = policyResult.FinalException
+                };
+            }
+            else
+            {
+                response = client.Execute<T>(req);
+            }
+
+            // if the response type is oneOf/anyOf, call FromJSON to deserialize the data
+            if (typeof({{{packageName}}}.{{modelPackage}}.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);
+            }
+
+            InterceptResponse(req, 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<Cookie>();
+                foreach (var restResponseCookie in response.Cookies)
+                {
+                    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;
+        }
+
+        {{#supportsAsync}}
+        private async Task<ApiResponse<T>> ExecAsync<T>(RestRequest req, IReadableConfiguration configuration, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            RestClient client = new RestClient(_baseUrl);
+
+            client.ClearHandlers();
+            var existingDeserializer = req.JsonSerializer as IDeserializer;
+            if (existingDeserializer != null)
+            {
+                client.AddHandler("application/json", () => existingDeserializer);
+                client.AddHandler("text/json", () => existingDeserializer);
+                client.AddHandler("text/x-json", () => existingDeserializer);
+                client.AddHandler("text/javascript", () => existingDeserializer);
+                client.AddHandler("*+json", () => existingDeserializer);
+            }
+            else
+            {
+                var customDeserializer = new CustomJsonCodec(SerializerSettings, configuration);
+                client.AddHandler("application/json", () => customDeserializer);
+                client.AddHandler("text/json", () => customDeserializer);
+                client.AddHandler("text/x-json", () => customDeserializer);
+                client.AddHandler("text/javascript", () => customDeserializer);
+                client.AddHandler("*+json", () => customDeserializer);
+            }
+
+            var xmlDeserializer = new XmlDeserializer();
+            client.AddHandler("application/xml", () => xmlDeserializer);
+            client.AddHandler("text/xml", () => xmlDeserializer);
+            client.AddHandler("*+xml", () => xmlDeserializer);
+            client.AddHandler("*", () => xmlDeserializer);
+
+            client.Timeout = configuration.Timeout;
+
+            if (configuration.Proxy != null)
+            {
+                client.Proxy = configuration.Proxy;
+            }
+
+            if (configuration.UserAgent != null)
+            {
+                client.UserAgent = configuration.UserAgent;
+            }
+
+            if (configuration.ClientCertificates != null)
+            {
+                client.ClientCertificates = configuration.ClientCertificates;
+            }
+
+            InterceptRequest(req);
+
+            IRestResponse<T> response;
+        {{#supportsRetry}}
+            if (RetryConfiguration.AsyncRetryPolicy != null)
+            {
+                var policy = RetryConfiguration.AsyncRetryPolicy;
+                var policyResult = await policy.ExecuteAndCaptureAsync((ct) => client.ExecuteAsync(req, ct), cancellationToken).ConfigureAwait(false);
+                response = (policyResult.Outcome == OutcomeType.Successful) ? client.Deserialize<T>(policyResult.Result) : new RestResponse<T>
+                {
+                    Request = req,
+                    ErrorException = policyResult.FinalException
+                };
+            }
+            else
+            {
+        {{/supportsRetry}}
+                response = await client.ExecuteAsync<T>(req, cancellationToken).ConfigureAwait(false);
+        {{#supportsRetry}}
+            }
+        {{/supportsRetry}}
+
+            // if the response type is oneOf/anyOf, call FromJSON to deserialize the data
+            if (typeof({{{packageName}}}.{{modelPackage}}.AbstractOpenAPISchema).IsAssignableFrom(typeof(T)))
+            {
+                response.Data = (T) typeof(T).GetMethod("FromJson").Invoke(null, new object[] { response.Content });
+            }
+            else if (typeof(T).Name == "Stream") // for binary response
+            {
+                response.Data = (T)(object)new MemoryStream(response.RawBytes);
+            }
+
+            InterceptResponse(req, 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<Cookie>();
+                foreach (var restResponseCookie in response.Cookies)
+                {
+                    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;
+        }
+
+        #region IAsynchronousClient
+        /// <summary>
+        /// Make a HTTP GET request (async).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public Task<ApiResponse<T>> GetAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return ExecAsync<T>(NewRequest(HttpMethod.Get, path, options, config), config, cancellationToken);
+        }
+
+        /// <summary>
+        /// Make a HTTP POST request (async).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public Task<ApiResponse<T>> PostAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return ExecAsync<T>(NewRequest(HttpMethod.Post, path, options, config), config, cancellationToken);
+        }
+
+        /// <summary>
+        /// Make a HTTP PUT request (async).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public Task<ApiResponse<T>> PutAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return ExecAsync<T>(NewRequest(HttpMethod.Put, path, options, config), config, cancellationToken);
+        }
+
+        /// <summary>
+        /// Make a HTTP DELETE request (async).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public Task<ApiResponse<T>> DeleteAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return ExecAsync<T>(NewRequest(HttpMethod.Delete, path, options, config), config, cancellationToken);
+        }
+
+        /// <summary>
+        /// Make a HTTP HEAD request (async).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public Task<ApiResponse<T>> HeadAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return ExecAsync<T>(NewRequest(HttpMethod.Head, path, options, config), config, cancellationToken);
+        }
+
+        /// <summary>
+        /// Make a HTTP OPTION request (async).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public Task<ApiResponse<T>> OptionsAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return ExecAsync<T>(NewRequest(HttpMethod.Options, path, options, config), config, cancellationToken);
+        }
+
+        /// <summary>
+        /// Make a HTTP PATCH request (async).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public Task<ApiResponse<T>> PatchAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return ExecAsync<T>(NewRequest(HttpMethod.Patch, path, options, config), config, cancellationToken);
+        }
+        #endregion IAsynchronousClient
+        {{/supportsAsync}}
+
+        #region ISynchronousClient
+        /// <summary>
+        /// Make a HTTP GET request (synchronous).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public ApiResponse<T> Get<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return Exec<T>(NewRequest(HttpMethod.Get, path, options, config), config);
+        }
+
+        /// <summary>
+        /// Make a HTTP POST request (synchronous).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public ApiResponse<T> Post<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return Exec<T>(NewRequest(HttpMethod.Post, path, options, config), config);
+        }
+
+        /// <summary>
+        /// Make a HTTP PUT request (synchronous).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public ApiResponse<T> Put<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return Exec<T>(NewRequest(HttpMethod.Put, path, options, config), config);
+        }
+
+        /// <summary>
+        /// Make a HTTP DELETE request (synchronous).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public ApiResponse<T> Delete<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return Exec<T>(NewRequest(HttpMethod.Delete, path, options, config), config);
+        }
+
+        /// <summary>
+        /// Make a HTTP HEAD request (synchronous).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public ApiResponse<T> Head<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return Exec<T>(NewRequest(HttpMethod.Head, path, options, config), config);
+        }
+
+        /// <summary>
+        /// Make a HTTP OPTION request (synchronous).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public ApiResponse<T> Options<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return Exec<T>(NewRequest(HttpMethod.Options, path, options, config), config);
+        }
+
+        /// <summary>
+        /// Make a HTTP PATCH request (synchronous).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public ApiResponse<T> Patch<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return Exec<T>(NewRequest(HttpMethod.Patch, path, options, config), config);
+        }
+        #endregion ISynchronousClient
+    }
+}
diff --git a/openapi/templates/csharp-netcore/ApiException.mustache b/openapi/templates/csharp-netcore/ApiException.mustache
new file mode 100644
index 00000000000..f7dadc5573c
--- /dev/null
+++ b/openapi/templates/csharp-netcore/ApiException.mustache
@@ -0,0 +1,60 @@
+{{>partial_header}}
+
+using System;
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// API Exception
+    /// </summary>
+    {{>visibility}} class ApiException : Exception
+    {
+        /// <summary>
+        /// Gets or sets the error code (HTTP status code)
+        /// </summary>
+        /// <value>The error code (HTTP status code).</value>
+        public int ErrorCode { get; set; }
+
+        /// <summary>
+        /// Gets or sets the error content (body json object)
+        /// </summary>
+        /// <value>The error content (Http response body).</value>
+        public object ErrorContent { get; private set; }
+
+        /// <summary>
+        /// Gets or sets the HTTP headers
+        /// </summary>
+        /// <value>HTTP headers</value>
+        public Multimap<string, string> Headers { get; private set; }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ApiException"/> class.
+        /// </summary>
+        public ApiException() { }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ApiException"/> class.
+        /// </summary>
+        /// <param name="errorCode">HTTP status code.</param>
+        /// <param name="message">Error message.</param>
+        public ApiException(int errorCode, string message) : base(message)
+        {
+            this.ErrorCode = errorCode;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ApiException"/> class.
+        /// </summary>
+        /// <param name="errorCode">HTTP status code.</param>
+        /// <param name="message">Error message.</param>
+        /// <param name="errorContent">Error content.</param>
+        /// <param name="headers">HTTP Headers.</param>
+        public ApiException(int errorCode, string message, object errorContent = null, Multimap<string, string> headers = null) : base(message)
+        {
+            this.ErrorCode = errorCode;
+            this.ErrorContent = errorContent;
+            this.Headers = headers;
+        }
+    }
+
+}
diff --git a/openapi/templates/csharp-netcore/ApiResponse.mustache b/openapi/templates/csharp-netcore/ApiResponse.mustache
new file mode 100644
index 00000000000..161c2acd16c
--- /dev/null
+++ b/openapi/templates/csharp-netcore/ApiResponse.mustache
@@ -0,0 +1,158 @@
+{{>partial_header}}
+
+using System;
+using System.Collections.Generic;
+using System.Net;
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// Provides a non-generic contract for the ApiResponse wrapper.
+    /// </summary>
+    public interface IApiResponse
+    {
+        /// <summary>
+        /// The data type of <see cref="Content"/>
+        /// </summary>
+        Type ResponseType { get; }
+
+        /// <summary>
+        /// The content of this response
+        /// </summary>
+        Object Content { get; }
+
+        /// <summary>
+        /// Gets or sets the status code (HTTP status code)
+        /// </summary>
+        /// <value>The status code.</value>
+        HttpStatusCode StatusCode { get; }
+
+        /// <summary>
+        /// Gets or sets the HTTP headers
+        /// </summary>
+        /// <value>HTTP headers</value>
+        Multimap<string, string> Headers { get; }
+
+        /// <summary>
+        /// Gets or sets any error text defined by the calling client.
+        /// </summary>
+        string ErrorText { get; set; }
+
+        /// <summary>
+        /// Gets or sets any cookies passed along on the response.
+        /// </summary>
+        List<Cookie> Cookies { get; set; }
+
+        /// <summary>
+        /// The raw content of this response
+        /// </summary>
+        string RawContent { get; }
+    }
+
+    /// <summary>
+    /// API Response
+    /// </summary>
+    public class ApiResponse<T> : IApiResponse
+    {
+        #region Properties
+
+        /// <summary>
+        /// Gets or sets the status code (HTTP status code)
+        /// </summary>
+        /// <value>The status code.</value>
+        public HttpStatusCode StatusCode { get; }
+
+        /// <summary>
+        /// Gets or sets the HTTP headers
+        /// </summary>
+        /// <value>HTTP headers</value>
+        public Multimap<string, string> Headers { get; }
+
+        /// <summary>
+        /// Gets or sets the data (parsed HTTP body)
+        /// </summary>
+        /// <value>The data.</value>
+        public T Data { get; }
+
+        /// <summary>
+        /// Gets or sets any error text defined by the calling client.
+        /// </summary>
+        public string ErrorText { get; set; }
+
+        /// <summary>
+        /// Gets or sets any cookies passed along on the response.
+        /// </summary>
+        public List<Cookie> Cookies { get; set; }
+
+        /// <summary>
+        /// The content of this response
+        /// </summary>
+        public Type ResponseType
+        {
+            get { return typeof(T); }
+        }
+
+        /// <summary>
+        /// The data type of <see cref="Content"/>
+        /// </summary>
+        public object Content
+        {
+            get { return Data; }
+        }
+
+        /// <summary>
+        /// The raw content
+        /// </summary>
+        public string RawContent { get; }
+
+        #endregion Properties
+
+        #region Constructors
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ApiResponse{T}" /> class.
+        /// </summary>
+        /// <param name="statusCode">HTTP status code.</param>
+        /// <param name="headers">HTTP headers.</param>
+        /// <param name="data">Data (parsed HTTP body)</param>
+        /// <param name="rawContent">Raw content.</param>
+        public ApiResponse(HttpStatusCode statusCode, Multimap<string, string> headers, T data, string rawContent)
+        {
+            StatusCode = statusCode;
+            Headers = headers;
+            Data = data;
+            RawContent = rawContent;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ApiResponse{T}" /> class.
+        /// </summary>
+        /// <param name="statusCode">HTTP status code.</param>
+        /// <param name="headers">HTTP headers.</param>
+        /// <param name="data">Data (parsed HTTP body)</param>
+        public ApiResponse(HttpStatusCode statusCode, Multimap<string, string> headers, T data) : this(statusCode, headers, data, null)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ApiResponse{T}" /> class.
+        /// </summary>
+        /// <param name="statusCode">HTTP status code.</param>
+        /// <param name="data">Data (parsed HTTP body)</param>
+        /// <param name="rawContent">Raw content.</param>
+        public ApiResponse(HttpStatusCode statusCode, T data, string rawContent) : this(statusCode, null, data, rawContent)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ApiResponse{T}" /> class.
+        /// </summary>
+        /// <param name="statusCode">HTTP status code.</param>
+        /// <param name="data">Data (parsed HTTP body)</param>
+        public ApiResponse(HttpStatusCode statusCode, T data) : this(statusCode, data, null)
+        {
+        }
+
+        #endregion Constructors
+    }
+}
diff --git a/openapi/templates/csharp-netcore/AssemblyInfo.mustache b/openapi/templates/csharp-netcore/AssemblyInfo.mustache
new file mode 100644
index 00000000000..d5d937dc19f
--- /dev/null
+++ b/openapi/templates/csharp-netcore/AssemblyInfo.mustache
@@ -0,0 +1,40 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("{{packageTitle}}")]
+[assembly: AssemblyDescription("{{packageDescription}}")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("{{packageCompany}}")]
+[assembly: AssemblyProduct("{{packageProductName}}")]
+[assembly: AssemblyCopyright("{{packageCopyright}}")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components.  If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("{{packageVersion}}")]
+[assembly: AssemblyFileVersion("{{packageVersion}}")]
+{{^supportsAsync}}
+// Settings which don't support asynchronous operations rely on non-public constructors
+// This is due to how RestSharp requires the type constraint `where T : new()` in places it probably shouldn't.
+[assembly: InternalsVisibleTo("RestSharp")]
+[assembly: InternalsVisibleTo("NewtonSoft.Json")]
+[assembly: InternalsVisibleTo("JsonSubTypes")]
+{{/supportsAsync}}
diff --git a/openapi/templates/csharp-netcore/ClientUtils.mustache b/openapi/templates/csharp-netcore/ClientUtils.mustache
new file mode 100644
index 00000000000..776be1b052f
--- /dev/null
+++ b/openapi/templates/csharp-netcore/ClientUtils.mustache
@@ -0,0 +1,239 @@
+{{>partial_header}}
+
+using System;
+using System.Collections;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+{{#useCompareNetObjects}}
+using KellermanSoftware.CompareNetObjects;
+{{/useCompareNetObjects}}
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// Utility functions providing some benefit to API client consumers.
+    /// </summary>
+    public static class ClientUtils
+    {
+        {{#useCompareNetObjects}}
+        /// <summary>
+        /// An instance of CompareLogic.
+        /// </summary>
+        public static CompareLogic compareLogic;
+
+        /// <summary>
+        /// Static constructor to initialise compareLogic.
+        /// </summary>
+        static ClientUtils()
+        {
+            compareLogic = new CompareLogic();
+        }
+
+        {{/useCompareNetObjects}}
+        /// <summary>
+        /// Sanitize filename by removing the path
+        /// </summary>
+        /// <param name="filename">Filename</param>
+        /// <returns>Filename</returns>
+        public static string SanitizeFilename(string filename)
+        {
+            Match match = Regex.Match(filename, @".*[/\\](.*)$");
+            return match.Success ? match.Groups[1].Value : filename;
+        }
+
+        /// <summary>
+        /// Convert params to key/value pairs.
+        /// Use collectionFormat to properly format lists and collections.
+        /// </summary>
+        /// <param name="collectionFormat">The swagger-supported collection format, one of: csv, tsv, ssv, pipes, multi</param>
+        /// <param name="name">Key name.</param>
+        /// <param name="value">Value object.</param>
+        /// <returns>A multimap of keys with 1..n associated values.</returns>
+        public static Multimap<string, string> ParameterToMultiMap(string collectionFormat, string name, object value)
+        {
+            var parameters = new Multimap<string, string>();
+
+            if (value is ICollection collection && collectionFormat == "multi")
+            {
+                foreach (var item in collection)
+                {
+                    parameters.Add(name, ParameterToString(item));
+                }
+            }
+            else if (value is IDictionary dictionary)
+            {
+                if(collectionFormat == "deepObject") {
+                    foreach (DictionaryEntry entry in dictionary)
+                    {
+                        parameters.Add(name + "[" + entry.Key + "]", ParameterToString(entry.Value));
+                    }
+                }
+                else {
+                    foreach (DictionaryEntry entry in dictionary)
+                    {
+                        parameters.Add(entry.Key.ToString(), ParameterToString(entry.Value));
+                    }
+                }
+            }
+            else
+            {
+                parameters.Add(name, ParameterToString(value));
+            }
+
+            return parameters;
+        }
+
+        /// <summary>
+        /// If parameter is DateTime, output in a formatted string (default ISO 8601), customizable with Configuration.DateTime.
+        /// If parameter is a list, join the list with ",".
+        /// Otherwise just return the string.
+        /// </summary>
+        /// <param name="obj">The parameter (header, path, query, form).</param>
+        /// <param name="configuration">An optional configuration instance, providing formatting options used in processing.</param>
+        /// <returns>Formatted string.</returns>
+        public static string ParameterToString(object obj, IReadableConfiguration configuration = null)
+        {
+            if (obj is DateTime dateTime)
+                // Return a formatted date string - Can be customized with Configuration.DateTimeFormat
+                // Defaults to an ISO 8601, using the known as a Round-trip date/time pattern ("o")
+                // https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx#Anchor_8
+                // For example: 2009-06-15T13:45:30.0000000
+                return dateTime.ToString((configuration ?? GlobalConfiguration.Instance).DateTimeFormat);
+            if (obj is DateTimeOffset dateTimeOffset)
+                // Return a formatted date string - Can be customized with Configuration.DateTimeFormat
+                // Defaults to an ISO 8601, using the known as a Round-trip date/time pattern ("o")
+                // https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx#Anchor_8
+                // For example: 2009-06-15T13:45:30.0000000
+                return dateTimeOffset.ToString((configuration ?? GlobalConfiguration.Instance).DateTimeFormat);
+            if (obj is bool boolean)
+                return boolean ? "true" : "false";
+            if (obj is ICollection collection)
+                return string.Join(",", collection.Cast<object>());
+
+            return Convert.ToString(obj, CultureInfo.InvariantCulture);
+        }
+
+        /// <summary>
+        /// URL encode a string
+        /// Credit/Ref: https://github.com/restsharp/RestSharp/blob/master/RestSharp/Extensions/StringExtensions.cs#L50
+        /// </summary>
+        /// <param name="input">string to be URL encoded</param>
+        /// <returns>Byte array</returns>
+        public static string UrlEncode(string input)
+        {
+            const int maxLength = 32766;
+
+            if (input == null)
+            {
+                throw new ArgumentNullException("input");
+            }
+
+            if (input.Length <= maxLength)
+            {
+                return Uri.EscapeDataString(input);
+            }
+
+            StringBuilder sb = new StringBuilder(input.Length * 2);
+            int index = 0;
+
+            while (index < input.Length)
+            {
+                int length = Math.Min(input.Length - index, maxLength);
+                string subString = input.Substring(index, length);
+
+                sb.Append(Uri.EscapeDataString(subString));
+                index += subString.Length;
+            }
+
+            return sb.ToString();
+        }
+
+        /// <summary>
+        /// Encode string in base64 format.
+        /// </summary>
+        /// <param name="text">string to be encoded.</param>
+        /// <returns>Encoded string.</returns>
+        public static string Base64Encode(string text)
+        {
+            return Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(text));
+        }
+
+        /// <summary>
+        /// Convert stream to byte array
+        /// </summary>
+        /// <param name="inputStream">Input stream to be converted</param>
+        /// <returns>Byte array</returns>
+        public static byte[] ReadAsBytes(Stream inputStream)
+        {
+            using (var ms = new MemoryStream())
+            {
+                inputStream.CopyTo(ms);
+                return ms.ToArray();
+            }
+        }
+
+        /// <summary>
+        /// Select the Content-Type header's value from the given content-type array:
+        /// if JSON type exists in the given array, use it;
+        /// otherwise use the first one defined in 'consumes'
+        /// </summary>
+        /// <param name="contentTypes">The Content-Type array to select from.</param>
+        /// <returns>The Content-Type header to use.</returns>
+        public static string SelectHeaderContentType(string[] contentTypes)
+        {
+            if (contentTypes.Length == 0)
+                return null;
+
+            foreach (var contentType in contentTypes)
+            {
+                if (IsJsonMime(contentType))
+                    return contentType;
+            }
+
+            return contentTypes[0]; // use the first content type specified in 'consumes'
+        }
+
+        /// <summary>
+        /// Select the Accept header's value from the given accepts array:
+        /// if JSON exists in the given array, use it;
+        /// otherwise use all of them (joining into a string)
+        /// </summary>
+        /// <param name="accepts">The accepts array to select from.</param>
+        /// <returns>The Accept header to use.</returns>
+        public static string SelectHeaderAccept(string[] accepts)
+        {
+            if (accepts.Length == 0)
+                return null;
+
+            if (accepts.Contains("application/json", StringComparer.OrdinalIgnoreCase))
+                return "application/json";
+
+            return string.Join(",", accepts);
+        }
+
+        /// <summary>
+        /// Provides a case-insensitive check that a provided content type is a known JSON-like content type.
+        /// </summary>
+        public static readonly Regex JsonRegex = new Regex("(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$");
+
+        /// <summary>
+        /// Check if the given MIME is a JSON MIME.
+        /// JSON MIME examples:
+        ///    application/json
+        ///    application/json; charset=UTF8
+        ///    APPLICATION/JSON
+        ///    application/vnd.company+json
+        /// </summary>
+        /// <param name="mime">MIME</param>
+        /// <returns>Returns True if MIME type is json.</returns>
+        public static bool IsJsonMime(string mime)
+        {
+            if (string.IsNullOrWhiteSpace(mime)) return false;
+
+            return JsonRegex.IsMatch(mime) || mime.Equals("application/json-patch+json");
+        }
+    }
+}
diff --git a/openapi/templates/csharp-netcore/Configuration.mustache b/openapi/templates/csharp-netcore/Configuration.mustache
new file mode 100644
index 00000000000..6c810f89bc1
--- /dev/null
+++ b/openapi/templates/csharp-netcore/Configuration.mustache
@@ -0,0 +1,574 @@
+{{>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;
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// Represents a set of configuration settings
+    /// </summary>
+    {{>visibility}} class Configuration : IReadableConfiguration
+    {
+        #region Constants
+
+        /// <summary>
+        /// Version of the package.
+        /// </summary>
+        /// <value>Version of the package.</value>
+        public const string Version = "{{packageVersion}}";
+
+        /// <summary>
+        /// Identifier for ISO 8601 DateTime Format
+        /// </summary>
+        /// <remarks>See https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx#Anchor_8 for more information.</remarks>
+        // ReSharper disable once InconsistentNaming
+        public const string ISO8601_DATETIME_FORMAT = "o";
+
+        #endregion Constants
+
+        #region Static Members
+
+        /// <summary>
+        /// Default creation of exceptions for a given method name and response object
+        /// </summary>
+        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
+
+        /// <summary>
+        /// Defines the base path of the target API server.
+        /// Example: http://localhost:3000/v1/
+        /// </summary>
+        private string _basePath;
+
+        /// <summary>
+        /// Gets or sets the API key based on the authentication name.
+        /// This is the key and value comprising the "secret" for accessing an API.
+        /// </summary>
+        /// <value>The API key.</value>
+        private IDictionary<string, string> _apiKey;
+
+        /// <summary>
+        /// Gets or sets the prefix (e.g. Token) of the API key based on the authentication name.
+        /// </summary>
+        /// <value>The prefix of the API key.</value>
+        private IDictionary<string, string> _apiKeyPrefix;
+
+        private string _dateTimeFormat = ISO8601_DATETIME_FORMAT;
+        private string _tempFolderPath = Path.GetTempPath();
+        {{#servers.0}}
+
+        /// <summary>
+        /// Gets or sets the servers defined in the OpenAPI spec.
+        /// </summary>
+        /// <value>The servers</value>
+        private IList<IReadOnlyDictionary<string, object>> _servers;
+        {{/servers.0}}
+        {{#hasHttpSignatureMethods}}
+
+        /// <summary>
+        /// HttpSigning configuration
+        /// </summary>
+        private HttpSigningConfiguration _HttpSigningConfiguration = null;
+        {{/hasHttpSignatureMethods}}
+        #endregion Private Members
+
+        #region Constructors
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Configuration" /> class
+        /// </summary>
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")]
+        public Configuration()
+        {
+            Proxy = null;
+            UserAgent = "{{httpUserAgent}}{{^httpUserAgent}}OpenAPI-Generator/{{packageVersion}}/csharp{{/httpUserAgent}}";
+            BasePath = "{{{basePath}}}";
+            DefaultHeaders = new {{^net35}}Concurrent{{/net35}}Dictionary<string, string>();
+            ApiKey = new {{^net35}}Concurrent{{/net35}}Dictionary<string, string>();
+            ApiKeyPrefix = new {{^net35}}Concurrent{{/net35}}Dictionary<string, string>();
+            {{#servers}}
+            {{#-first}}
+            Servers = new List<IReadOnlyDictionary<string, object>>()
+            {
+            {{/-first}}
+                {
+                    new Dictionary<string, object> {
+                        {"url", "{{{url}}}"},
+                        {"description", "{{{description}}}{{^description}}No description provided{{/description}}"},
+                        {{#variables}}
+                        {{#-first}}
+                        {
+                            "variables", new Dictionary<string, object> {
+                        {{/-first}}
+                                {
+                                    "{{{name}}}", new Dictionary<string, object> {
+                                        {"description", "{{{description}}}{{^description}}No description provided{{/description}}"},
+                                        {"default_value", "{{{defaultValue}}}"},
+                                        {{#enumValues}}
+                                        {{#-first}}
+                                        {
+                                            "enum_values", new List<string>() {
+                                        {{/-first}}
+                                                "{{{.}}}"{{^-last}},{{/-last}}
+                                        {{#-last}}
+                                            }
+                                        }
+                                        {{/-last}}
+                                        {{/enumValues}}
+                                    }
+                                }{{^-last}},{{/-last}}
+                        {{#-last}}
+                            }
+                        }
+                        {{/-last}}
+                        {{/variables}}
+                    }
+                }{{^-last}},{{/-last}}
+            {{#-last}}
+            };
+            {{/-last}}
+            {{/servers}}
+
+            // Setting Timeout has side effects (forces ApiClient creation).
+            Timeout = 100000;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Configuration" /> class
+        /// </summary>
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")]
+        public Configuration(
+            IDictionary<string, string> defaultHeaders,
+            IDictionary<string, string> apiKey,
+            IDictionary<string, string> 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
+
+        /// <summary>
+        /// Gets or sets the base path for API access.
+        /// </summary>
+        public virtual string BasePath {
+            get { return _basePath; }
+            set { _basePath = value; }
+        }
+
+        /// <summary>
+        /// Gets or sets the default header.
+        /// </summary>
+        [Obsolete("Use DefaultHeaders instead.")]
+        public virtual IDictionary<string, string> DefaultHeader
+        {
+            get
+            {
+                return DefaultHeaders;
+            }
+            set
+            {
+                DefaultHeaders = value;
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the default headers.
+        /// </summary>
+        public virtual IDictionary<string, string> DefaultHeaders { get; set; }
+
+        /// <summary>
+        /// Gets or sets the HTTP timeout (milliseconds) of ApiClient. Default to 100000 milliseconds.
+        /// </summary>
+        public virtual int Timeout { get; set; }
+
+        /// <summary>
+        /// Gets or sets the proxy
+        /// </summary>
+        /// <value>Proxy.</value>
+        public virtual WebProxy Proxy { get; set; }
+
+        /// <summary>
+        /// Gets or sets the HTTP user agent.
+        /// </summary>
+        /// <value>Http user agent.</value>
+        public virtual string UserAgent { get; set; }
+
+        /// <summary>
+        /// Gets or sets the username (HTTP basic authentication).
+        /// </summary>
+        /// <value>The username.</value>
+        public virtual string Username { get; set; }
+
+        /// <summary>
+        /// Gets or sets the password (HTTP basic authentication).
+        /// </summary>
+        /// <value>The password.</value>
+        public virtual string Password { get; set; }
+
+        /// <summary>
+        /// Gets the API key with prefix.
+        /// </summary>
+        /// <param name="apiKeyIdentifier">API key identifier (authentication scheme).</param>
+        /// <returns>API key with prefix.</returns>
+        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;
+        }
+
+        /// <summary>
+        /// Gets or sets certificate collection to be sent with requests.
+        /// </summary>
+        /// <value>X509 Certificate collection.</value>
+        public X509CertificateCollection ClientCertificates { get; set; }
+
+        /// <summary>
+        /// Gets or sets the access token for OAuth2 authentication.
+        ///
+        /// This helper property simplifies code generation.
+        /// </summary>
+        /// <value>The access token.</value>
+        public virtual string AccessToken { get; set; }
+
+        /// <summary>
+        /// Gets or sets the temporary folder path to store the files downloaded from the server.
+        /// </summary>
+        /// <value>Folder path.</value>
+        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;
+                }
+            }
+        }
+
+        /// <summary>
+        /// 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
+        /// </summary>
+        /// <value>The DateTimeFormat string</value>
+        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;
+            }
+        }
+
+        /// <summary>
+        /// 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:
+        /// <example>
+        /// ApiKeyPrefix["Authorization"] = "Bearer";
+        /// </example>
+        /// … where ApiKey["Authorization"] would then be used to set the value of your bearer token.
+        ///
+        /// <remarks>
+        /// OAuth2 workflows should set tokens via AccessToken.
+        /// </remarks>
+        /// </summary>
+        /// <value>The prefix of the API key.</value>
+        public virtual IDictionary<string, string> ApiKeyPrefix
+        {
+            get { return _apiKeyPrefix; }
+            set
+            {
+                if (value == null)
+                {
+                    throw new InvalidOperationException("ApiKeyPrefix collection may not be null.");
+                }
+                _apiKeyPrefix = value;
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the API key based on the authentication name.
+        /// </summary>
+        /// <value>The API key.</value>
+        public virtual IDictionary<string, string> ApiKey
+        {
+            get { return _apiKey; }
+            set
+            {
+                if (value == null)
+                {
+                    throw new InvalidOperationException("ApiKey collection may not be null.");
+                }
+                _apiKey = value;
+            }
+        }
+        {{#servers.0}}
+
+        /// <summary>
+        /// Gets or sets the servers.
+        /// </summary>
+        /// <value>The servers.</value>
+        public virtual IList<IReadOnlyDictionary<string, object>> Servers
+        {
+            get { return _servers; }
+            set
+            {
+                if (value == null)
+                {
+                    throw new InvalidOperationException("Servers may not be null.");
+                }
+                _servers = value;
+            }
+        }
+
+        /// <summary>
+        /// Returns URL based on server settings without providing values
+        /// for the variables
+        /// </summary>
+        /// <param name="index">Array index of the server settings.</param>
+        /// <return>The server URL.</return>
+        public string GetServerUrl(int index)
+        {
+            return GetServerUrl(index, null);
+        }
+
+        /// <summary>
+        /// Returns URL based on server settings.
+        /// </summary>
+        /// <param name="index">Array index of the server settings.</param>
+        /// <param name="inputVariables">Dictionary of the variables and the corresponding values.</param>
+        /// <return>The server URL.</return>
+        public string GetServerUrl(int index, Dictionary<string, string> 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<string, string>();
+            }
+
+            IReadOnlyDictionary<string, object> server = Servers[index];
+            string url = (string)server["url"];
+
+            // go through variable and assign a value
+            foreach (KeyValuePair<string, object> variable in (IReadOnlyDictionary<string, object>)server["variables"])
+            {
+
+                IReadOnlyDictionary<string, object> serverVariables = (IReadOnlyDictionary<string, object>)(variable.Value);
+
+                if (inputVariables.ContainsKey(variable.Key))
+                {
+                    if (((List<string>)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<string>)serverVariables["enum_values"]}");
+                    }
+                }
+                else
+                {
+                    // use default value
+                    url = url.Replace("{" + variable.Key + "}", (string)serverVariables["default_value"]);
+                }
+            }
+
+            return url;
+        }
+        {{/servers.0}}
+        {{#hasHttpSignatureMethods}}
+
+        /// <summary>
+        /// Gets and Sets the HttpSigningConfiguration
+        /// </summary>
+        public HttpSigningConfiguration HttpSigningConfiguration
+        {
+            get { return _HttpSigningConfiguration; }
+            set { _HttpSigningConfiguration = value; }
+        }
+        {{/hasHttpSignatureMethods}}
+
+        #endregion Properties
+
+        #region Methods
+
+        /// <summary>
+        /// Returns a string with essential information for debugging.
+        /// </summary>
+        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;
+        }
+
+        /// <summary>
+        /// Add Api Key Header.
+        /// </summary>
+        /// <param name="key">Api Key name.</param>
+        /// <param name="value">Api Key value.</param>
+        /// <returns></returns>
+        public void AddApiKey(string key, string value)
+        {
+            ApiKey[key] = value;
+        }
+
+        /// <summary>
+        /// Sets the API key prefix.
+        /// </summary>
+        /// <param name="key">Api Key name.</param>
+        /// <param name="value">Api Key value.</param>
+        public void AddApiKeyPrefix(string key, string value)
+        {
+            ApiKeyPrefix[key] = value;
+        }
+
+        #endregion Methods
+
+        #region Static Members
+        /// <summary>
+        /// Merge configurations.
+        /// </summary>
+        /// <param name="first">First configuration.</param>
+        /// <param name="second">Second configuration.</param>
+        /// <return>Merged configuration.</return>
+        public static IReadableConfiguration MergeConfigurations(IReadableConfiguration first, IReadableConfiguration second)
+        {
+            if (second == null) return first ?? GlobalConfiguration.Instance;
+
+            Dictionary<string, string> apiKey = first.ApiKey.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
+            Dictionary<string, string> apiKeyPrefix = first.ApiKeyPrefix.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
+            Dictionary<string, string> 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,
+                {{#hasHttpSignatureMethods}}
+                HttpSigningConfiguration = second.HttpSigningConfiguration ?? first.HttpSigningConfiguration,
+                {{/hasHttpSignatureMethods}}
+                TempFolderPath = second.TempFolderPath ?? first.TempFolderPath,
+                DateTimeFormat = second.DateTimeFormat ?? first.DateTimeFormat
+            };
+            return config;
+        }
+        #endregion Static Members
+    }
+}
diff --git a/openapi/templates/csharp-netcore/ExceptionFactory.mustache b/openapi/templates/csharp-netcore/ExceptionFactory.mustache
new file mode 100644
index 00000000000..4a141f6f1ae
--- /dev/null
+++ b/openapi/templates/csharp-netcore/ExceptionFactory.mustache
@@ -0,0 +1,14 @@
+{{>partial_header}}
+
+using System;
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// A delegate to ExceptionFactory method
+    /// </summary>
+    /// <param name="methodName">Method name</param>
+    /// <param name="response">Response</param>
+    /// <returns>Exceptions</returns>
+    {{>visibility}} delegate Exception ExceptionFactory(string methodName, IApiResponse response);
+}
diff --git a/openapi/templates/csharp-netcore/GlobalConfiguration.mustache b/openapi/templates/csharp-netcore/GlobalConfiguration.mustache
new file mode 100644
index 00000000000..bdfa4b99cc1
--- /dev/null
+++ b/openapi/templates/csharp-netcore/GlobalConfiguration.mustache
@@ -0,0 +1,59 @@
+{{>partial_header}}
+
+using System.Collections.Generic;
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// <see cref="GlobalConfiguration"/> provides a compile-time extension point for globally configuring
+    /// API Clients.
+    /// </summary>
+    /// <remarks>
+    /// A customized implementation via partial class may reside in another file and may
+    /// be excluded from automatic generation via a .openapi-generator-ignore file.
+    /// </remarks>
+    public partial class GlobalConfiguration : Configuration
+    {
+        #region Private Members
+
+        private static readonly object GlobalConfigSync = new { };
+        private static IReadableConfiguration _globalConfiguration;
+
+        #endregion Private Members
+
+        #region Constructors
+
+        /// <inheritdoc />
+        private GlobalConfiguration()
+        {
+        }
+
+        /// <inheritdoc />
+        public GlobalConfiguration(IDictionary<string, string> defaultHeader, IDictionary<string, string> apiKey, IDictionary<string, string> apiKeyPrefix, string basePath = "http://localhost:3000/api") : base(defaultHeader, apiKey, apiKeyPrefix, basePath)
+        {
+        }
+
+        static GlobalConfiguration()
+        {
+            Instance = new GlobalConfiguration();
+        }
+
+        #endregion Constructors
+
+        /// <summary>
+        /// Gets or sets the default Configuration.
+        /// </summary>
+        /// <value>Configuration.</value>
+        public static IReadableConfiguration Instance
+        {
+            get { return _globalConfiguration; }
+            set
+            {
+                lock (GlobalConfigSync)
+                {
+                    _globalConfiguration = value;
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/openapi/templates/csharp-netcore/HttpMethod.mustache b/openapi/templates/csharp-netcore/HttpMethod.mustache
new file mode 100644
index 00000000000..904a042a9ac
--- /dev/null
+++ b/openapi/templates/csharp-netcore/HttpMethod.mustache
@@ -0,0 +1,25 @@
+{{>partial_header}}
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// Http methods supported by swagger
+    /// </summary>
+    public enum HttpMethod
+    {
+        /// <summary>HTTP GET request.</summary>
+        Get,
+        /// <summary>HTTP POST request.</summary>
+        Post,
+        /// <summary>HTTP PUT request.</summary>
+        Put,
+        /// <summary>HTTP DELETE request.</summary>
+        Delete,
+        /// <summary>HTTP HEAD request.</summary>
+        Head,
+        /// <summary>HTTP OPTIONS request.</summary>
+        Options,
+        /// <summary>HTTP PATCH request.</summary>
+        Patch
+    }
+}
diff --git a/openapi/templates/csharp-netcore/HttpSigningConfiguration.mustache b/openapi/templates/csharp-netcore/HttpSigningConfiguration.mustache
new file mode 100644
index 00000000000..aa788ad6224
--- /dev/null
+++ b/openapi/templates/csharp-netcore/HttpSigningConfiguration.mustache
@@ -0,0 +1,757 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Security.Cryptography;
+using System.Text;
+using System.Web;
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// Class for HttpSigning auth related parameter and methods
+    /// </summary>
+    public class HttpSigningConfiguration
+    {
+        #region
+        /// <summary>
+        /// Initialize the HashAlgorithm and SigningAlgorithm to default value
+        /// </summary>
+        public HttpSigningConfiguration()
+        {
+            HashAlgorithm = HashAlgorithmName.SHA256;
+            SigningAlgorithm = "PKCS1-v15";
+        }
+        #endregion
+
+        #region Properties
+        /// <summary>
+        ///Gets the Api keyId
+        /// </summary>
+        public string KeyId { get; set; }
+
+        /// <summary>
+        /// Gets the Key file path
+        /// </summary>
+        public string KeyFilePath { get; set; }
+
+        /// <summary>
+        /// Gets the key pass phrase for password protected key
+        /// </summary>
+        public SecureString KeyPassPhrase { get; set; }
+
+        /// <summary>
+        /// Gets the HTTP signing header
+        /// </summary>
+        public List<string> HttpSigningHeader { get; set; }
+
+        /// <summary>
+        /// Gets the hash algorithm sha256 or sha512
+        /// </summary>
+        public HashAlgorithmName HashAlgorithm { get; set; }
+
+        /// <summary>
+        /// Gets the signing algorithm
+        /// </summary>
+        public string SigningAlgorithm { get; set; }
+
+        /// <summary>
+        /// Gets the Signature validity period in seconds
+        /// </summary>
+        public int SignatureValidityPeriod { get; set; }
+
+        #endregion
+
+        #region enum
+        private enum PrivateKeyType
+        {
+            None = 0,
+            RSA = 1,
+            ECDSA = 2,
+        }
+        #endregion
+
+        #region Methods
+        /// <summary>
+        /// Gets the Headers for HttpSigning
+        /// </summary>
+        /// <param name="basePath">Base path</param>
+        /// <param name="method">HTTP method</param>
+        /// <param name="path">Path</param>
+        /// <param name="requestOptions">Request options</param>
+        /// <returns></returns>
+        internal Dictionary<string, string> GetHttpSignedHeader(string basePath,string method, string path, RequestOptions requestOptions)
+        {
+            const string HEADER_REQUEST_TARGET = "(request-target)";
+            //The time when the HTTP signature expires. The API server should reject HTTP requests
+            //that have expired.
+            const string HEADER_EXPIRES = "(expires)";
+            //The 'Date' header.
+            const string HEADER_DATE = "Date";
+            //The 'Host' header.
+            const string HEADER_HOST = "Host";
+            //The time when the HTTP signature was generated.
+            const string HEADER_CREATED = "(created)";
+            //When the 'Digest' header is included in the HTTP signature, the client automatically
+            //computes the digest of the HTTP request body, per RFC 3230.
+            const string HEADER_DIGEST = "Digest";
+            //The 'Authorization' header is automatically generated by the client. It includes
+            //the list of signed headers and a base64-encoded signature.
+            const string HEADER_AUTHORIZATION = "Authorization";
+
+            //Hash table to store singed headers
+            var HttpSignedRequestHeader = new Dictionary<string, string>();
+            var HttpSignatureHeader = new Dictionary<string, string>();
+
+            if (HttpSigningHeader.Count == 0)
+            {
+                HttpSigningHeader.Add("(created)");
+            }
+
+            if (requestOptions.PathParameters != null)
+            {
+                foreach (var pathParam in requestOptions.PathParameters)
+                {
+                    var tempPath = path.Replace(pathParam.Key, "0");
+                    path = string.Format(tempPath, pathParam.Value);
+                }
+            }
+
+            var httpValues = HttpUtility.ParseQueryString(string.Empty);
+            foreach (var parameter in requestOptions.QueryParameters)
+            {
+#if (NETCOREAPP)
+                if (parameter.Value.Count > 1)
+                { // array
+                    foreach (var value in parameter.Value)
+                    {
+                        httpValues.Add(HttpUtility.UrlEncode(parameter.Key) + "[]", value);
+                    }
+                }
+                else
+                {
+                httpValues.Add(HttpUtility.UrlEncode(parameter.Key), parameter.Value[0]);
+                }
+#else
+                if (parameter.Value.Count > 1)
+                { // array
+                    foreach (var value in parameter.Value)
+                    {
+                        httpValues.Add(parameter.Key + "[]", value);
+                    }
+                }
+                else
+                {
+                    httpValues.Add(parameter.Key, parameter.Value[0]);
+                }
+#endif
+            }
+            var uriBuilder = new UriBuilder(string.Concat(basePath, path));
+            uriBuilder.Query = httpValues.ToString().Replace("+", "%20");
+
+            var dateTime = DateTime.Now;
+            string Digest = string.Empty;
+
+            //get the body
+            string requestBody = string.Empty;
+            if (requestOptions.Data != null)
+            {
+                var serializerSettings = new JsonSerializerSettings();
+                requestBody = JsonConvert.SerializeObject(requestOptions.Data, serializerSettings);
+            }
+
+            if (HashAlgorithm == HashAlgorithmName.SHA256)
+            {
+                var bodyDigest = GetStringHash(HashAlgorithm.ToString(), requestBody);
+                Digest = string.Format("SHA-256={0}", Convert.ToBase64String(bodyDigest));
+            }
+            else if (HashAlgorithm == HashAlgorithmName.SHA512)
+            {
+                var bodyDigest = GetStringHash(HashAlgorithm.ToString(), requestBody);
+                Digest = string.Format("SHA-512={0}", Convert.ToBase64String(bodyDigest));
+            }
+            else
+            {
+                throw new Exception(string.Format("{0} not supported", HashAlgorithm));
+            }
+
+
+            foreach (var header in HttpSigningHeader)
+            {
+                if (header.Equals(HEADER_REQUEST_TARGET))
+                {
+                    var targetUrl = string.Format("{0} {1}{2}", method.ToLower(), uriBuilder.Path, uriBuilder.Query);
+                    HttpSignatureHeader.Add(header.ToLower(), targetUrl);
+                }
+                else if (header.Equals(HEADER_EXPIRES))
+                {
+                    var expireDateTime = dateTime.AddSeconds(SignatureValidityPeriod);
+                    HttpSignatureHeader.Add(header.ToLower(), GetUnixTime(expireDateTime).ToString());
+                }
+                else if (header.Equals(HEADER_DATE))
+                {
+                    var utcDateTime = dateTime.ToUniversalTime().ToString("r");
+                    HttpSignatureHeader.Add(header.ToLower(), utcDateTime);
+                    HttpSignedRequestHeader.Add(HEADER_DATE, utcDateTime);
+                }
+                else if (header.Equals(HEADER_HOST))
+                {
+                    HttpSignatureHeader.Add(header.ToLower(), uriBuilder.Host);
+                    HttpSignedRequestHeader.Add(HEADER_HOST, uriBuilder.Host);
+                }
+                else if (header.Equals(HEADER_CREATED))
+                {
+                    HttpSignatureHeader.Add(header.ToLower(), GetUnixTime(dateTime).ToString());
+                }
+                else if (header.Equals(HEADER_DIGEST))
+                {
+                    HttpSignedRequestHeader.Add(HEADER_DIGEST, Digest);
+                    HttpSignatureHeader.Add(header.ToLower(), Digest);
+                }
+                else
+                {
+                    bool isHeaderFound = false;
+                    foreach (var item in requestOptions.HeaderParameters)
+                    {
+                        if (string.Equals(item.Key, header, StringComparison.OrdinalIgnoreCase))
+                        {
+                            HttpSignatureHeader.Add(header.ToLower(), item.Value.ToString());
+                            isHeaderFound = true;
+                            break;
+                        }
+                    }
+                    if (!isHeaderFound)
+                    {
+                        throw new Exception(string.Format("Cannot sign HTTP request.Request does not contain the {0} header.",header));
+                    }
+                }
+
+            }
+            var headersKeysString = string.Join(" ", HttpSignatureHeader.Keys);
+            var headerValuesList = new List<string>();
+
+            foreach (var keyVal in HttpSignatureHeader)
+            {
+                headerValuesList.Add(string.Format("{0}: {1}", keyVal.Key, keyVal.Value));
+
+            }
+            //Concatenate headers value separated by new line
+            var headerValuesString = string.Join("\n", headerValuesList);
+            var signatureStringHash = GetStringHash(HashAlgorithm.ToString(), headerValuesString);
+            string headerSignatureStr = null;
+            var keyType = GetKeyType(KeyFilePath);
+
+            if (keyType == PrivateKeyType.RSA)
+            {
+                headerSignatureStr = GetRSASignature(signatureStringHash);
+            }
+            else if (keyType == PrivateKeyType.ECDSA)
+            {
+                headerSignatureStr = GetECDSASignature(signatureStringHash);
+            }
+            var cryptographicScheme = "hs2019";
+            var authorizationHeaderValue = string.Format("Signature keyId=\"{0}\",algorithm=\"{1}\"",
+                KeyId, cryptographicScheme);
+
+            if (HttpSignatureHeader.ContainsKey(HEADER_CREATED))
+            {
+                authorizationHeaderValue += string.Format(",created={0}", HttpSignatureHeader[HEADER_CREATED]);
+            }
+
+            if (HttpSignatureHeader.ContainsKey(HEADER_EXPIRES))
+            {
+                authorizationHeaderValue += string.Format(",expires={0}", HttpSignatureHeader[HEADER_EXPIRES]);
+            }
+
+            authorizationHeaderValue += string.Format(",headers=\"{0}\",signature=\"{1}\"",
+                headersKeysString, headerSignatureStr);
+
+            HttpSignedRequestHeader.Add(HEADER_AUTHORIZATION, authorizationHeaderValue);
+
+            return HttpSignedRequestHeader;
+        }
+
+        private byte[] GetStringHash(string hashName, string stringToBeHashed)
+        {
+            var hashAlgorithm = System.Security.Cryptography.HashAlgorithm.Create(hashName);
+            var bytes = Encoding.UTF8.GetBytes(stringToBeHashed);
+            var stringHash = hashAlgorithm.ComputeHash(bytes);
+            return stringHash;
+        }
+
+        private int GetUnixTime(DateTime date2)
+        {
+            DateTime date1 = new DateTime(1970, 01, 01);
+            TimeSpan timeSpan = date2 - date1;
+            return (int)timeSpan.TotalSeconds;
+        }
+
+        private string GetRSASignature(byte[] stringToSign)
+        {
+            RSA rsa = GetRSAProviderFromPemFile(KeyFilePath, KeyPassPhrase);
+            if (SigningAlgorithm == "RSASSA-PSS")
+            {
+                var signedbytes = rsa.SignHash(stringToSign, HashAlgorithm, RSASignaturePadding.Pss);
+                return Convert.ToBase64String(signedbytes);
+            }
+            else if (SigningAlgorithm == "PKCS1-v15")
+            {
+                var signedbytes = rsa.SignHash(stringToSign, HashAlgorithm, RSASignaturePadding.Pkcs1);
+                return Convert.ToBase64String(signedbytes);
+            }
+            return string.Empty;
+        }
+
+        /// <summary>
+        /// Gets the ECDSA signature
+        /// </summary>
+        /// <param name="dataToSign"></param>
+        /// <returns></returns>
+        private string GetECDSASignature(byte[] dataToSign)
+        {
+            if (!File.Exists(KeyFilePath))
+            {
+                throw new Exception("key file path does not exist.");
+            }
+
+            var ecKeyHeader = "-----BEGIN EC PRIVATE KEY-----";
+            var ecKeyFooter = "-----END EC PRIVATE KEY-----";
+            var keyStr = File.ReadAllText(KeyFilePath);
+            var ecKeyBase64String = keyStr.Replace(ecKeyHeader, "").Replace(ecKeyFooter, "").Trim();
+            var keyBytes = System.Convert.FromBase64String(ecKeyBase64String);
+            var ecdsa = ECDsa.Create();
+
+#if (NETCOREAPP3_0 || NETCOREAPP3_1 || NET5_0)
+            var byteCount = 0;
+            if (KeyPassPhrase != null)
+            {
+                IntPtr unmanagedString = IntPtr.Zero;
+                try
+                {
+                    // convert secure string to byte array
+                    unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(KeyPassPhrase);
+                    ecdsa.ImportEncryptedPkcs8PrivateKey(Encoding.UTF8.GetBytes(Marshal.PtrToStringUni(unmanagedString)), keyBytes, out byteCount);
+                }
+                finally
+                {
+                    if (unmanagedString != IntPtr.Zero)
+                    {
+                        Marshal.ZeroFreeBSTR(unmanagedString);
+                    }
+                }
+            }
+            else
+            {
+                ecdsa.ImportPkcs8PrivateKey(keyBytes, out byteCount);
+            }
+            var signedBytes = ecdsa.SignHash(dataToSign);
+            var derBytes = ConvertToECDSAANS1Format(signedBytes);
+            var signedString = System.Convert.ToBase64String(derBytes);
+
+            return signedString;
+#else
+            throw new Exception("ECDSA signing is supported only on NETCOREAPP3_0 and above");
+#endif
+
+        }
+
+        private  byte[] ConvertToECDSAANS1Format(byte[] signedBytes)
+        {
+            var derBytes = new List<byte>();
+            byte derLength = 68; //default length for ECDSA code signing bit 0x44
+            byte rbytesLength = 32; //R length 0x20
+            byte sbytesLength = 32; //S length 0x20
+            var rBytes = new List<byte>();
+            var sBytes = new List<byte>();
+            for (int i = 0; i < 32; i++)
+            {
+                rBytes.Add(signedBytes[i]);
+            }
+            for (int i = 32; i < 64; i++)
+            {
+                sBytes.Add(signedBytes[i]);
+            }
+
+            if (rBytes[0] > 0x7F)
+            {
+                derLength++;
+                rbytesLength++;
+                var tempBytes = new List<byte>();
+                tempBytes.AddRange(rBytes);
+                rBytes.Clear();
+                rBytes.Add(0x00);
+                rBytes.AddRange(tempBytes);
+            }
+
+            if (sBytes[0] > 0x7F)
+            {
+                derLength++;
+                sbytesLength++;
+                var tempBytes = new List<byte>();
+                tempBytes.AddRange(sBytes);
+                sBytes.Clear();
+                sBytes.Add(0x00);
+                sBytes.AddRange(tempBytes);
+
+            }
+
+            derBytes.Add(48);  //start of the sequence 0x30
+            derBytes.Add(derLength);  //total length r length, type and r bytes
+
+            derBytes.Add(2); //tag for integer
+            derBytes.Add(rbytesLength); //length of r
+            derBytes.AddRange(rBytes);
+
+            derBytes.Add(2); //tag for integer
+            derBytes.Add(sbytesLength); //length of s
+            derBytes.AddRange(sBytes);
+            return derBytes.ToArray();
+        }
+
+        private  RSACryptoServiceProvider GetRSAProviderFromPemFile(string pemfile, SecureString keyPassPhrase = null)
+        {
+            const string pempubheader = "-----BEGIN PUBLIC KEY-----";
+            const string pempubfooter = "-----END PUBLIC KEY-----";
+            bool isPrivateKeyFile = true;
+            byte[] pemkey = null;
+
+            if (!File.Exists(pemfile))
+            {
+                throw new Exception("private key file does not exist.");
+            }
+            string pemstr = File.ReadAllText(pemfile).Trim();
+
+            if (pemstr.StartsWith(pempubheader) && pemstr.EndsWith(pempubfooter))
+            {
+                isPrivateKeyFile = false;
+            }
+
+            if (isPrivateKeyFile)
+            {
+                pemkey = ConvertPrivateKeyToBytes(pemstr, keyPassPhrase);
+                if (pemkey == null)
+                {
+                    return null;
+                }
+                return DecodeRSAPrivateKey(pemkey);
+            }
+            return null;
+        }
+
+        private byte[] ConvertPrivateKeyToBytes(string instr, SecureString keyPassPhrase = null)
+        {
+            const string pemprivheader = "-----BEGIN RSA PRIVATE KEY-----";
+            const string pemprivfooter = "-----END RSA PRIVATE KEY-----";
+            string pemstr = instr.Trim();
+            byte[] binkey;
+
+            if (!pemstr.StartsWith(pemprivheader) || !pemstr.EndsWith(pemprivfooter))
+            {
+                return null;
+            }
+
+            StringBuilder sb = new StringBuilder(pemstr);
+            sb.Replace(pemprivheader, "");
+            sb.Replace(pemprivfooter, "");
+            string pvkstr = sb.ToString().Trim();
+
+            try
+            {   // if there are no PEM encryption info lines, this is an UNencrypted PEM private key
+                binkey = Convert.FromBase64String(pvkstr);
+                return binkey;
+            }
+            catch (System.FormatException)
+            {
+                StringReader str = new StringReader(pvkstr);
+
+                //-------- read PEM encryption info. lines and extract salt -----
+                if (!str.ReadLine().StartsWith("Proc-Type: 4,ENCRYPTED"))
+                {
+                    return null;
+                }
+                string saltline = str.ReadLine();
+                if (!saltline.StartsWith("DEK-Info: DES-EDE3-CBC,"))
+                {
+                    return null;
+                }
+                string saltstr = saltline.Substring(saltline.IndexOf(",") + 1).Trim();
+                byte[] salt = new byte[saltstr.Length / 2];
+                for (int i = 0; i < salt.Length; i++)
+                    salt[i] = Convert.ToByte(saltstr.Substring(i * 2, 2), 16);
+                if (!(str.ReadLine() == ""))
+                {
+                    return null;
+                }
+
+                //------ remaining b64 data is encrypted RSA key ----
+                string encryptedstr = str.ReadToEnd();
+
+                try
+                {   //should have b64 encrypted RSA key now
+                    binkey = Convert.FromBase64String(encryptedstr);
+                }
+                catch (System.FormatException)
+                {   //data is not in base64 format
+                    return null;
+                }
+
+                byte[] deskey = GetEncryptedKey(salt, keyPassPhrase, 1, 2);    // count=1 (for OpenSSL implementation); 2 iterations to get at least 24 bytes
+                if (deskey == null)
+                {
+                    return null;
+                }
+
+                //------ Decrypt the encrypted 3des-encrypted RSA private key ------
+                byte[] rsakey = DecryptKey(binkey, deskey, salt); //OpenSSL uses salt value in PEM header also as 3DES IV
+                return rsakey;
+            }
+        }
+
+        private RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
+        {
+            byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
+
+            // ---------  Set up stream to decode the asn.1 encoded RSA private key  ------
+            MemoryStream mem = new MemoryStream(privkey);
+            BinaryReader binr = new BinaryReader(mem);    //wrap Memory Stream with BinaryReader for easy reading
+            byte bt = 0;
+            ushort twobytes = 0;
+            int elems = 0;
+            try
+            {
+                twobytes = binr.ReadUInt16();
+                if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
+                {
+                    binr.ReadByte();    //advance 1 byte
+                }
+                else if (twobytes == 0x8230)
+                {
+                    binr.ReadInt16();   //advance 2 bytes
+                }
+                else
+                {
+                    return null;
+                }
+
+                twobytes = binr.ReadUInt16();
+                if (twobytes != 0x0102) //version number
+                {
+                    return null;
+                }
+                bt = binr.ReadByte();
+                if (bt != 0x00)
+                {
+                    return null;
+                }
+
+                //------  all private key components are Integer sequences ----
+                elems = GetIntegerSize(binr);
+                MODULUS = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                E = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                D = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                P = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                Q = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                DP = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                DQ = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                IQ = binr.ReadBytes(elems);
+
+                // ------- create RSACryptoServiceProvider instance and initialize with public key -----
+                RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
+                RSAParameters RSAparams = new RSAParameters();
+                RSAparams.Modulus = MODULUS;
+                RSAparams.Exponent = E;
+                RSAparams.D = D;
+                RSAparams.P = P;
+                RSAparams.Q = Q;
+                RSAparams.DP = DP;
+                RSAparams.DQ = DQ;
+                RSAparams.InverseQ = IQ;
+                RSA.ImportParameters(RSAparams);
+                return RSA;
+            }
+            catch (Exception)
+            {
+                return null;
+            }
+            finally
+            {
+                binr.Close();
+            }
+        }
+
+        private int GetIntegerSize(BinaryReader binr)
+        {
+            byte bt = 0;
+            byte lowbyte = 0x00;
+            byte highbyte = 0x00;
+            int count = 0;
+            bt = binr.ReadByte();
+            if (bt != 0x02) //expect integer
+            {
+                return 0;
+            }
+            bt = binr.ReadByte();
+
+            if (bt == 0x81)
+            {
+                count = binr.ReadByte();    // data size in next byte
+            }
+            else if (bt == 0x82)
+            {
+                highbyte = binr.ReadByte(); // data size in next 2 bytes
+                lowbyte = binr.ReadByte();
+                byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
+                count = BitConverter.ToInt32(modint, 0);
+            }
+            else
+            {
+                count = bt;     // we already have the data size
+            }
+            while (binr.ReadByte() == 0x00)
+            {
+                //remove high order zeros in data
+                count -= 1;
+            }
+            binr.BaseStream.Seek(-1, SeekOrigin.Current);
+            //last ReadByte wasn't a removed zero, so back up a byte
+            return count;
+        }
+
+        private byte[] GetEncryptedKey(byte[] salt, SecureString secpswd, int count, int miter)
+        {
+            IntPtr unmanagedPswd = IntPtr.Zero;
+            int HASHLENGTH = 16;    //MD5 bytes
+            byte[] keymaterial = new byte[HASHLENGTH * miter];     //to store concatenated Mi hashed results
+
+            byte[] psbytes = new byte[secpswd.Length];
+            unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
+            Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length);
+            Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);
+
+            // --- concatenate salt and pswd bytes into fixed data array ---
+            byte[] data00 = new byte[psbytes.Length + salt.Length];
+            Array.Copy(psbytes, data00, psbytes.Length);      //copy the pswd bytes
+            Array.Copy(salt, 0, data00, psbytes.Length, salt.Length); //concatenate the salt bytes
+
+            // ---- do multi-hashing and concatenate results  D1, D2 ...  into keymaterial bytes ----
+            MD5 md5 = new MD5CryptoServiceProvider();
+            byte[] result = null;
+            byte[] hashtarget = new byte[HASHLENGTH + data00.Length];   //fixed length initial hashtarget
+
+            for (int j = 0; j < miter; j++)
+            {
+                // ----  Now hash consecutively for count times ------
+                if (j == 0)
+                {
+                    result = data00;    //initialize
+                }
+                else
+                {
+                    Array.Copy(result, hashtarget, result.Length);
+                    Array.Copy(data00, 0, hashtarget, result.Length, data00.Length);
+                    result = hashtarget;
+                }
+
+                for (int i = 0; i < count; i++)
+                    result = md5.ComputeHash(result);
+                Array.Copy(result, 0, keymaterial, j * HASHLENGTH, result.Length);  //concatenate to keymaterial
+            }
+            byte[] deskey = new byte[24];
+            Array.Copy(keymaterial, deskey, deskey.Length);
+
+            Array.Clear(psbytes, 0, psbytes.Length);
+            Array.Clear(data00, 0, data00.Length);
+            Array.Clear(result, 0, result.Length);
+            Array.Clear(hashtarget, 0, hashtarget.Length);
+            Array.Clear(keymaterial, 0, keymaterial.Length);
+            return deskey;
+        }
+
+        private byte[] DecryptKey(byte[] cipherData, byte[] desKey, byte[] IV)
+        {
+            MemoryStream memst = new MemoryStream();
+            TripleDES alg = TripleDES.Create();
+            alg.Key = desKey;
+            alg.IV = IV;
+            try
+            {
+                CryptoStream cs = new CryptoStream(memst, alg.CreateDecryptor(), CryptoStreamMode.Write);
+                cs.Write(cipherData, 0, cipherData.Length);
+                cs.Close();
+            }
+            catch (Exception)
+            {
+                return null;
+            }
+            byte[] decryptedData = memst.ToArray();
+            return decryptedData;
+        }
+
+        /// <summary>
+        /// Detect the key type from the pem file.
+        /// </summary>
+        /// <param name="keyFilePath">key file path in pem format</param>
+        /// <returns></returns>
+        private PrivateKeyType GetKeyType(string keyFilePath)
+        {
+            if (!File.Exists(keyFilePath))
+            {
+                throw new Exception("Key file path does not exist.");
+            }
+
+            var ecPrivateKeyHeader = "BEGIN EC PRIVATE KEY";
+            var ecPrivateKeyFooter = "END EC PRIVATE KEY";
+            var rsaPrivateKeyHeader = "BEGIN RSA PRIVATE KEY";
+            var rsaPrivateFooter = "END RSA PRIVATE KEY";
+            //var pkcs8Header = "BEGIN PRIVATE KEY";
+            //var pkcs8Footer = "END PRIVATE KEY";
+            var keyType = PrivateKeyType.None;
+            var key = File.ReadAllLines(keyFilePath);
+
+            if (key[0].ToString().Contains(rsaPrivateKeyHeader) &&
+                key[key.Length - 1].ToString().Contains(rsaPrivateFooter))
+            {
+                keyType = PrivateKeyType.RSA;
+            }
+            else if (key[0].ToString().Contains(ecPrivateKeyHeader) &&
+                key[key.Length - 1].ToString().Contains(ecPrivateKeyFooter))
+            {
+                keyType = PrivateKeyType.ECDSA;
+            }
+            else if (key[0].ToString().Contains(ecPrivateKeyHeader) &&
+                key[key.Length - 1].ToString().Contains(ecPrivateKeyFooter))
+            {
+
+                /* this type of key can hold many type different types of private key, but here due lack of pem header
+                Considering this as EC key
+                */
+                //TODO :- update the key based on oid
+                keyType = PrivateKeyType.ECDSA;
+            }
+            else
+            {
+                throw new Exception("Either the key is invalid or key is not supported");
+
+            }
+            return keyType;
+        }
+        #endregion
+    }
+}
diff --git a/openapi/templates/csharp-netcore/IApiAccessor.mustache b/openapi/templates/csharp-netcore/IApiAccessor.mustache
new file mode 100644
index 00000000000..a269f56e904
--- /dev/null
+++ b/openapi/templates/csharp-netcore/IApiAccessor.mustache
@@ -0,0 +1,29 @@
+{{>partial_header}}
+
+using System;
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// Represents configuration aspects required to interact with the API endpoints.
+    /// </summary>
+    {{>visibility}} interface IApiAccessor
+    {
+        /// <summary>
+        /// Gets or sets the configuration object
+        /// </summary>
+        /// <value>An instance of the Configuration</value>
+        IReadableConfiguration Configuration { get; set; }
+
+        /// <summary>
+        /// Gets the base path of the API client.
+        /// </summary>
+        /// <value>The base path</value>
+        string GetBasePath();
+
+        /// <summary>
+        /// Provides a factory method hook for the creation of exceptions.
+        /// </summary>
+        ExceptionFactory ExceptionFactory { get; set; }
+    }
+}
diff --git a/openapi/templates/csharp-netcore/IAsynchronousClient.mustache b/openapi/templates/csharp-netcore/IAsynchronousClient.mustache
new file mode 100644
index 00000000000..f0c88fae473
--- /dev/null
+++ b/openapi/templates/csharp-netcore/IAsynchronousClient.mustache
@@ -0,0 +1,92 @@
+{{>partial_header}}
+
+using System;
+using System.Threading.Tasks;
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// Contract for Asynchronous RESTful API interactions.
+    ///
+    /// This interface allows consumers to provide a custom API accessor client.
+    /// </summary>
+    public interface IAsynchronousClient
+    {
+        /// <summary>
+        /// Executes a non-blocking call to some <paramref name="path"/> using the GET http verb.
+        /// </summary>
+        /// <param name="path">The relative path to invoke.</param>
+        /// <param name="options">The request parameters to pass along to the client.</param>
+        /// <param name="configuration">Per-request configurable settings.</param>
+        /// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
+        /// <typeparam name="T">The return type.</typeparam>
+        /// <returns>A task eventually representing the response data, decorated with <see cref="ApiResponse{T}"/></returns>
+        Task<ApiResponse<T>> GetAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+
+        /// <summary>
+        /// Executes a non-blocking call to some <paramref name="path"/> using the POST http verb.
+        /// </summary>
+        /// <param name="path">The relative path to invoke.</param>
+        /// <param name="options">The request parameters to pass along to the client.</param>
+        /// <param name="configuration">Per-request configurable settings.</param>
+        /// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
+        /// <typeparam name="T">The return type.</typeparam>
+        /// <returns>A task eventually representing the response data, decorated with <see cref="ApiResponse{T}"/></returns>
+        Task<ApiResponse<T>> PostAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+
+        /// <summary>
+        /// Executes a non-blocking call to some <paramref name="path"/> using the PUT http verb.
+        /// </summary>
+        /// <param name="path">The relative path to invoke.</param>
+        /// <param name="options">The request parameters to pass along to the client.</param>
+        /// <param name="configuration">Per-request configurable settings.</param>
+        /// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
+        /// <typeparam name="T">The return type.</typeparam>
+        /// <returns>A task eventually representing the response data, decorated with <see cref="ApiResponse{T}"/></returns>
+        Task<ApiResponse<T>> PutAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+
+        /// <summary>
+        /// Executes a non-blocking call to some <paramref name="path"/> using the DELETE http verb.
+        /// </summary>
+        /// <param name="path">The relative path to invoke.</param>
+        /// <param name="options">The request parameters to pass along to the client.</param>
+        /// <param name="configuration">Per-request configurable settings.</param>
+        /// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
+        /// <typeparam name="T">The return type.</typeparam>
+        /// <returns>A task eventually representing the response data, decorated with <see cref="ApiResponse{T}"/></returns>
+        Task<ApiResponse<T>> DeleteAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+
+        /// <summary>
+        /// Executes a non-blocking call to some <paramref name="path"/> using the HEAD http verb.
+        /// </summary>
+        /// <param name="path">The relative path to invoke.</param>
+        /// <param name="options">The request parameters to pass along to the client.</param>
+        /// <param name="configuration">Per-request configurable settings.</param>
+        /// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
+        /// <typeparam name="T">The return type.</typeparam>
+        /// <returns>A task eventually representing the response data, decorated with <see cref="ApiResponse{T}"/></returns>
+        Task<ApiResponse<T>> HeadAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+
+        /// <summary>
+        /// Executes a non-blocking call to some <paramref name="path"/> using the OPTIONS http verb.
+        /// </summary>
+        /// <param name="path">The relative path to invoke.</param>
+        /// <param name="options">The request parameters to pass along to the client.</param>
+        /// <param name="configuration">Per-request configurable settings.</param>
+        /// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
+        /// <typeparam name="T">The return type.</typeparam>
+        /// <returns>A task eventually representing the response data, decorated with <see cref="ApiResponse{T}"/></returns>
+        Task<ApiResponse<T>> OptionsAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+
+        /// <summary>
+        /// Executes a non-blocking call to some <paramref name="path"/> using the PATCH http verb.
+        /// </summary>
+        /// <param name="path">The relative path to invoke.</param>
+        /// <param name="options">The request parameters to pass along to the client.</param>
+        /// <param name="configuration">Per-request configurable settings.</param>
+        /// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
+        /// <typeparam name="T">The return type.</typeparam>
+        /// <returns>A task eventually representing the response data, decorated with <see cref="ApiResponse{T}"/></returns>
+        Task<ApiResponse<T>> PatchAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+    }
+}
diff --git a/openapi/templates/csharp-netcore/IReadableConfiguration.mustache b/openapi/templates/csharp-netcore/IReadableConfiguration.mustache
new file mode 100644
index 00000000000..bf142ffbac1
--- /dev/null
+++ b/openapi/templates/csharp-netcore/IReadableConfiguration.mustache
@@ -0,0 +1,114 @@
+{{>partial_header}}
+
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Security.Cryptography.X509Certificates;
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// Represents a readable-only configuration contract.
+    /// </summary>
+    public interface IReadableConfiguration
+    {
+        /// <summary>
+        /// Gets the access token.
+        /// </summary>
+        /// <value>Access token.</value>
+        string AccessToken { get; }
+
+        /// <summary>
+        /// Gets the API key.
+        /// </summary>
+        /// <value>API key.</value>
+        IDictionary<string, string> ApiKey { get; }
+
+        /// <summary>
+        /// Gets the API key prefix.
+        /// </summary>
+        /// <value>API key prefix.</value>
+        IDictionary<string, string> ApiKeyPrefix { get; }
+
+        /// <summary>
+        /// Gets the base path.
+        /// </summary>
+        /// <value>Base path.</value>
+        string BasePath { get; }
+
+        /// <summary>
+        /// Gets the date time format.
+        /// </summary>
+        /// <value>Date time format.</value>
+        string DateTimeFormat { get; }
+
+        /// <summary>
+        /// Gets the default header.
+        /// </summary>
+        /// <value>Default header.</value>
+        [Obsolete("Use DefaultHeaders instead.")]
+        IDictionary<string, string> DefaultHeader { get; }
+
+        /// <summary>
+        /// Gets the default headers.
+        /// </summary>
+        /// <value>Default headers.</value>
+        IDictionary<string, string> DefaultHeaders { get; }
+
+        /// <summary>
+        /// Gets the temp folder path.
+        /// </summary>
+        /// <value>Temp folder path.</value>
+        string TempFolderPath { get; }
+
+        /// <summary>
+        /// Gets the HTTP connection timeout (in milliseconds)
+        /// </summary>
+        /// <value>HTTP connection timeout.</value>
+        int Timeout { get; }
+
+        /// <summary>
+        /// Gets the proxy.
+        /// </summary>
+        /// <value>Proxy.</value>
+        WebProxy Proxy { get; }
+
+        /// <summary>
+        /// Gets the user agent.
+        /// </summary>
+        /// <value>User agent.</value>
+        string UserAgent { get; }
+
+        /// <summary>
+        /// Gets the username.
+        /// </summary>
+        /// <value>Username.</value>
+        string Username { get; }
+
+        /// <summary>
+        /// Gets the password.
+        /// </summary>
+        /// <value>Password.</value>
+        string Password { get; }
+
+        /// <summary>
+        /// Gets the API key with prefix.
+        /// </summary>
+        /// <param name="apiKeyIdentifier">API key identifier (authentication scheme).</param>
+        /// <returns>API key with prefix.</returns>
+        string GetApiKeyWithPrefix(string apiKeyIdentifier);
+
+        /// <summary>
+        /// Gets certificate collection to be sent with requests.
+        /// </summary>
+        /// <value>X509 Certificate collection.</value>
+        X509CertificateCollection ClientCertificates { get; }
+        {{#hasHttpSignatureMethods}}
+
+        /// <summary>
+        /// Gets the HttpSigning configuration
+        /// </summary>
+        HttpSigningConfiguration HttpSigningConfiguration { get; }
+        {{/hasHttpSignatureMethods}}
+    }
+}
diff --git a/openapi/templates/csharp-netcore/ISynchronousClient.mustache b/openapi/templates/csharp-netcore/ISynchronousClient.mustache
new file mode 100644
index 00000000000..c09bfbfed66
--- /dev/null
+++ b/openapi/templates/csharp-netcore/ISynchronousClient.mustache
@@ -0,0 +1,85 @@
+{{>partial_header}}
+
+using System;
+using System.IO;
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// Contract for Synchronous RESTful API interactions.
+    ///
+    /// This interface allows consumers to provide a custom API accessor client.
+    /// </summary>
+    public interface ISynchronousClient
+    {
+        /// <summary>
+        /// Executes a blocking call to some <paramref name="path"/> using the GET http verb.
+        /// </summary>
+        /// <param name="path">The relative path to invoke.</param>
+        /// <param name="options">The request parameters to pass along to the client.</param>
+        /// <param name="configuration">Per-request configurable settings.</param>
+        /// <typeparam name="T">The return type.</typeparam>
+        /// <returns>The response data, decorated with <see cref="ApiResponse{T}"/></returns>
+        ApiResponse<T> Get<T>(string path, RequestOptions options, IReadableConfiguration configuration = null);
+
+        /// <summary>
+        /// Executes a blocking call to some <paramref name="path"/> using the POST http verb.
+        /// </summary>
+        /// <param name="path">The relative path to invoke.</param>
+        /// <param name="options">The request parameters to pass along to the client.</param>
+        /// <param name="configuration">Per-request configurable settings.</param>
+        /// <typeparam name="T">The return type.</typeparam>
+        /// <returns>The response data, decorated with <see cref="ApiResponse{T}"/></returns>
+        ApiResponse<T> Post<T>(string path, RequestOptions options, IReadableConfiguration configuration = null);
+
+        /// <summary>
+        /// Executes a blocking call to some <paramref name="path"/> using the PUT http verb.
+        /// </summary>
+        /// <param name="path">The relative path to invoke.</param>
+        /// <param name="options">The request parameters to pass along to the client.</param>
+        /// <param name="configuration">Per-request configurable settings.</param>
+        /// <typeparam name="T">The return type.</typeparam>
+        /// <returns>The response data, decorated with <see cref="ApiResponse{T}"/></returns>
+        ApiResponse<T> Put<T>(string path, RequestOptions options, IReadableConfiguration configuration = null);
+
+        /// <summary>
+        /// Executes a blocking call to some <paramref name="path"/> using the DELETE http verb.
+        /// </summary>
+        /// <param name="path">The relative path to invoke.</param>
+        /// <param name="options">The request parameters to pass along to the client.</param>
+        /// <param name="configuration">Per-request configurable settings.</param>
+        /// <typeparam name="T">The return type.</typeparam>
+        /// <returns>The response data, decorated with <see cref="ApiResponse{T}"/></returns>
+        ApiResponse<T> Delete<T>(string path, RequestOptions options, IReadableConfiguration configuration = null);
+
+        /// <summary>
+        /// Executes a blocking call to some <paramref name="path"/> using the HEAD http verb.
+        /// </summary>
+        /// <param name="path">The relative path to invoke.</param>
+        /// <param name="options">The request parameters to pass along to the client.</param>
+        /// <param name="configuration">Per-request configurable settings.</param>
+        /// <typeparam name="T">The return type.</typeparam>
+        /// <returns>The response data, decorated with <see cref="ApiResponse{T}"/></returns>
+        ApiResponse<T> Head<T>(string path, RequestOptions options, IReadableConfiguration configuration = null);
+
+        /// <summary>
+        /// Executes a blocking call to some <paramref name="path"/> using the OPTIONS http verb.
+        /// </summary>
+        /// <param name="path">The relative path to invoke.</param>
+        /// <param name="options">The request parameters to pass along to the client.</param>
+        /// <param name="configuration">Per-request configurable settings.</param>
+        /// <typeparam name="T">The return type.</typeparam>
+        /// <returns>The response data, decorated with <see cref="ApiResponse{T}"/></returns>
+        ApiResponse<T> Options<T>(string path, RequestOptions options, IReadableConfiguration configuration = null);
+
+        /// <summary>
+        /// Executes a blocking call to some <paramref name="path"/> using the PATCH http verb.
+        /// </summary>
+        /// <param name="path">The relative path to invoke.</param>
+        /// <param name="options">The request parameters to pass along to the client.</param>
+        /// <param name="configuration">Per-request configurable settings.</param>
+        /// <typeparam name="T">The return type.</typeparam>
+        /// <returns>The response data, decorated with <see cref="ApiResponse{T}"/></returns>
+        ApiResponse<T> Patch<T>(string path, RequestOptions options, IReadableConfiguration configuration = null);
+    }
+}
diff --git a/openapi/templates/csharp-netcore/JsonSubTypesTests.mustache b/openapi/templates/csharp-netcore/JsonSubTypesTests.mustache
new file mode 100644
index 00000000000..55b1d51837d
--- /dev/null
+++ b/openapi/templates/csharp-netcore/JsonSubTypesTests.mustache
@@ -0,0 +1,125 @@
+{{>partial_header}}
+
+using System.Collections.Generic;
+using System.Linq;
+using JsonSubTypes;
+using Newtonsoft.Json;
+using NUnit.Framework;
+
+using {{packageName}}.{{apiPackage}};
+using {{packageName}}.{{modelPackage}};
+using {{packageName}}.Client;
+
+namespace {{packageName}}.Test.Client
+{
+    public class JsonSubTypesTests
+    {
+        [Test]
+        public void TestSimpleJsonSubTypesExample()
+        {
+            var animal =
+                JsonConvert.DeserializeObject<IAnimal>("{\"Kind\":\"Dog\",\"Breed\":\"Jack Russell Terrier\"}");
+            Assert.AreEqual("Jack Russell Terrier", (animal as Dog)?.Breed);
+        }
+
+        [Test]
+        public void DeserializeObjectWithCustomMapping()
+        {
+            var animal =
+                JsonConvert.DeserializeObject<Animal2>("{\"Sound\":\"Bark\",\"Breed\":\"Jack Russell Terrier\"}");
+            Assert.AreEqual("Jack Russell Terrier", (animal as Dog2)?.Breed);
+        }
+
+        [Test]
+        public void DeserializeObjectMappingByPropertyPresence()
+        {
+            string json =
+                "[{\"Department\":\"Department1\",\"JobTitle\":\"JobTitle1\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}," +
+                "{\"Department\":\"Department1\",\"JobTitle\":\"JobTitle1\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}," +
+                "{\"Skill\":\"Painter\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}]";
+
+
+            var persons = JsonConvert.DeserializeObject<ICollection<Person>>(json);
+            Assert.AreEqual("Painter", (persons.Last() as Artist)?.Skill);
+        }
+    }
+
+    [JsonConverter(typeof(JsonSubtypes), "Kind")]
+    public interface IAnimal
+    {
+        string Kind { get; }
+    }
+
+    public class Dog : IAnimal
+    {
+        public Dog()
+        {
+            Kind = "Dog";
+        }
+
+        public string Kind { get; }
+        public string Breed { get; set; }
+    }
+
+    class Cat : IAnimal
+    {
+        public Cat()
+        {
+            Kind = "Cat";
+        }
+
+        public string Kind { get; }
+        bool Declawed { get; set; }
+    }
+
+    [JsonConverter(typeof(JsonSubtypes), "Sound")]
+    [JsonSubtypes.KnownSubType(typeof(Dog2), "Bark")]
+    [JsonSubtypes.KnownSubType(typeof(Cat2), "Meow")]
+    public class Animal2
+    {
+        public virtual string Sound { get; }
+        public string Color { get; set; }
+    }
+
+    public class Dog2 : Animal2
+    {
+        public Dog2()
+        {
+            Sound = "Bark";
+        }
+
+        public override string Sound { get; }
+        public string Breed { get; set; }
+    }
+
+    public class Cat2 : Animal2
+    {
+        public Cat2()
+        {
+            Sound = "Meow";
+        }
+
+        public override string Sound { get; }
+        public bool Declawed { get; set; }
+    }
+
+    [JsonConverter(typeof(JsonSubtypes))]
+    [JsonSubtypes.KnownSubTypeWithProperty(typeof(Employee), "JobTitle")]
+    [JsonSubtypes.KnownSubTypeWithProperty(typeof(Artist), "Skill")]
+    public class Person
+    {
+        public string FirstName { get; set; }
+        public string LastName { get; set; }
+    }
+
+    public class Employee : Person
+    {
+        public string Department { get; set; }
+        public string JobTitle { get; set; }
+    }
+
+    public class Artist : Person
+    {
+        public string Skill { get; set; }
+    }
+}
diff --git a/openapi/templates/csharp-netcore/Multimap.mustache b/openapi/templates/csharp-netcore/Multimap.mustache
new file mode 100644
index 00000000000..8624af00924
--- /dev/null
+++ b/openapi/templates/csharp-netcore/Multimap.mustache
@@ -0,0 +1,287 @@
+{{>partial_header}}
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// A dictionary in which one key has many associated values.
+    /// </summary>
+    /// <typeparam name="TKey">The type of the key</typeparam>
+    /// <typeparam name="TValue">The type of the value associated with the key.</typeparam>
+    public class Multimap<TKey, TValue> : IDictionary<TKey, IList<TValue>>
+    {
+        #region Private Fields
+
+        private readonly Dictionary<TKey, IList<TValue>> _dictionary;
+
+        #endregion Private Fields
+
+        #region Constructors
+
+        /// <summary>
+        /// Empty Constructor.
+        /// </summary>
+        public Multimap()
+        {
+            _dictionary = new Dictionary<TKey, IList<TValue>>();
+        }
+
+        /// <summary>
+        /// Constructor with comparer.
+        /// </summary>
+        /// <param name="comparer"></param>
+        public Multimap(IEqualityComparer<TKey> comparer)
+        {
+            _dictionary = new Dictionary<TKey, IList<TValue>>(comparer);
+        }
+
+        #endregion Constructors
+
+        #region Enumerators
+
+        /// <summary>
+        /// To get the enumerator.
+        /// </summary>
+        /// <returns>Enumerator</returns>
+        public IEnumerator<KeyValuePair<TKey, IList<TValue>>> GetEnumerator()
+        {
+            return _dictionary.GetEnumerator();
+        }
+
+        /// <summary>
+        /// To get the enumerator.
+        /// </summary>
+        /// <returns>Enumerator</returns>
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return _dictionary.GetEnumerator();
+        }
+
+        #endregion Enumerators
+
+        #region Public Members
+        /// <summary>
+        /// Add values to Multimap
+        /// </summary>
+        /// <param name="item">Key value pair</param>
+        public void Add(KeyValuePair<TKey, IList<TValue>> item)
+        {
+            if (!TryAdd(item.Key, item.Value))
+                throw new InvalidOperationException("Could not add values to Multimap.");
+        }
+
+        /// <summary>
+        /// Add Multimap to Multimap
+        /// </summary>
+        /// <param name="multimap">Multimap</param>
+        public void Add(Multimap<TKey, TValue> multimap)
+        {
+            foreach (var item in multimap)
+            {
+                if (!TryAdd(item.Key, item.Value))
+                    throw new InvalidOperationException("Could not add values to Multimap.");
+            }
+        }
+
+        /// <summary>
+        /// Clear Multimap
+        /// </summary>
+        public void Clear()
+        {
+            _dictionary.Clear();
+        }
+
+        /// <summary>
+        /// Determines whether Multimap contains the specified item.
+        /// </summary>
+        /// <param name="item">Key value pair</param>
+        /// <exception cref="NotImplementedException">Method needs to be implemented</exception>
+        /// <returns>true if the Multimap contains the item; otherwise, false.</returns>
+        public bool Contains(KeyValuePair<TKey, IList<TValue>> item)
+        {
+            throw new NotImplementedException();
+        }
+
+        /// <summary>
+        ///  Copy items of the Multimap to an array,
+        ///     starting at a particular array index.
+        /// </summary>
+        /// <param name="array">The array that is the destination of the items copied
+        ///     from Multimap. The array must have zero-based indexing.</param>
+        /// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
+        /// <exception cref="NotImplementedException">Method needs to be implemented</exception>
+        public void CopyTo(KeyValuePair<TKey, IList<TValue>>[] array, int arrayIndex)
+        {
+            throw new NotImplementedException();
+        }
+
+        /// <summary>
+        /// Removes the specified item from the Multimap.
+        /// </summary>
+        /// <param name="item">Key value pair</param>
+        /// <returns>true if the item is successfully removed; otherwise, false.</returns>
+        /// <exception cref="NotImplementedException">Method needs to be implemented</exception>
+        public bool Remove(KeyValuePair<TKey, IList<TValue>> item)
+        {
+            throw new NotImplementedException();
+        }
+
+        /// <summary>
+        /// Gets the number of items contained in the Multimap.
+        /// </summary>
+        public int Count => _dictionary.Count;
+
+        /// <summary>
+        /// Gets a value indicating whether the Multimap is read-only.
+        /// </summary>
+        public bool IsReadOnly => false;
+
+        /// <summary>
+        /// Adds an item with the provided key and value to the Multimap.
+        /// </summary>
+        /// <param name="key">The object to use as the key of the item to add.</param>
+        /// <param name="value">The object to use as the value of the item to add.</param>
+        /// <exception cref="InvalidOperationException">Thrown when couldn't add the value to Multimap.</exception>
+        public void Add(TKey key, IList<TValue> value)
+        {
+            if (value != null && value.Count > 0)
+            {
+                if (_dictionary.TryGetValue(key, out var list))
+                {
+                    foreach (var k in value) list.Add(k);
+                }
+                else
+                {
+                    list = new List<TValue>(value);
+                    if (!TryAdd(key, list))
+                        throw new InvalidOperationException("Could not add values to Multimap.");
+                }
+            }
+        }
+
+        /// <summary>
+        /// Determines whether the Multimap contains an item with the specified key.
+        /// </summary>
+        /// <param name="key">The key to locate in the Multimap.</param>
+        /// <returns>true if the Multimap contains an item with
+        ///     the key; otherwise, false.</returns>
+        public bool ContainsKey(TKey key)
+        {
+            return _dictionary.ContainsKey(key);
+        }
+
+        /// <summary>
+        /// Removes item with the specified key from the Multimap.
+        /// </summary>
+        /// <param name="key">The key to locate in the Multimap.</param>
+        /// <returns>true if the item is successfully removed; otherwise, false.</returns>
+        public bool Remove(TKey key)
+        {
+            return TryRemove(key, out var _);
+        }
+
+        /// <summary>
+        /// Gets the value associated with the specified key.
+        /// </summary>
+        /// <param name="key">The key whose value to get.</param>
+        /// <param name="value">When this method returns, the value associated with the specified key, if the
+        ///     key is found; otherwise, the default value for the type of the value parameter.
+        ///     This parameter is passed uninitialized.</param>
+        /// <returns> true if the object that implements Multimap contains
+        ///     an item with the specified key; otherwise, false.</returns>
+        public bool TryGetValue(TKey key, out IList<TValue> value)
+        {
+            return _dictionary.TryGetValue(key, out value);
+        }
+
+        /// <summary>
+        /// Gets or sets the item with the specified key.
+        /// </summary>
+        /// <param name="key">The key of the item to get or set.</param>
+        /// <returns>The value of the specified key.</returns>
+        public IList<TValue> this[TKey key]
+        {
+            get => _dictionary[key];
+            set => _dictionary[key] = value;
+        }
+
+        /// <summary>
+        /// Gets a System.Collections.Generic.ICollection containing the keys of the Multimap.
+        /// </summary>
+        public ICollection<TKey> Keys => _dictionary.Keys;
+
+        /// <summary>
+        /// Gets a System.Collections.Generic.ICollection containing the values of the Multimap.
+        /// </summary>
+        public ICollection<IList<TValue>> Values => _dictionary.Values;
+
+        /// <summary>
+        ///  Copy the items of the Multimap to an System.Array,
+        ///     starting at a particular System.Array index.
+        /// </summary>
+        /// <param name="array">The one-dimensional System.Array that is the destination of the items copied
+        ///     from Multimap. The System.Array must have zero-based indexing.</param>
+        /// <param name="index">The zero-based index in array at which copying begins.</param>
+        public void CopyTo(Array array, int index)
+        {
+            ((ICollection)_dictionary).CopyTo(array, index);
+        }
+
+        /// <summary>
+        /// Adds an item with the provided key and value to the Multimap.
+        /// </summary>
+        /// <param name="key">The object to use as the key of the item to add.</param>
+        /// <param name="value">The object to use as the value of the item to add.</param>
+        /// <exception cref="InvalidOperationException">Thrown when couldn't add value to Multimap.</exception>
+        public void Add(TKey key, TValue value)
+        {
+            if (value != null)
+            {
+                if (_dictionary.TryGetValue(key, out var list))
+                {
+                    list.Add(value);
+                }
+                else
+                {
+                    list = new List<TValue> { value };
+                    if (!TryAdd(key, list))
+                        throw new InvalidOperationException("Could not add value to Multimap.");
+                }
+            }
+        }
+
+        #endregion Public Members
+
+        #region Private Members
+
+        /**
+         * Helper method to encapsulate generator differences between dictionary types.
+         */
+        private bool TryRemove(TKey key, out IList<TValue> value)
+        {
+            _dictionary.TryGetValue(key, out value);
+            return _dictionary.Remove(key);
+        }
+
+        /**
+         * Helper method to encapsulate generator differences between dictionary types.
+         */
+        private bool TryAdd(TKey key, IList<TValue> value)
+        {
+            try
+            {
+                _dictionary.Add(key, value);
+            }
+            catch (ArgumentException)
+            {
+                return false;
+            }
+
+            return true;
+        }
+        #endregion Private Members
+    }
+}
diff --git a/openapi/templates/csharp-netcore/OpenAPIDateConverter.mustache b/openapi/templates/csharp-netcore/OpenAPIDateConverter.mustache
new file mode 100644
index 00000000000..d7905102576
--- /dev/null
+++ b/openapi/templates/csharp-netcore/OpenAPIDateConverter.mustache
@@ -0,0 +1,21 @@
+{{>partial_header}}
+using Newtonsoft.Json.Converters;
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// Formatter for 'date' openapi formats ss defined by full-date - RFC3339
+    /// see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#data-types
+    /// </summary>
+    public class OpenAPIDateConverter : IsoDateTimeConverter
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="OpenAPIDateConverter" /> class.
+        /// </summary>
+        public OpenAPIDateConverter()
+        {
+            // full-date   = date-fullyear "-" date-month "-" date-mday
+            DateTimeFormat = "yyyy-MM-dd";
+        }
+    }
+}
diff --git a/openapi/templates/csharp-netcore/README.mustache b/openapi/templates/csharp-netcore/README.mustache
new file mode 100644
index 00000000000..fd872051c3c
--- /dev/null
+++ b/openapi/templates/csharp-netcore/README.mustache
@@ -0,0 +1,270 @@
+# {{packageName}} - the C# library for the {{appName}}
+
+{{#appDescriptionWithNewLines}}
+{{{.}}}
+{{/appDescriptionWithNewLines}}
+
+This C# SDK is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
+
+- API version: {{appVersion}}
+- SDK version: {{packageVersion}}
+{{^hideGenerationTimestamp}}
+- Build date: {{generatedDate}}
+{{/hideGenerationTimestamp}}
+- Build package: {{generatorClass}}
+{{#infoUrl}}
+    For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}})
+{{/infoUrl}}
+
+<a name="frameworks-supported"></a>
+## Frameworks supported
+{{#netStandard}}
+- .NET Core >=1.0
+- .NET Framework >=4.6
+- Mono/Xamarin >=vNext
+{{/netStandard}}
+
+<a name="dependencies"></a>
+## Dependencies
+
+{{#useRestSharp}}
+- [RestSharp](https://www.nuget.org/packages/RestSharp) - 106.11.7 or later
+{{/useRestSharp}}
+- [Json.NET](https://www.nuget.org/packages/Newtonsoft.Json/) - 12.0.3 or later
+- [JsonSubTypes](https://www.nuget.org/packages/JsonSubTypes/) - 1.8.0 or later
+{{#useCompareNetObjects}}
+- [CompareNETObjects](https://www.nuget.org/packages/CompareNETObjects) - 4.61.0 or later
+{{/useCompareNetObjects}}
+{{#validatable}}
+- [System.ComponentModel.Annotations](https://www.nuget.org/packages/System.ComponentModel.Annotations) - 5.0.0 or later
+{{/validatable}}
+
+The DLLs included in the package may not be the latest version. We recommend using [NuGet](https://docs.nuget.org/consume/installing-nuget) to obtain the latest version of the packages:
+```
+{{#useRestSharp}}
+Install-Package RestSharp
+{{/useRestSharp}}
+Install-Package Newtonsoft.Json
+Install-Package JsonSubTypes
+{{#validatable}}
+Install-Package System.ComponentModel.Annotations
+{{/validatable}}
+{{#useCompareNetObjects}}
+Install-Package CompareNETObjects
+{{/useCompareNetObjects}}
+```
+{{#useRestSharp}}
+
+NOTE: RestSharp versions greater than 105.1.0 have a bug which causes file uploads to fail. See [RestSharp#742](https://github.com/restsharp/RestSharp/issues/742).
+NOTE: RestSharp for .Net Core creates a new socket for each api call, which can lead to a socket exhaustion problem. See [RestSharp#1406](https://github.com/restsharp/RestSharp/issues/1406).
+
+{{/useRestSharp}}
+<a name="installation"></a>
+## Installation
+{{#netStandard}}
+Generate the DLL using your preferred tool (e.g. `dotnet build`)
+{{/netStandard}}
+{{^netStandard}}
+Run the following command to generate the DLL
+- [Mac/Linux] `/bin/sh build.sh`
+- [Windows] `build.bat`
+{{/netStandard}}
+
+Then include the DLL (under the `bin` folder) in the C# project, and use the namespaces:
+```csharp
+using {{packageName}}.{{apiPackage}};
+using {{packageName}}.Client;
+using {{packageName}}.{{modelPackage}};
+```
+{{^netStandard}}
+<a name="packaging"></a>
+## Packaging
+
+A `.nuspec` is included with the project. You can follow the Nuget quickstart to [create](https://docs.microsoft.com/en-us/nuget/quickstart/create-and-publish-a-package#create-the-package) and [publish](https://docs.microsoft.com/en-us/nuget/quickstart/create-and-publish-a-package#publish-the-package) packages.
+
+This `.nuspec` uses placeholders from the `.csproj`, so build the `.csproj` directly:
+
+```
+nuget pack -Build -OutputDirectory out {{packageName}}.csproj
+```
+
+Then, publish to a [local feed](https://docs.microsoft.com/en-us/nuget/hosting-packages/local-feeds) or [other host](https://docs.microsoft.com/en-us/nuget/hosting-packages/overview) and consume the new package via Nuget as usual.
+
+{{/netStandard}}
+<a name="usage"></a>
+## Usage
+
+To use the API client with a HTTP proxy, setup a `System.Net.WebProxy`
+```csharp
+Configuration c = new Configuration();
+System.Net.WebProxy webProxy = new System.Net.WebProxy("http://myProxyUrl:80/");
+webProxy.Credentials = System.Net.CredentialCache.DefaultCredentials;
+c.Proxy = webProxy;
+```
+{{#useHttpClient}}
+
+### Connections
+Each ApiClass (properly the ApiClient inside it) will create an instance of HttpClient. It will use that for the entire lifecycle and dispose it when called the Dispose method.
+
+To better manager the connections it's a common practice to reuse the HttpClient and HttpClientHandler (see [here](https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#issues-with-the-original-httpclient-class-available-in-net) for details). To use your own HttpClient instance just pass it to the ApiClass constructor.
+
+```csharp
+HttpClientHandler yourHandler = new HttpClientHandler();
+HttpClient yourHttpClient = new HttpClient(yourHandler);
+var api = new YourApiClass(yourHttpClient, yourHandler);
+```
+
+If you want to use an HttpClient and don't have access to the handler, for example in a DI context in Asp.net Core when using IHttpClientFactory.
+
+```csharp
+HttpClient yourHttpClient = new HttpClient();
+var api = new YourApiClass(yourHttpClient);
+```
+You'll loose some configuration settings, the features affected are: Setting and Retrieving Cookies, Client Certificates, Proxy settings. You need to either manually handle those in your setup of the HttpClient or they won't be available.
+
+Here an example of DI setup in a sample web project:
+
+```csharp
+services.AddHttpClient<YourApiClass>(httpClient =>
+   new PetApi(httpClient));
+```
+
+{{/useHttpClient}}
+
+<a name="getting-started"></a>
+## Getting Started
+
+```csharp
+using System.Collections.Generic;
+using System.Diagnostics;
+{{#useHttpClient}}
+using System.Net.Http;
+{{/useHttpClient}}
+using {{packageName}}.{{apiPackage}};
+using {{packageName}}.Client;
+using {{packageName}}.{{modelPackage}};
+
+namespace Example
+{
+    public class {{operationId}}Example
+    {
+        public static void Main()
+        {
+{{#apiInfo}}{{#apis}}{{#-first}}{{#operations}}{{#operation}}{{#-first}}
+            Configuration config = new Configuration();
+            config.BasePath = "{{{basePath}}}";
+            {{#hasAuthMethods}}
+            {{#authMethods}}
+            {{#isBasicBasic}}
+            // Configure HTTP basic authorization: {{{name}}}
+            config.Username = "YOUR_USERNAME";
+            config.Password = "YOUR_PASSWORD";
+            {{/isBasicBasic}}
+            {{#isBasicBearer}}
+            // Configure Bearer token for authorization: {{{name}}}
+            config.AccessToken = "YOUR_BEARER_TOKEN";
+            {{/isBasicBearer}}
+            {{#isApiKey}}
+            // Configure API key authorization: {{{name}}}
+            config.ApiKey.Add("{{{keyParamName}}}", "YOUR_API_KEY");
+            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed
+            // config.ApiKeyPrefix.Add("{{{keyParamName}}}", "Bearer");
+            {{/isApiKey}}
+            {{#isOAuth}}
+            // Configure OAuth2 access token for authorization: {{{name}}}
+            config.AccessToken = "YOUR_ACCESS_TOKEN";
+            {{/isOAuth}}
+            {{/authMethods}}
+
+            {{/hasAuthMethods}}
+            {{#useHttpClient}}
+            // create instances of HttpClient, HttpClientHandler to be reused later with different Api classes
+            HttpClient httpClient = new HttpClient();
+            HttpClientHandler httpClientHandler = new HttpClientHandler();
+            var apiInstance = new {{classname}}(httpClient, config, httpClientHandler);
+            {{/useHttpClient}}
+            {{^useHttpClient}}
+            var apiInstance = new {{classname}}(config);
+            {{/useHttpClient}}
+            {{#allParams}}
+            {{#isPrimitiveType}}
+            var {{paramName}} = {{{example}}};  // {{{dataType}}} | {{{description}}}{{^required}} (optional) {{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}
+            {{/isPrimitiveType}}
+            {{^isPrimitiveType}}
+            var {{paramName}} = new {{{dataType}}}(); // {{{dataType}}} | {{{description}}}{{^required}} (optional) {{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}
+            {{/isPrimitiveType}}
+            {{/allParams}}
+
+            try
+            {
+                {{#summary}}
+                // {{{.}}}
+                {{/summary}}
+                {{#returnType}}{{{.}}} result = {{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}});{{#returnType}}
+                Debug.WriteLine(result);{{/returnType}}
+            }
+            catch (ApiException e)
+            {
+                Debug.Print("Exception when calling {{classname}}.{{operationId}}: " + e.Message );
+                Debug.Print("Status Code: "+ e.ErrorCode);
+                Debug.Print(e.StackTrace);
+            }
+{{/-first}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}}
+        }
+    }
+}
+```
+
+<a name="documentation-for-api-endpoints"></a>
+## Documentation for API Endpoints
+
+All URIs are relative to *{{{basePath}}}*
+
+Class | Method | HTTP request | Description
+------------ | ------------- | ------------- | -------------
+{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{{summary}}}
+{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
+
+<a name="documentation-for-models"></a>
+## Documentation for Models
+
+{{#modelPackage}}
+{{#models}}{{#model}} - [{{{modelPackage}}}.{{{classname}}}]({{modelDocPath}}{{{classname}}}.md)
+{{/model}}{{/models}}
+{{/modelPackage}}
+{{^modelPackage}}
+No model defined in this package
+{{/modelPackage}}
+
+<a name="documentation-for-authorization"></a>
+## Documentation for Authorization
+
+{{^authMethods}}
+All endpoints do not require authorization.
+{{/authMethods}}
+{{#authMethods}}
+{{#last}}
+Authentication schemes defined for the API:
+{{/last}}
+{{/authMethods}}
+{{#authMethods}}
+<a name="{{name}}"></a>
+### {{name}}
+
+{{#isApiKey}}- **Type**: API key
+- **API key parameter name**: {{keyParamName}}
+- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}}
+{{/isApiKey}}
+{{#isBasicBasic}}- **Type**: HTTP basic authentication
+{{/isBasicBasic}}
+{{#isBasicBearer}}- **Type**: Bearer Authentication
+{{/isBasicBearer}}
+{{#isOAuth}}- **Type**: OAuth
+- **Flow**: {{flow}}
+- **Authorization URL**: {{authorizationUrl}}
+- **Scopes**: {{^scopes}}N/A{{/scopes}}
+{{#scopes}}  - {{scope}}: {{description}}
+{{/scopes}}
+{{/isOAuth}}
+
+{{/authMethods}}
diff --git a/openapi/templates/csharp-netcore/ReadOnlyDictionary.mustache b/openapi/templates/csharp-netcore/ReadOnlyDictionary.mustache
new file mode 100644
index 00000000000..1299b2436be
--- /dev/null
+++ b/openapi/templates/csharp-netcore/ReadOnlyDictionary.mustache
@@ -0,0 +1,137 @@
+{{>partial_header}}
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace {{packageName}}.Client
+{
+    public class ReadOnlyDictionary<T, K> : IDictionary<T, K>
+    {
+        private IDictionary<T, K> _dictionaryImplementation;
+        public IEnumerator<KeyValuePair<T, K>> GetEnumerator()
+        {
+            return _dictionaryImplementation.GetEnumerator();
+        }
+
+        public ReadOnlyDictionary()
+        {
+            _dictionaryImplementation = new Dictionary<T, K>();
+        }
+
+        public ReadOnlyDictionary(IDictionary<T, K> dictionaryImplementation)
+        {
+            if (dictionaryImplementation == null) throw new ArgumentNullException("dictionaryImplementation");
+            _dictionaryImplementation = dictionaryImplementation;
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return ((IEnumerable) _dictionaryImplementation).GetEnumerator();
+        }
+
+        public void Add(KeyValuePair<T, K> item)
+        {
+            throw new ReadonlyOperationException("This instance is readonly.");
+        }
+
+        public void Clear()
+        {
+            throw new ReadonlyOperationException("This instance is readonly.");
+        }
+
+        public bool Contains(KeyValuePair<T, K> item)
+        {
+            return _dictionaryImplementation.Contains(item);
+        }
+
+        public void CopyTo(KeyValuePair<T, K>[] array, int arrayIndex)
+        {
+            _dictionaryImplementation.CopyTo(array, arrayIndex);
+        }
+
+        public bool Remove(KeyValuePair<T, K> item)
+        {
+            throw new ReadonlyOperationException("This instance is readonly.");
+        }
+
+        public int Count
+        {
+            get { return _dictionaryImplementation.Count; }
+        }
+
+        public bool IsReadOnly
+        {
+            get { return true; }
+        }
+
+        public void Add(T key, K value)
+        {
+            throw new ReadonlyOperationException("This instance is readonly.");
+        }
+
+        public bool ContainsKey(T key)
+        {
+            return _dictionaryImplementation.ContainsKey(key);
+        }
+
+        public bool Remove(T key)
+        {
+            throw new ReadonlyOperationException("This instance is readonly.");
+        }
+
+        public bool TryGetValue(T key, out K value)
+        {
+            return _dictionaryImplementation.TryGetValue(key, out value);
+        }
+
+        public K this[T key]
+        {
+            get { return _dictionaryImplementation[key]; }
+            set
+            {
+                throw new ReadonlyOperationException("This instance is readonly.");
+
+            }
+        }
+
+        public ICollection<T> Keys
+        {
+            get { return _dictionaryImplementation.Keys; }
+        }
+
+        public ICollection<K> Values
+        {
+            get { return _dictionaryImplementation.Values; }
+        }
+    }
+
+    [Serializable]
+    public class ReadonlyOperationException : Exception
+    {
+        //
+        // For guidelines regarding the creation of new exception types, see
+        //    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp
+        // and
+        //    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp
+        //
+
+        public ReadonlyOperationException()
+        {
+        }
+
+        public ReadonlyOperationException(string message) : base(message)
+        {
+        }
+
+        public ReadonlyOperationException(string message, Exception inner) : base(message, inner)
+        {
+        }
+
+        protected ReadonlyOperationException(
+            SerializationInfo info,
+            StreamingContext context) : base(info, context)
+        {
+        }
+    }
+}
diff --git a/openapi/templates/csharp-netcore/RequestOptions.mustache b/openapi/templates/csharp-netcore/RequestOptions.mustache
new file mode 100644
index 00000000000..dc924c733c1
--- /dev/null
+++ b/openapi/templates/csharp-netcore/RequestOptions.mustache
@@ -0,0 +1,66 @@
+{{>partial_header}}
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// A container for generalized request inputs. This type allows consumers to extend the request functionality
+    /// by abstracting away from the default (built-in) request framework (e.g. RestSharp).
+    /// </summary>
+    public class RequestOptions
+    {
+        /// <summary>
+        /// Parameters to be bound to path parts of the Request's URL
+        /// </summary>
+        public Dictionary<string, string> PathParameters { get; set; }
+
+        /// <summary>
+        /// Query parameters to be applied to the request.
+        /// Keys may have 1 or more values associated.
+        /// </summary>
+        public Multimap<string, string> QueryParameters { get; set; }
+
+        /// <summary>
+        /// Header parameters to be applied to to the request.
+        /// Keys may have 1 or more values associated.
+        /// </summary>
+        public Multimap<string, string> HeaderParameters { get; set; }
+
+        /// <summary>
+        /// Form parameters to be sent along with the request.
+        /// </summary>
+        public Dictionary<string, string> FormParameters { get; set; }
+
+        /// <summary>
+        /// File parameters to be sent along with the request.
+        /// </summary>
+        public Dictionary<string, Stream> FileParameters { get; set; }
+
+        /// <summary>
+        /// Cookies to be sent along with the request.
+        /// </summary>
+        public List<Cookie> Cookies { get; set; }
+
+        /// <summary>
+        /// Any data associated with a request body.
+        /// </summary>
+        public Object Data { get; set; }
+
+        /// <summary>
+        /// Constructs a new instance of <see cref="RequestOptions"/>
+        /// </summary>
+        public RequestOptions()
+        {
+            PathParameters = new Dictionary<string, string>();
+            QueryParameters = new Multimap<string, string>();
+            HeaderParameters = new Multimap<string, string>();
+            FormParameters = new Dictionary<string, string>();
+            FileParameters = new Dictionary<string, Stream>();
+            Cookies = new List<Cookie>();
+        }
+    }
+}
diff --git a/openapi/templates/csharp-netcore/RetryConfiguration.mustache b/openapi/templates/csharp-netcore/RetryConfiguration.mustache
new file mode 100644
index 00000000000..29aa6092681
--- /dev/null
+++ b/openapi/templates/csharp-netcore/RetryConfiguration.mustache
@@ -0,0 +1,39 @@
+using Polly;
+{{#useRestSharp}}
+using RestSharp;
+{{/useRestSharp}}
+{{#useHttpClient}}
+using System.Net.Http;
+{{/useHttpClient}}
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// Configuration class to set the polly retry policies to be applied to the requests.
+    /// </summary>
+    public class RetryConfiguration
+    {
+{{#useRestSharp}}
+        /// <summary>
+        /// Retry policy
+        /// </summary>
+        public static Policy<IRestResponse> RetryPolicy { get; set; }
+
+        /// <summary>
+        /// Async retry policy
+        /// </summary>
+        public static AsyncPolicy<IRestResponse> AsyncRetryPolicy { get; set; }
+{{/useRestSharp}}
+{{#useHttpClient}}
+        /// <summary>
+        /// Retry policy
+        /// </summary>
+        public static Policy<HttpResponseMessage> RetryPolicy { get; set; }
+
+        /// <summary>
+        /// Async retry policy
+        /// </summary>
+        public static AsyncPolicy<HttpResponseMessage> AsyncRetryPolicy { get; set; }
+{{/useHttpClient}}
+    }
+}
diff --git a/openapi/templates/csharp-netcore/Solution.mustache b/openapi/templates/csharp-netcore/Solution.mustache
new file mode 100644
index 00000000000..112cc3dc405
--- /dev/null
+++ b/openapi/templates/csharp-netcore/Solution.mustache
@@ -0,0 +1,27 @@
+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
+{{^excludeTests}}Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "{{testPackageName}}", "src\{{testPackageName}}\{{testPackageName}}.csproj", "{19F1DEBC-DE5E-4517-8062-F000CD499087}"
+EndProject
+{{/excludeTests}}Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{{packageGuid}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{{packageGuid}}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{{packageGuid}}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{{packageGuid}}.Release|Any CPU.Build.0 = Release|Any CPU
+		{19F1DEBC-DE5E-4517-8062-F000CD499087}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{19F1DEBC-DE5E-4517-8062-F000CD499087}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{19F1DEBC-DE5E-4517-8062-F000CD499087}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{19F1DEBC-DE5E-4517-8062-F000CD499087}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
\ No newline at end of file
diff --git a/openapi/templates/csharp-netcore/TestProject.mustache b/openapi/templates/csharp-netcore/TestProject.mustache
new file mode 100644
index 00000000000..57e1455087f
--- /dev/null
+++ b/openapi/templates/csharp-netcore/TestProject.mustache
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+{{#appName}}
+{{{.}}}
+
+{{/appName}}
+{{#appDescription}}
+{{{.}}}
+
+{{/appDescription}}
+{{#version}}The version of the OpenAPI document: {{{.}}}{{/version}}
+{{#infoEmail}}Contact: {{{.}}}{{/infoEmail}}
+-->
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>{{testPackageName}}</RootNamespace>
+    <AssemblyName>{{testPackageName}}</AssemblyName>
+    <TargetFramework{{#multiTarget}}s{{/multiTarget}}>{{testTargetFramework}}</TargetFramework{{#multiTarget}}s{{/multiTarget}}>
+    <IsPackable>false</IsPackable>
+    <FileAlignment>512</FileAlignment>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.2" />
+    <PackageReference Include="xunit" Version="2.4.1" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\{{packageName}}\{{packageName}}.csproj">
+      <Project>{{packageGuid}}</Project>
+      <Name>{{packageName}}</Name>
+    </ProjectReference>
+  </ItemGroup>
+</Project>
diff --git a/openapi/templates/csharp-netcore/WebRequestPathBuilder.mustache b/openapi/templates/csharp-netcore/WebRequestPathBuilder.mustache
new file mode 100644
index 00000000000..30e5181eeac
--- /dev/null
+++ b/openapi/templates/csharp-netcore/WebRequestPathBuilder.mustache
@@ -0,0 +1,45 @@
+{{>partial_header}}
+using System.Collections.Generic;
+using System.Web;
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// A URI builder
+    /// </summary>
+    class WebRequestPathBuilder
+    {
+            private string _baseUrl;
+            private string _path;
+            private string _query = "?";
+            public WebRequestPathBuilder(string baseUrl, string path)
+            {
+                _baseUrl = baseUrl;
+                _path = path;
+            }
+
+            public void AddPathParameters(Dictionary<string, string> parameters)
+            {
+                foreach (var parameter in parameters)
+                {
+                    _path = _path.Replace("{" + parameter.Key + "}", HttpUtility.UrlEncode(parameter.Value));
+                }
+            }
+
+            public void AddQueryParameters(Multimap<string, string> parameters)
+            {
+                foreach (var parameter in parameters)
+                {
+                    foreach (var value in parameter.Value)
+                    {
+                        _query = _query + parameter.Key + "=" + HttpUtility.UrlEncode(value) + "&";
+                    }
+                }
+            }
+
+            public string GetFullUri()
+            {
+                return _baseUrl + _path + _query.Substring(0, _query.Length - 1);
+            }
+    }
+}
diff --git a/openapi/templates/csharp-netcore/api.mustache b/openapi/templates/csharp-netcore/api.mustache
new file mode 100644
index 00000000000..237a14f92b7
--- /dev/null
+++ b/openapi/templates/csharp-netcore/api.mustache
@@ -0,0 +1,650 @@
+{{>partial_header}}
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Net;
+using System.Net.Mime;
+using {{packageName}}.Client;
+{{#hasImport}}using {{packageName}}.{{modelPackage}};
+{{/hasImport}}
+
+namespace {{packageName}}.{{apiPackage}}
+{
+    {{#operations}}
+
+    /// <summary>
+    /// Represents a collection of functions to interact with the API endpoints
+    /// </summary>
+    {{>visibility}} interface {{interfacePrefix}}{{classname}}Sync : IApiAccessor
+    {
+        #region Synchronous Operations
+        {{#operation}}
+        /// <summary>
+        /// {{summary}}
+        /// </summary>
+        {{#notes}}
+        /// <remarks>
+        /// {{.}}
+        /// </remarks>
+        {{/notes}}
+        /// <exception cref="{{packageName}}.Client.ApiException">Thrown when fails to make API call</exception>
+        {{#allParams}}/// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}</param>
+        {{/allParams}}/// <returns>{{returnType}}</returns>
+        {{#isDeprecated}}
+        [Obsolete]
+        {{/isDeprecated}}
+        {{{returnType}}}{{^returnType}}void{{/returnType}} {{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}});
+
+        /// <summary>
+        /// {{summary}}
+        /// </summary>
+        /// <remarks>
+        /// {{notes}}
+        /// </remarks>
+        /// <exception cref="{{packageName}}.Client.ApiException">Thrown when fails to make API call</exception>
+        {{#allParams}}/// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}</param>
+        {{/allParams}}/// <returns>ApiResponse of {{returnType}}{{^returnType}}Object(void){{/returnType}}</returns>
+        {{#isDeprecated}}
+        [Obsolete]
+        {{/isDeprecated}}
+        ApiResponse<{{{returnType}}}{{^returnType}}Object{{/returnType}}> {{operationId}}WithHttpInfo({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}});
+        {{/operation}}
+        #endregion Synchronous Operations
+    }
+
+    {{#supportsAsync}}
+    /// <summary>
+    /// Represents a collection of functions to interact with the API endpoints
+    /// </summary>
+    {{>visibility}} interface {{interfacePrefix}}{{classname}}Async : IApiAccessor
+    {
+        #region Asynchronous Operations
+        {{#operation}}
+        /// <summary>
+        /// {{summary}}
+        /// </summary>
+        /// <remarks>
+        /// {{notes}}
+        /// </remarks>
+        /// <exception cref="{{packageName}}.Client.ApiException">Thrown when fails to make API call</exception>
+        {{#allParams}}
+        /// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}</param>
+        {{/allParams}}
+        /// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
+        /// <returns>Task of {{returnType}}{{^returnType}}void{{/returnType}}</returns>
+        {{#isDeprecated}}
+        [Obsolete]
+        {{/isDeprecated}}
+        {{#returnType}}System.Threading.Tasks.Task<{{{.}}}>{{/returnType}}{{^returnType}}System.Threading.Tasks.Task{{/returnType}} {{operationId}}Async({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+
+        /// <summary>
+        /// {{summary}}
+        /// </summary>
+        /// <remarks>
+        /// {{notes}}
+        /// </remarks>
+        /// <exception cref="{{packageName}}.Client.ApiException">Thrown when fails to make API call</exception>
+        {{#allParams}}
+        /// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}</param>
+        {{/allParams}}
+        /// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
+        /// <returns>Task of ApiResponse{{#returnType}} ({{.}}){{/returnType}}</returns>
+        {{#isDeprecated}}
+        [Obsolete]
+        {{/isDeprecated}}
+        System.Threading.Tasks.Task<ApiResponse<{{{returnType}}}{{^returnType}}Object{{/returnType}}>> {{operationId}}WithHttpInfoAsync({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+        {{/operation}}
+        #endregion Asynchronous Operations
+    }
+    {{/supportsAsync}}
+
+    /// <summary>
+    /// Represents a collection of functions to interact with the API endpoints
+    /// </summary>
+    {{>visibility}} interface {{interfacePrefix}}{{classname}} : {{interfacePrefix}}{{classname}}Sync{{#supportsAsync}}, {{interfacePrefix}}{{classname}}Async{{/supportsAsync}}
+    {
+
+    }
+
+    /// <summary>
+    /// Represents a collection of functions to interact with the API endpoints
+    /// </summary>
+    {{>visibility}} partial class {{classname}} : {{interfacePrefix}}{{classname}}
+    {
+        private {{packageName}}.Client.ExceptionFactory _exceptionFactory = (name, response) => null;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="{{classname}}"/> class.
+        /// </summary>
+        /// <returns></returns>
+        public {{classname}}() : this((string)null)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="{{classname}}"/> class.
+        /// </summary>
+        /// <returns></returns>
+        public {{classname}}(string basePath)
+        {
+            this.Configuration = {{packageName}}.Client.Configuration.MergeConfigurations(
+                {{packageName}}.Client.GlobalConfiguration.Instance,
+                new {{packageName}}.Client.Configuration { BasePath = basePath }
+            );
+            this.Client = new {{packageName}}.Client.ApiClient(this.Configuration.BasePath);
+            {{#supportsAsync}}
+            this.AsynchronousClient = new {{packageName}}.Client.ApiClient(this.Configuration.BasePath);
+            {{/supportsAsync}}
+            this.ExceptionFactory = {{packageName}}.Client.Configuration.DefaultExceptionFactory;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="{{classname}}"/> class
+        /// using Configuration object
+        /// </summary>
+        /// <param name="configuration">An instance of Configuration</param>
+        /// <returns></returns>
+        public {{classname}}({{packageName}}.Client.Configuration configuration)
+        {
+            if (configuration == null) throw new ArgumentNullException("configuration");
+
+            this.Configuration = {{packageName}}.Client.Configuration.MergeConfigurations(
+                {{packageName}}.Client.GlobalConfiguration.Instance,
+                configuration
+            );
+            this.Client = new {{packageName}}.Client.ApiClient(this.Configuration.BasePath);
+            {{#supportsAsync}}
+            this.AsynchronousClient = new {{packageName}}.Client.ApiClient(this.Configuration.BasePath);
+            {{/supportsAsync}}
+            ExceptionFactory = {{packageName}}.Client.Configuration.DefaultExceptionFactory;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="{{classname}}"/> class
+        /// using a Configuration object and client instance.
+        /// </summary>
+        /// <param name="client">The client interface for synchronous API access.</param>{{#supportsAsync}}
+        /// <param name="asyncClient">The client interface for asynchronous API access.</param>{{/supportsAsync}}
+        /// <param name="configuration">The configuration object.</param>
+        public {{classname}}({{packageName}}.Client.ISynchronousClient client, {{#supportsAsync}}{{packageName}}.Client.IAsynchronousClient asyncClient, {{/supportsAsync}}{{packageName}}.Client.IReadableConfiguration configuration)
+        {
+            if (client == null) throw new ArgumentNullException("client");
+            {{#supportsAsync}}
+            if (asyncClient == null) throw new ArgumentNullException("asyncClient");
+            {{/supportsAsync}}
+            if (configuration == null) throw new ArgumentNullException("configuration");
+
+            this.Client = client;
+            {{#supportsAsync}}
+            this.AsynchronousClient = asyncClient;
+            {{/supportsAsync}}
+            this.Configuration = configuration;
+            this.ExceptionFactory = {{packageName}}.Client.Configuration.DefaultExceptionFactory;
+        }
+
+        {{#supportsAsync}}
+        /// <summary>
+        /// The client for accessing this underlying API asynchronously.
+        /// </summary>
+        public {{packageName}}.Client.IAsynchronousClient AsynchronousClient { get; set; }
+        {{/supportsAsync}}
+
+        /// <summary>
+        /// The client for accessing this underlying API synchronously.
+        /// </summary>
+        public {{packageName}}.Client.ISynchronousClient Client { get; set; }
+
+        /// <summary>
+        /// Gets the base path of the API client.
+        /// </summary>
+        /// <value>The base path</value>
+        public string GetBasePath()
+        {
+            return this.Configuration.BasePath;
+        }
+
+        /// <summary>
+        /// Gets or sets the configuration object
+        /// </summary>
+        /// <value>An instance of the Configuration</value>
+        public {{packageName}}.Client.IReadableConfiguration Configuration { get; set; }
+
+        /// <summary>
+        /// Provides a factory method hook for the creation of exceptions.
+        /// </summary>
+        public {{packageName}}.Client.ExceptionFactory ExceptionFactory
+        {
+            get
+            {
+                if (_exceptionFactory != null && _exceptionFactory.GetInvocationList().Length > 1)
+                {
+                    throw new InvalidOperationException("Multicast delegate for ExceptionFactory is unsupported.");
+                }
+                return _exceptionFactory;
+            }
+            set { _exceptionFactory = value; }
+        }
+
+        {{#operation}}
+        /// <summary>
+        /// {{summary}} {{notes}}
+        /// </summary>
+        /// <exception cref="{{packageName}}.Client.ApiException">Thrown when fails to make API call</exception>
+        {{#allParams}}/// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}</param>
+        {{/allParams}}/// <returns>{{returnType}}</returns>
+        {{#isDeprecated}}
+        [Obsolete]
+        {{/isDeprecated}}
+        public {{{returnType}}}{{^returnType}}void{{/returnType}} {{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}})
+        {
+            {{#returnType}}{{packageName}}.Client.ApiResponse<{{{returnType}}}> localVarResponse = {{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}});
+            return localVarResponse.Data;{{/returnType}}{{^returnType}}{{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}});{{/returnType}}
+        }
+
+        /// <summary>
+        /// {{summary}} {{notes}}
+        /// </summary>
+        /// <exception cref="{{packageName}}.Client.ApiException">Thrown when fails to make API call</exception>
+        {{#allParams}}/// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}</param>
+        {{/allParams}}/// <returns>ApiResponse of {{returnType}}{{^returnType}}Object(void){{/returnType}}</returns>
+        {{#isDeprecated}}
+        [Obsolete]
+        {{/isDeprecated}}
+        public {{packageName}}.Client.ApiResponse<{{{returnType}}}{{^returnType}}Object{{/returnType}}> {{operationId}}WithHttpInfo({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}})
+        {
+            {{#allParams}}
+            {{#required}}
+            {{^vendorExtensions.x-csharp-value-type}}
+            // verify the required parameter '{{paramName}}' is set
+            if ({{paramName}} == null)
+                throw new {{packageName}}.Client.ApiException(400, "Missing required parameter '{{paramName}}' when calling {{classname}}->{{operationId}}");
+
+            {{/vendorExtensions.x-csharp-value-type}}
+            {{/required}}
+            {{/allParams}}
+            {{packageName}}.Client.RequestOptions localVarRequestOptions = new {{packageName}}.Client.RequestOptions();
+
+            string[] _contentTypes = new string[] {
+                {{#consumes}}
+                "{{{mediaType}}}"{{^-last}},{{/-last}}
+                {{/consumes}}
+            };
+
+            // to determine the Accept header
+            string[] _accepts = new string[] {
+                {{#produces}}
+                "{{{mediaType}}}"{{^-last}},{{/-last}}
+                {{/produces}}
+            };
+
+            var localVarContentType = {{packageName}}.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
+            if (localVarContentType != null) localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
+
+            var localVarAccept = {{packageName}}.Client.ClientUtils.SelectHeaderAccept(_accepts);
+            if (localVarAccept != null) localVarRequestOptions.HeaderParameters.Add("Accept", localVarAccept);
+
+            {{#pathParams}}
+            {{#required}}
+            localVarRequestOptions.PathParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // path parameter
+            {{/required}}
+            {{^required}}
+            if ({{paramName}} != null)
+            {
+                localVarRequestOptions.PathParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // path parameter
+            }
+            {{/required}}
+            {{/pathParams}}
+            {{#queryParams}}
+            {{#required}}
+            {{#isDeepObject}}
+            {{#items.vars}}
+            localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{baseName}}", {{paramName}}.{{name}}));
+            {{/items.vars}}
+            {{^items}}
+            localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("deepObject", "{{baseName}}", {{paramName}}));
+            {{/items}}
+            {{/isDeepObject}}
+            {{^isDeepObject}}
+            localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{baseName}}", {{paramName}}));
+            {{/isDeepObject}}
+            {{/required}}
+            {{^required}}
+            if ({{paramName}} != null)
+            {
+                {{#isDeepObject}}
+                    {{#items.vars}}
+                if ({{paramName}}.{{name}} != null)
+                {
+                    localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{baseName}}", {{paramName}}.{{name}}));
+                }
+                    {{/items.vars}}
+                {{^items}}
+                localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("deepObject", "{{baseName}}", {{paramName}}));
+                {{/items}}
+                {{/isDeepObject}}
+                {{^isDeepObject}}
+                localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{baseName}}", {{paramName}}));
+                {{/isDeepObject}}
+            }
+            {{/required}}
+            {{/queryParams}}
+            {{#headerParams}}
+            {{#required}}
+            localVarRequestOptions.HeaderParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // header parameter
+            {{/required}}
+            {{^required}}
+            if ({{paramName}} != null)
+            {
+                localVarRequestOptions.HeaderParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // header parameter
+            }
+            {{/required}}
+            {{/headerParams}}
+            {{#formParams}}
+            {{#required}}
+            {{#isFile}}
+            localVarRequestOptions.FileParameters.Add("{{baseName}}", {{paramName}});
+            {{/isFile}}
+            {{^isFile}}
+            localVarRequestOptions.FormParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // form parameter
+            {{/isFile}}
+            {{/required}}
+            {{^required}}
+            if ({{paramName}} != null)
+            {
+                {{#isFile}}
+                localVarRequestOptions.FileParameters.Add("{{baseName}}", {{paramName}});
+                {{/isFile}}
+                {{^isFile}}
+                localVarRequestOptions.FormParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // form parameter
+                {{/isFile}}
+            }
+            {{/required}}
+            {{/formParams}}
+            {{#bodyParam}}
+            localVarRequestOptions.Data = {{paramName}};
+            {{/bodyParam}}
+
+            {{#authMethods}}
+            // authentication ({{name}}) required
+            {{#isApiKey}}
+            {{#isKeyInCookie}}
+            // cookie parameter support
+            if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
+            {
+                localVarRequestOptions.Cookies.Add(new Cookie("{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")));
+            }
+            {{/isKeyInCookie}}
+            {{#isKeyInHeader}}
+            if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
+            {
+                localVarRequestOptions.HeaderParameters.Add("{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}"));
+            }
+            {{/isKeyInHeader}}
+            {{#isKeyInQuery}}
+            if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
+            {
+                localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("", "{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")));
+            }
+            {{/isKeyInQuery}}
+            {{/isApiKey}}
+            {{#isBasicBasic}}
+            // http basic authentication required
+            if (!string.IsNullOrEmpty(this.Configuration.Username) || !string.IsNullOrEmpty(this.Configuration.Password))
+            {
+                localVarRequestOptions.HeaderParameters.Add("Authorization", "Basic " + {{packageName}}.Client.ClientUtils.Base64Encode(this.Configuration.Username + ":" + this.Configuration.Password));
+            }
+            {{/isBasicBasic}}
+            {{#isBasicBearer}}
+            // bearer authentication required
+            if (!string.IsNullOrEmpty(this.Configuration.AccessToken))
+            {
+                localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken);
+            }
+            {{/isBasicBearer}}
+            {{#isOAuth}}
+            // oauth required
+            if (!string.IsNullOrEmpty(this.Configuration.AccessToken))
+            {
+                localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken);
+            }
+            {{/isOAuth}}
+            {{#isHttpSignature}}
+            if (this.Configuration.HttpSigningConfiguration != null)
+            {
+                var HttpSigningHeaders = this.Configuration.HttpSigningConfiguration.GetHttpSignedHeader(this.Configuration.BasePath, "{{{httpMethod}}}", "{{{path}}}", localVarRequestOptions);
+                foreach (var headerItem in HttpSigningHeaders)
+                {
+                    if (localVarRequestOptions.HeaderParameters.ContainsKey(headerItem.Key))
+                    {
+                        localVarRequestOptions.HeaderParameters[headerItem.Key] = new List<string>() { headerItem.Value };
+                    }
+                    else
+                    {
+                        localVarRequestOptions.HeaderParameters.Add(headerItem.Key, headerItem.Value);
+                    }
+                }
+            }
+            {{/isHttpSignature}}
+            {{/authMethods}}
+
+            // make the HTTP request
+            var localVarResponse = this.Client.{{#lambda.titlecase}}{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}{{/lambda.titlecase}}<{{{returnType}}}{{^returnType}}Object{{/returnType}}>("{{{path}}}", localVarRequestOptions, this.Configuration);
+
+            if (this.ExceptionFactory != null)
+            {
+                Exception _exception = this.ExceptionFactory("{{operationId}}", localVarResponse);
+                if (_exception != null) throw _exception;
+            }
+
+            return localVarResponse;
+        }
+
+        {{#supportsAsync}}
+        /// <summary>
+        /// {{summary}} {{notes}}
+        /// </summary>
+        /// <exception cref="{{packageName}}.Client.ApiException">Thrown when fails to make API call</exception>
+        {{#allParams}}
+        /// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}</param>
+        {{/allParams}}
+        /// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
+        /// <returns>Task of {{returnType}}{{^returnType}}void{{/returnType}}</returns>
+        {{#isDeprecated}}
+        [Obsolete]
+        {{/isDeprecated}}
+        {{#returnType}}public async System.Threading.Tasks.Task<{{{.}}}>{{/returnType}}{{^returnType}}public async System.Threading.Tasks.Task{{/returnType}} {{operationId}}Async({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            {{#returnType}}{{packageName}}.Client.ApiResponse<{{{returnType}}}> localVarResponse = await {{operationId}}WithHttpInfoAsync({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}cancellationToken).ConfigureAwait(false);
+            return localVarResponse.Data;{{/returnType}}{{^returnType}}await {{operationId}}WithHttpInfoAsync({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}cancellationToken).ConfigureAwait(false);{{/returnType}}
+        }
+
+        /// <summary>
+        /// {{summary}} {{notes}}
+        /// </summary>
+        /// <exception cref="{{packageName}}.Client.ApiException">Thrown when fails to make API call</exception>
+        {{#allParams}}
+        /// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}</param>
+        {{/allParams}}
+        /// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
+        /// <returns>Task of ApiResponse{{#returnType}} ({{.}}){{/returnType}}</returns>
+        {{#isDeprecated}}
+        [Obsolete]
+        {{/isDeprecated}}
+        public async System.Threading.Tasks.Task<{{packageName}}.Client.ApiResponse<{{{returnType}}}{{^returnType}}Object{{/returnType}}>> {{operationId}}WithHttpInfoAsync({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            {{#allParams}}
+            {{#required}}
+            {{^vendorExtensions.x-csharp-value-type}}
+            // verify the required parameter '{{paramName}}' is set
+            if ({{paramName}} == null)
+                throw new {{packageName}}.Client.ApiException(400, "Missing required parameter '{{paramName}}' when calling {{classname}}->{{operationId}}");
+
+            {{/vendorExtensions.x-csharp-value-type}}
+            {{/required}}
+            {{/allParams}}
+
+            {{packageName}}.Client.RequestOptions localVarRequestOptions = new {{packageName}}.Client.RequestOptions();
+
+            string[] _contentTypes = new string[] {
+                {{#consumes}}
+                "{{{mediaType}}}"{{^-last}}, {{/-last}}
+                {{/consumes}}
+            };
+
+            // to determine the Accept header
+            string[] _accepts = new string[] {
+                {{#produces}}
+                "{{{mediaType}}}"{{^-last}},{{/-last}}
+                {{/produces}}
+            };
+
+
+            var localVarContentType = {{packageName}}.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
+            if (localVarContentType != null) localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
+
+            var localVarAccept = {{packageName}}.Client.ClientUtils.SelectHeaderAccept(_accepts);
+            if (localVarAccept != null) localVarRequestOptions.HeaderParameters.Add("Accept", localVarAccept);
+
+            {{#pathParams}}
+            {{#required}}
+            localVarRequestOptions.PathParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // path parameter
+            {{/required}}
+            {{^required}}
+            if ({{paramName}} != null)
+            {
+                localVarRequestOptions.PathParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // path parameter
+            }
+            {{/required}}
+            {{/pathParams}}
+            {{#queryParams}}
+            {{#required}}
+            localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{baseName}}", {{paramName}}));
+            {{/required}}
+            {{^required}}
+            if ({{paramName}} != null)
+            {
+                localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{baseName}}", {{paramName}}));
+            }
+            {{/required}}
+            {{/queryParams}}
+            {{#headerParams}}
+            {{#required}}
+            localVarRequestOptions.HeaderParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // header parameter
+            {{/required}}
+            {{^required}}
+            if ({{paramName}} != null)
+            {
+                localVarRequestOptions.HeaderParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // header parameter
+            }
+            {{/required}}
+            {{/headerParams}}
+            {{#formParams}}
+            {{#required}}
+            {{#isFile}}
+            localVarRequestOptions.FileParameters.Add("{{baseName}}", {{paramName}});
+            {{/isFile}}
+            {{^isFile}}
+            localVarRequestOptions.FormParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // form parameter
+            {{/isFile}}
+            {{/required}}
+            {{^required}}
+            if ({{paramName}} != null)
+            {
+                {{#isFile}}
+                localVarRequestOptions.FileParameters.Add("{{baseName}}", {{paramName}});
+                {{/isFile}}
+                {{^isFile}}
+                localVarRequestOptions.FormParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // form parameter
+                {{/isFile}}
+            }
+            {{/required}}
+            {{/formParams}}
+            {{#bodyParam}}
+            localVarRequestOptions.Data = {{paramName}};
+            {{/bodyParam}}
+
+            {{#authMethods}}
+            // authentication ({{name}}) required
+            {{#isApiKey}}
+            {{#isKeyInCookie}}
+            // cookie parameter support
+            if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
+            {
+                localVarRequestOptions.Cookies.Add(new Cookie("{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")));
+            }
+            {{/isKeyInCookie}}
+            {{#isKeyInHeader}}
+            if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
+            {
+                localVarRequestOptions.HeaderParameters.Add("{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}"));
+            }
+            {{/isKeyInHeader}}
+            {{#isKeyInQuery}}
+            if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
+            {
+                localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("", "{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")));
+            }
+            {{/isKeyInQuery}}
+            {{/isApiKey}}
+            {{#isBasic}}
+            {{#isBasicBasic}}
+            // http basic authentication required
+            if (!string.IsNullOrEmpty(this.Configuration.Username) || !string.IsNullOrEmpty(this.Configuration.Password))
+            {
+                localVarRequestOptions.HeaderParameters.Add("Authorization", "Basic " + {{packageName}}.Client.ClientUtils.Base64Encode(this.Configuration.Username + ":" + this.Configuration.Password));
+            }
+            {{/isBasicBasic}}
+            {{#isBasicBearer}}
+            // bearer authentication required
+            if (!string.IsNullOrEmpty(this.Configuration.AccessToken))
+            {
+                localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken);
+            }
+            {{/isBasicBearer}}
+            {{/isBasic}}
+            {{#isOAuth}}
+            // oauth required
+            if (!string.IsNullOrEmpty(this.Configuration.AccessToken))
+            {
+                localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken);
+            }
+            {{/isOAuth}}
+            {{#isHttpSignature}}
+            if (this.Configuration.HttpSigningConfiguration != null)
+            {
+                var HttpSigningHeaders = this.Configuration.HttpSigningConfiguration.GetHttpSignedHeader(this.Configuration.BasePath, "{{{httpMethod}}}", "{{{path}}}", localVarRequestOptions);
+                foreach (var headerItem in HttpSigningHeaders)
+                {
+                    if (localVarRequestOptions.HeaderParameters.ContainsKey(headerItem.Key))
+                    {
+                        localVarRequestOptions.HeaderParameters[headerItem.Key] = new List<string>() { headerItem.Value };
+                    }
+                    else
+                    {
+                        localVarRequestOptions.HeaderParameters.Add(headerItem.Key, headerItem.Value);
+                    }
+                }
+            }
+            {{/isHttpSignature}}
+            {{/authMethods}}
+
+            // make the HTTP request
+
+            var localVarResponse = await this.AsynchronousClient.{{#lambda.titlecase}}{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}{{/lambda.titlecase}}Async<{{{returnType}}}{{^returnType}}Object{{/returnType}}>("{{{path}}}", localVarRequestOptions, this.Configuration, cancellationToken).ConfigureAwait(false);
+
+            if (this.ExceptionFactory != null)
+            {
+                Exception _exception = this.ExceptionFactory("{{operationId}}", localVarResponse);
+                if (_exception != null) throw _exception;
+            }
+
+            return localVarResponse;
+        }
+
+        {{/supportsAsync}}
+        {{/operation}}
+    }
+    {{/operations}}
+}
diff --git a/openapi/templates/csharp-netcore/api_doc.mustache b/openapi/templates/csharp-netcore/api_doc.mustache
new file mode 100644
index 00000000000..1286829ad40
--- /dev/null
+++ b/openapi/templates/csharp-netcore/api_doc.mustache
@@ -0,0 +1,134 @@
+# {{packageName}}.{{apiPackage}}.{{classname}}{{#description}}
+{{.}}{{/description}}
+
+All URIs are relative to *{{{basePath}}}*
+
+Method | HTTP request | Description
+------------- | ------------- | -------------
+{{#operations}}{{#operation}}[**{{operationId}}**]({{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{summary}}
+{{/operation}}{{/operations}}
+
+{{#operations}}
+{{#operation}}
+<a name="{{{operationIdLowerCase}}}"></a>
+# **{{{operationId}}}**
+> {{returnType}}{{^returnType}}void{{/returnType}} {{operationId}} ({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = null{{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}})
+
+{{{summary}}}{{#notes}}
+
+{{{.}}}{{/notes}}
+
+### Example
+```csharp
+using System.Collections.Generic;
+using System.Diagnostics;
+{{#useHttpClient}}
+using System.Net.Http;
+{{/useHttpClient}}
+using {{packageName}}.{{apiPackage}};
+using {{packageName}}.Client;
+using {{packageName}}.{{modelPackage}};
+
+namespace Example
+{
+    public class {{operationId}}Example
+    {
+        public static void Main()
+        {
+            Configuration config = new Configuration();
+            config.BasePath = "{{{basePath}}}";
+            {{#hasAuthMethods}}
+            {{#authMethods}}
+            {{#isBasicBasic}}
+            // Configure HTTP basic authorization: {{{name}}}
+            config.Username = "YOUR_USERNAME";
+            config.Password = "YOUR_PASSWORD";
+            {{/isBasicBasic}}
+            {{#isBasicBearer}}
+            // Configure Bearer token for authorization: {{{name}}}
+            config.AccessToken = "YOUR_BEARER_TOKEN";
+            {{/isBasicBearer}}
+            {{#isApiKey}}
+            // Configure API key authorization: {{{name}}}
+            config.AddApiKey("{{{keyParamName}}}", "YOUR_API_KEY");
+            // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed
+            // config.AddApiKeyPrefix("{{{keyParamName}}}", "Bearer");
+            {{/isApiKey}}
+            {{#isOAuth}}
+            // Configure OAuth2 access token for authorization: {{{name}}}
+            config.AccessToken = "YOUR_ACCESS_TOKEN";
+            {{/isOAuth}}
+            {{/authMethods}}
+
+            {{/hasAuthMethods}}
+            {{#useHttpClient}}
+            // create instances of HttpClient, HttpClientHandler to be reused later with different Api classes
+            HttpClient httpClient = new HttpClient();
+            HttpClientHandler httpClientHandler = new HttpClientHandler();
+            var apiInstance = new {{classname}}(httpClient, config, httpClientHandler);
+            {{/useHttpClient}}
+            {{^useHttpClient}}
+            var apiInstance = new {{classname}}(config);
+            {{/useHttpClient}}
+            {{#allParams}}
+            {{#isPrimitiveType}}
+            var {{paramName}} = {{{example}}};  // {{{dataType}}} | {{{description}}}{{^required}} (optional) {{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}
+            {{/isPrimitiveType}}
+            {{^isPrimitiveType}}
+            var {{paramName}} = new {{{dataType}}}(); // {{{dataType}}} | {{{description}}}{{^required}} (optional) {{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}
+            {{/isPrimitiveType}}
+            {{/allParams}}
+
+            try
+            {
+                {{#summary}}
+                // {{{.}}}
+                {{/summary}}
+                {{#returnType}}{{{.}}} result = {{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}});{{#returnType}}
+                Debug.WriteLine(result);{{/returnType}}
+            }
+            catch (ApiException  e)
+            {
+                Debug.Print("Exception when calling {{classname}}.{{operationId}}: " + e.Message );
+                Debug.Print("Status Code: "+ e.ErrorCode);
+                Debug.Print(e.StackTrace);
+            }
+        }
+    }
+}
+```
+
+### Parameters
+{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}}
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}}
+{{#allParams}} **{{paramName}}** | {{#isFile}}**{{dataType}}**{{/isFile}}{{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{^isFile}}[**{{dataType}}**]({{#isContainer}}{{baseType}}{{/isContainer}}{{^isContainer}}{{dataType}}{{/isContainer}}.md){{/isFile}}{{/isPrimitiveType}}| {{description}} | {{^required}}[optional] {{/required}}{{#defaultValue}}[default to {{.}}]{{/defaultValue}}
+{{/allParams}}
+
+### Return type
+
+{{#returnType}}{{#returnTypeIsPrimitive}}**{{{returnType}}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{returnType}}**]({{returnBaseType}}.md){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}void (empty response body){{/returnType}}
+
+### Authorization
+
+{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{{name}}}](../README.md#{{{name}}}){{^-last}}, {{/-last}}{{/authMethods}}
+
+### HTTP request headers
+
+ - **Content-Type**: {{#consumes}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/consumes}}{{^consumes}}Not defined{{/consumes}}
+ - **Accept**: {{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}{{^produces}}Not defined{{/produces}}
+
+{{#responses.0}}
+
+### HTTP response details
+| Status code | Description | Response headers |
+|-------------|-------------|------------------|
+{{#responses}}
+| **{{code}}** | {{message}} | {{#headers}} * {{baseName}} - {{description}} <br> {{/headers}}{{^headers.0}} - {{/headers.0}} |
+{{/responses}}
+{{/responses.0}}
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+{{/operation}}
+{{/operations}}
diff --git a/openapi/templates/csharp-netcore/appveyor.mustache b/openapi/templates/csharp-netcore/appveyor.mustache
new file mode 100644
index 00000000000..eb85fc2a85b
--- /dev/null
+++ b/openapi/templates/csharp-netcore/appveyor.mustache
@@ -0,0 +1,9 @@
+# auto-generated by OpenAPI Generator (https://github.com/OpenAPITools/openapi-generator)
+#
+image: Visual Studio 2019
+clone_depth: 1
+build_script:
+- dotnet build -c Release
+- dotnet test -c Release
+after_build:
+- dotnet pack .\src\{{{packageName}}}\{{{packageName}}}.csproj -o ../../output -c Release --no-build
diff --git a/openapi/templates/csharp-netcore/git_push.sh.mustache b/openapi/templates/csharp-netcore/git_push.sh.mustache
new file mode 100644
index 00000000000..305f9c115ce
--- /dev/null
+++ b/openapi/templates/csharp-netcore/git_push.sh.mustache
@@ -0,0 +1,57 @@
+#!/bin/sh
+# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
+#
+# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com"
+
+git_user_id=$1
+git_repo_id=$2
+release_note=$3
+git_host=$4
+
+if [ "$git_host" = "" ]; then
+    git_host="{{{gitHost}}}"
+    echo "[INFO] No command line input provided. Set \$git_host to $git_host"
+fi
+
+if [ "$git_user_id" = "" ]; then
+    git_user_id="{{{gitUserId}}}"
+    echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
+fi
+
+if [ "$git_repo_id" = "" ]; then
+    git_repo_id="{{{gitRepoId}}}"
+    echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
+fi
+
+if [ "$release_note" = "" ]; then
+    release_note="{{{releaseNote}}}"
+    echo "[INFO] No command line input provided. Set \$release_note to $release_note"
+fi
+
+# Initialize the local directory as a Git repository
+git init
+
+# Adds the files in the local repository and stages them for commit.
+git add .
+
+# Commits the tracked changes and prepares them to be pushed to a remote repository.
+git commit -m "$release_note"
+
+# Sets the new remote
+git_remote=`git remote`
+if [ "$git_remote" = "" ]; then # git remote not defined
+
+    if [ "$GIT_TOKEN" = "" ]; then
+        echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment."
+        git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git
+    else
+        git remote add origin https://${git_user_id}:${GIT_TOKEN}@${git_host}/${git_user_id}/${git_repo_id}.git
+    fi
+
+fi
+
+git pull origin master
+
+# Pushes (Forces) the changes in the local repository up to the remote repository
+echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git"
+git push origin master 2>&1 | grep -v 'To https'
diff --git a/openapi/templates/csharp-netcore/gitignore.mustache b/openapi/templates/csharp-netcore/gitignore.mustache
new file mode 100644
index 00000000000..1ee53850b84
--- /dev/null
+++ b/openapi/templates/csharp-netcore/gitignore.mustache
@@ -0,0 +1,362 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
diff --git a/openapi/templates/csharp-netcore/libraries/httpclient/ApiClient.mustache b/openapi/templates/csharp-netcore/libraries/httpclient/ApiClient.mustache
new file mode 100644
index 00000000000..c94d7aaeae6
--- /dev/null
+++ b/openapi/templates/csharp-netcore/libraries/httpclient/ApiClient.mustache
@@ -0,0 +1,739 @@
+{{>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;
+{{^netStandard}}
+using System.Web;
+{{/netStandard}}
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
+{{#useWebRequest}}
+using System.Net.Http;
+{{/useWebRequest}}
+using System.Net.Http;
+using System.Net.Http.Headers;
+{{#supportsRetry}}
+using Polly;
+{{/supportsRetry}}
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// To Serialize/Deserialize JSON using our custom logic, but only when ContentType is JSON.
+    /// </summary>
+    internal class CustomJsonCodec
+    {
+        private readonly IReadableConfiguration _configuration;
+        private static readonly string _contentType = "application/json";
+        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;
+        }
+
+        /// <summary>
+        /// Serialize the object into a JSON string.
+        /// </summary>
+        /// <param name="obj">Object to be serialized.</param>
+        /// <returns>A JSON string.</returns>
+        public string Serialize(object obj)
+        {
+            if (obj != null && obj is {{{packageName}}}.{{modelPackage}}.AbstractOpenAPISchema)
+            {
+                // the object to be serialized is an oneOf/anyOf schema
+                return (({{{packageName}}}.{{modelPackage}}.AbstractOpenAPISchema)obj).ToJson();
+            }
+            else
+            {
+                return JsonConvert.SerializeObject(obj, _serializerSettings);
+            }
+        }
+
+        public async Task<T> Deserialize<T>(HttpResponseMessage response)
+        {
+            var result = (T) await Deserialize(response, typeof(T));
+            return result;
+        }
+
+        /// <summary>
+        /// Deserialize the JSON string into a proper object.
+        /// </summary>
+        /// <param name="response">The HTTP response.</param>
+        /// <param name="type">Object type.</param>
+        /// <returns>Object representation of the JSON string.</returns>
+        internal async Task<object> Deserialize(HttpResponseMessage response, Type type)
+        {
+            IList<string> headers = response.Headers.Select(x => x.Key + "=" + x.Value).ToList();
+
+            if (type == typeof(byte[])) // return byte array
+            {
+                return await response.Content.ReadAsByteArrayAsync();
+            }
+
+            // TODO: ? if (type.IsAssignableFrom(typeof(Stream)))
+            if (type == typeof(Stream))
+            {
+                var bytes = await response.Content.ReadAsByteArrayAsync();
+                if (headers != null)
+                {
+                    var filePath = string.IsNullOrEmpty(_configuration.TempFolderPath)
+                        ? Path.GetTempPath()
+                        : _configuration.TempFolderPath;
+                    var regex = new Regex(@"Content-Disposition=.*filename=['""]?([^'""\s]+)['""]?$");
+                    foreach (var header in headers)
+                    {
+                        var match = regex.Match(header.ToString());
+                        if (match.Success)
+                        {
+                            string fileName = filePath + ClientUtils.SanitizeFilename(match.Groups[1].Value.Replace("\"", "").Replace("'", ""));
+                            File.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(await response.Content.ReadAsStringAsync(), null, System.Globalization.DateTimeStyles.RoundtripKind);
+            }
+
+            if (type == typeof(string) || type.Name.StartsWith("System.Nullable")) // return primitive type
+            {
+                return Convert.ChangeType(await response.Content.ReadAsStringAsync(), type);
+            }
+
+            // at this point, it must be a model (json)
+            try
+            {
+                return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync(), type, _serializerSettings);
+            }
+            catch (Exception e)
+            {
+                throw new ApiException(500, e.Message);
+            }
+        }
+
+        public string RootElement { get; set; }
+        public string Namespace { get; set; }
+        public string DateFormat { get; set; }
+
+        public string ContentType
+        {
+            get { return _contentType; }
+            set { throw new InvalidOperationException("Not allowed to set content type."); }
+        }
+    }
+    /// <summary>
+    /// Provides a default implementation of an Api client (both synchronous and asynchronous implementations),
+    /// encapsulating general REST accessor use cases.
+    /// </summary>
+    /// <remarks>
+    /// The Dispose method will manage the HttpClient lifecycle when not passed by constructor.
+    /// </remarks>
+    {{>visibility}} partial class ApiClient : IDisposable, ISynchronousClient{{#supportsAsync}}, IAsynchronousClient{{/supportsAsync}}
+    {
+        private readonly string _baseUrl;
+
+        private readonly HttpClientHandler _httpClientHandler;
+        private readonly HttpClient _httpClient;
+        private readonly bool _disposeClient;
+
+        /// <summary>
+        /// Specifies the settings on a <see cref="JsonSerializer" /> object.
+        /// These settings can be adjusted to accommodate custom serialization rules.
+        /// </summary>
+        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
+                }
+            }
+        };
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ApiClient" />, defaulting to the global configurations' base url.
+        /// **IMPORTANT** This will also create an instance of HttpClient, which is less than ideal.
+        /// It's better to reuse the <see href="https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#issues-with-the-original-httpclient-class-available-in-net">HttpClient and HttpClientHandler</see>.
+        /// </summary>
+        public ApiClient() :
+                 this({{packageName}}.Client.GlobalConfiguration.Instance.BasePath)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ApiClient" />.
+        /// **IMPORTANT** This will also create an instance of HttpClient, which is less than ideal.
+        /// It's better to reuse the <see href="https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#issues-with-the-original-httpclient-class-available-in-net">HttpClient and HttpClientHandler</see>.
+        /// </summary>
+        /// <param name="basePath">The target service's base path in URL format.</param>
+        /// <exception cref="ArgumentException"></exception>
+        public ApiClient(string basePath)
+        {
+            if (string.IsNullOrEmpty(basePath)) throw new ArgumentException("basePath cannot be empty");
+
+            _httpClientHandler = new HttpClientHandler();
+            _httpClient = new HttpClient(_httpClientHandler, true);
+            _disposeClient = true;
+            _baseUrl = basePath;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ApiClient" />, defaulting to the global configurations' base url.
+        /// </summary>
+        /// <param name="client">An instance of HttpClient.</param>
+        /// <param name="handler">An optional instance of HttpClientHandler that is used by HttpClient.</param>
+        /// <exception cref="ArgumentNullException"></exception>
+        /// <remarks>
+        /// Some configuration settings will not be applied without passing an HttpClientHandler.
+        /// The features affected are: Setting and Retrieving Cookies, Client Certificates, Proxy settings.
+        /// </remarks>
+        public ApiClient(HttpClient client, HttpClientHandler handler = null) :
+                 this(client, {{packageName}}.Client.GlobalConfiguration.Instance.BasePath, handler)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ApiClient" />.
+        /// </summary>
+        /// <param name="client">An instance of HttpClient.</param>
+        /// <param name="basePath">The target service's base path in URL format.</param>
+        /// <param name="handler">An optional instance of HttpClientHandler that is used by HttpClient.</param>
+        /// <exception cref="ArgumentNullException"></exception>
+        /// <exception cref="ArgumentException"></exception>
+        /// <remarks>
+        /// Some configuration settings will not be applied without passing an HttpClientHandler.
+        /// The features affected are: Setting and Retrieving Cookies, Client Certificates, Proxy settings.
+        /// </remarks>
+        public ApiClient(HttpClient client, string basePath, HttpClientHandler handler = null)
+        {
+            if (client == null) throw new ArgumentNullException("client cannot be null");
+            if (string.IsNullOrEmpty(basePath)) throw new ArgumentException("basePath cannot be empty");
+
+            _httpClientHandler = handler;
+            _httpClient = client;
+            _baseUrl = basePath;
+        }
+
+        /// <summary>
+        /// Disposes resources if they were created by us
+        /// </summary>
+        public void Dispose()
+        {
+            if(_disposeClient) {
+                _httpClient.Dispose();
+            }
+        }
+
+        /// Prepares multipart/form-data content
+        {{! TODO: Add handling of improper usage }}
+        HttpContent PrepareMultipartFormDataContent(RequestOptions options)
+        {
+            string boundary = "---------" + Guid.NewGuid().ToString().ToUpperInvariant();
+            var multipartContent = new MultipartFormDataContent(boundary);
+            foreach (var formParameter in options.FormParameters)
+            {
+                multipartContent.Add(new StringContent(formParameter.Value), formParameter.Key);
+            }
+
+            if (options.FileParameters != null && options.FileParameters.Count > 0)
+            {
+                foreach (var fileParam in options.FileParameters)
+                {
+                    var content = new StreamContent(fileParam.Value.Content);
+                    content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
+                    multipartContent.Add(content, fileParam.Key,
+                        fileParam.Value.Name);
+                }
+            }
+            return multipartContent;
+        }
+
+        /// <summary>
+        /// Provides all logic for constructing a new HttpRequestMessage.
+        /// At this point, all information for querying the service is known. Here, it is simply
+        /// mapped into the a HttpRequestMessage.
+        /// </summary>
+        /// <param name="method">The http verb.</param>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <returns>[private] A new HttpRequestMessage instance.</returns>
+        /// <exception cref="ArgumentNullException"></exception>
+        private HttpRequestMessage 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");
+
+            WebRequestPathBuilder builder = new WebRequestPathBuilder(_baseUrl, path);
+
+            builder.AddPathParameters(options.PathParameters);
+
+            builder.AddQueryParameters(options.QueryParameters);
+
+            HttpRequestMessage request = new HttpRequestMessage(method, builder.GetFullUri());
+
+            if (configuration.UserAgent != null)
+            {
+                request.Headers.TryAddWithoutValidation("User-Agent", configuration.UserAgent);
+            }
+
+            if (configuration.DefaultHeaders != null)
+            {
+                foreach (var headerParam in configuration.DefaultHeaders)
+                {
+                    request.Headers.Add(headerParam.Key, headerParam.Value);
+                }
+            }
+
+            if (options.HeaderParameters != null)
+            {
+                foreach (var headerParam in options.HeaderParameters)
+                {
+                    foreach (var value in headerParam.Value)
+                    {
+                        // Todo make content headers actually content headers
+                        request.Headers.TryAddWithoutValidation(headerParam.Key, value);
+                    }
+                }
+            }
+
+            List<Tuple<HttpContent, string, string>> contentList = new List<Tuple<HttpContent, string, string>>();
+
+            string contentType = null;
+            if (options.HeaderParameters != null && options.HeaderParameters.ContainsKey("Content-Type"))
+            {
+                var contentTypes = options.HeaderParameters["Content-Type"];
+                contentType = contentTypes.FirstOrDefault();
+            }
+
+            {{!// TODO Add error handling in case of improper usage}}
+            if (contentType == "multipart/form-data")
+            {
+                request.Content = PrepareMultipartFormDataContent(options);
+            }
+            else if (contentType == "application/x-www-form-urlencoded")
+            {
+                request.Content = new FormUrlEncodedContent(options.FormParameters);
+            }
+            else
+            {
+                if (options.Data != null)
+                {
+                    if (options.Data is FileParameter fp)
+                    {
+                        contentType = contentType ?? "application/octet-stream";
+
+                        var streamContent = new StreamContent(fp.Content);
+                        streamContent.Headers.ContentType = new MediaTypeHeaderValue(contentType);
+                        request.Content = streamContent;
+                    }
+                    else
+                    {
+                        var serializer = new CustomJsonCodec(SerializerSettings, configuration);
+                        request.Content = new StringContent(serializer.Serialize(options.Data), new UTF8Encoding(),
+                            "application/json");
+                    }
+                }
+            }
+
+
+
+            // TODO provide an alternative that allows cookies per request instead of per API client
+            if (options.Cookies != null && options.Cookies.Count > 0)
+            {
+                request.Properties["CookieContainer"] = options.Cookies;
+            }
+
+            return request;
+        }
+
+        partial void InterceptRequest(HttpRequestMessage req);
+        partial void InterceptResponse(HttpRequestMessage req, HttpResponseMessage response);
+
+        private async Task<ApiResponse<T>> ToApiResponse<T>(HttpResponseMessage response, object responseData, Uri uri)
+        {
+            T result = (T) responseData;
+            string rawContent = await response.Content.ReadAsStringAsync();
+
+            var transformed = new ApiResponse<T>(response.StatusCode, new Multimap<string, string>({{#caseInsensitiveResponseHeaders}}StringComparer.OrdinalIgnoreCase{{/caseInsensitiveResponseHeaders}}), result, rawContent)
+            {
+                ErrorText = response.ReasonPhrase,
+                Cookies = new List<Cookie>()
+            };
+
+            // process response headers, e.g. Access-Control-Allow-Methods
+            if (response.Headers != null)
+            {
+                foreach (var responseHeader in response.Headers)
+                {
+                    transformed.Headers.Add(responseHeader.Key, ClientUtils.ParameterToString(responseHeader.Value));
+                }
+            }
+
+            // process response content headers, e.g. Content-Type
+            if (response.Content.Headers != null)
+            {
+                foreach (var responseHeader in response.Content.Headers)
+                {
+                    transformed.Headers.Add(responseHeader.Key, ClientUtils.ParameterToString(responseHeader.Value));
+                }
+            }
+
+            if (_httpClientHandler != null && response != null)
+            {
+                try {
+                    foreach (Cookie cookie in _httpClientHandler.CookieContainer.GetCookies(uri))
+                    {
+                        transformed.Cookies.Add(cookie);
+                    }
+                }
+                catch (PlatformNotSupportedException) {}
+            }
+
+            return transformed;
+        }
+
+        private ApiResponse<T> Exec<T>(HttpRequestMessage req, IReadableConfiguration configuration)
+        {
+            return ExecAsync<T>(req, configuration).GetAwaiter().GetResult();
+        }
+
+        private async Task<ApiResponse<T>> ExecAsync<T>(HttpRequestMessage req,
+            IReadableConfiguration configuration,
+            System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            var deserializer = new CustomJsonCodec(SerializerSettings, configuration);
+
+            var finalToken = cancellationToken;
+
+            if (configuration.Timeout > 0)
+            {
+                var tokenSource = new CancellationTokenSource(configuration.Timeout);
+                finalToken = CancellationTokenSource.CreateLinkedTokenSource(finalToken, tokenSource.Token).Token;
+            }
+
+            if (configuration.Proxy != null)
+            {
+                if(_httpClientHandler == null) throw new InvalidOperationException("Configuration `Proxy` not supported when the client is explicitly created without an HttpClientHandler, use the proper constructor.");
+                _httpClientHandler.Proxy = configuration.Proxy;
+            }
+
+            if (configuration.ClientCertificates != null)
+            {
+                if(_httpClientHandler == null) throw new InvalidOperationException("Configuration `ClientCertificates` not supported when the client is explicitly created without an HttpClientHandler, use the proper constructor.");
+                _httpClientHandler.ClientCertificates.AddRange(configuration.ClientCertificates);
+            }
+
+            var cookieContainer = req.Properties.ContainsKey("CookieContainer") ? req.Properties["CookieContainer"] as List<Cookie> : null;
+
+            if (cookieContainer != null)
+            {
+                if(_httpClientHandler == null) throw new InvalidOperationException("Request property `CookieContainer` not supported when the client is explicitly created without an HttpClientHandler, use the proper constructor.");
+                foreach (var cookie in cookieContainer)
+                {
+                    _httpClientHandler.CookieContainer.Add(cookie);
+                }
+            }
+
+            InterceptRequest(req);
+
+            HttpResponseMessage response;
+{{#supportsRetry}}
+            if (RetryConfiguration.AsyncRetryPolicy != null)
+            {
+                var policy = RetryConfiguration.AsyncRetryPolicy;
+                var policyResult = await policy
+                    .ExecuteAndCaptureAsync(() => _httpClient.SendAsync(req, cancellationToken))
+                    .ConfigureAwait(false);
+                response = (policyResult.Outcome == OutcomeType.Successful) ?
+                    policyResult.Result : new HttpResponseMessage()
+                    {
+                        ReasonPhrase = policyResult.FinalException.ToString(),
+                        RequestMessage = req
+                    };
+            }
+            else
+            {
+{{/supportsRetry}}
+                response = await _httpClient.SendAsync(req, cancellationToken).ConfigureAwait(false);
+{{#supportsRetry}}
+            }
+{{/supportsRetry}}
+
+            if (!response.IsSuccessStatusCode)
+            {
+                return await ToApiResponse<T>(response, default(T), req.RequestUri);
+            }
+
+            object responseData = await deserializer.Deserialize<T>(response);
+
+            // if the response type is oneOf/anyOf, call FromJSON to deserialize the data
+            if (typeof({{{packageName}}}.{{modelPackage}}.AbstractOpenAPISchema).IsAssignableFrom(typeof(T)))
+            {
+                responseData = (T) typeof(T).GetMethod("FromJson").Invoke(null, new object[] { response.Content });
+            }
+            else if (typeof(T).Name == "Stream") // for binary response
+            {
+                responseData = (T) (object) await response.Content.ReadAsStreamAsync();
+            }
+
+            InterceptResponse(req, response);
+
+            return await ToApiResponse<T>(response, responseData, req.RequestUri);
+        }
+
+        {{#supportsAsync}}
+        #region IAsynchronousClient
+        /// <summary>
+        /// Make a HTTP GET request (async).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public Task<ApiResponse<T>> GetAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return ExecAsync<T>(NewRequest(HttpMethod.Get, path, options, config), config, cancellationToken);
+        }
+
+        /// <summary>
+        /// Make a HTTP POST request (async).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public Task<ApiResponse<T>> PostAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return ExecAsync<T>(NewRequest(HttpMethod.Post, path, options, config), config, cancellationToken);
+        }
+
+        /// <summary>
+        /// Make a HTTP PUT request (async).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public Task<ApiResponse<T>> PutAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return ExecAsync<T>(NewRequest(HttpMethod.Put, path, options, config), config, cancellationToken);
+        }
+
+        /// <summary>
+        /// Make a HTTP DELETE request (async).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public Task<ApiResponse<T>> DeleteAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return ExecAsync<T>(NewRequest(HttpMethod.Delete, path, options, config), config, cancellationToken);
+        }
+
+        /// <summary>
+        /// Make a HTTP HEAD request (async).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public Task<ApiResponse<T>> HeadAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return ExecAsync<T>(NewRequest(HttpMethod.Head, path, options, config), config, cancellationToken);
+        }
+
+        /// <summary>
+        /// Make a HTTP OPTION request (async).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public Task<ApiResponse<T>> OptionsAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return ExecAsync<T>(NewRequest(HttpMethod.Options, path, options, config), config, cancellationToken);
+        }
+
+        /// <summary>
+        /// Make a HTTP PATCH request (async).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public Task<ApiResponse<T>> PatchAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return ExecAsync<T>(NewRequest(new HttpMethod("PATCH"), path, options, config), config, cancellationToken);
+        }
+        #endregion IAsynchronousClient
+        {{/supportsAsync}}
+
+        #region ISynchronousClient
+        /// <summary>
+        /// Make a HTTP GET request (synchronous).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public ApiResponse<T> Get<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return Exec<T>(NewRequest(HttpMethod.Get, path, options, config), config);
+        }
+
+        /// <summary>
+        /// Make a HTTP POST request (synchronous).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public ApiResponse<T> Post<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return Exec<T>(NewRequest(HttpMethod.Post, path, options, config), config);
+        }
+
+        /// <summary>
+        /// Make a HTTP PUT request (synchronous).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public ApiResponse<T> Put<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return Exec<T>(NewRequest(HttpMethod.Put, path, options, config), config);
+        }
+
+        /// <summary>
+        /// Make a HTTP DELETE request (synchronous).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public ApiResponse<T> Delete<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return Exec<T>(NewRequest(HttpMethod.Delete, path, options, config), config);
+        }
+
+        /// <summary>
+        /// Make a HTTP HEAD request (synchronous).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public ApiResponse<T> Head<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return Exec<T>(NewRequest(HttpMethod.Head, path, options, config), config);
+        }
+
+        /// <summary>
+        /// Make a HTTP OPTION request (synchronous).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public ApiResponse<T> Options<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return Exec<T>(NewRequest(HttpMethod.Options, path, options, config), config);
+        }
+
+        /// <summary>
+        /// Make a HTTP PATCH request (synchronous).
+        /// </summary>
+        /// <param name="path">The target path (or resource).</param>
+        /// <param name="options">The additional request options.</param>
+        /// <param name="configuration">A per-request configuration object. It is assumed that any merge with
+        /// GlobalConfiguration has been done before calling this method.</param>
+        /// <returns>A Task containing ApiResponse</returns>
+        public ApiResponse<T> Patch<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
+        {
+            var config = configuration ?? GlobalConfiguration.Instance;
+            return Exec<T>(NewRequest(new HttpMethod("PATCH"), path, options, config), config);
+        }
+        #endregion ISynchronousClient
+    }
+}
diff --git a/openapi/templates/csharp-netcore/libraries/httpclient/FileParameter.mustache b/openapi/templates/csharp-netcore/libraries/httpclient/FileParameter.mustache
new file mode 100644
index 00000000000..3fa243ed54f
--- /dev/null
+++ b/openapi/templates/csharp-netcore/libraries/httpclient/FileParameter.mustache
@@ -0,0 +1,54 @@
+{{>partial_header}}
+
+using System.IO;
+
+namespace {{packageName}}.Client
+{
+
+    /// <summary>
+    /// Represents a File passed to the API as a Parameter, allows using different backends for files
+    /// </summary>
+    public class FileParameter
+    {
+        /// <summary>
+        /// The filename
+        /// </summary>
+        public string Name { get; set; } = "no_name_provided";
+
+        /// <summary>
+        /// The content of the file
+        /// </summary>
+        public Stream Content { get; set; }
+
+        /// <summary>
+        /// Construct a FileParameter just from the contents, will extract the filename from a filestream
+        /// </summary>
+        /// <param name="content"> The file content </param>
+        public FileParameter(Stream content)
+        {
+            if (content is FileStream fs)
+            {
+                Name = fs.Name;
+            }
+            Content = content;
+        }
+
+        /// <summary>
+        /// Construct a FileParameter from name and content
+        /// </summary>
+        /// <param name="filename">The filename</param>
+        /// <param name="content">The file content</param>
+        public FileParameter(string filename, Stream content)
+        {
+            Name = filename;
+            Content = content;
+        }
+
+        /// <summary>
+        /// Implicit conversion of stream to file parameter. Useful for backwards compatibility.
+        /// </summary>
+        /// <param name="s">Stream to convert</param>
+        /// <returns>FileParameter</returns>
+        public static implicit operator FileParameter(Stream s) => new FileParameter(s);
+    }
+}
\ No newline at end of file
diff --git a/openapi/templates/csharp-netcore/libraries/httpclient/RequestOptions.mustache b/openapi/templates/csharp-netcore/libraries/httpclient/RequestOptions.mustache
new file mode 100644
index 00000000000..bc5a8e348b8
--- /dev/null
+++ b/openapi/templates/csharp-netcore/libraries/httpclient/RequestOptions.mustache
@@ -0,0 +1,66 @@
+{{>partial_header}}
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+
+namespace {{packageName}}.Client
+{
+    /// <summary>
+    /// A container for generalized request inputs. This type allows consumers to extend the request functionality
+    /// by abstracting away from the default (built-in) request framework (e.g. RestSharp).
+    /// </summary>
+    public class RequestOptions
+    {
+        /// <summary>
+        /// Parameters to be bound to path parts of the Request's URL
+        /// </summary>
+        public Dictionary<string, string> PathParameters { get; set; }
+
+        /// <summary>
+        /// Query parameters to be applied to the request.
+        /// Keys may have 1 or more values associated.
+        /// </summary>
+        public Multimap<string, string> QueryParameters { get; set; }
+
+        /// <summary>
+        /// Header parameters to be applied to to the request.
+        /// Keys may have 1 or more values associated.
+        /// </summary>
+        public Multimap<string, string> HeaderParameters { get; set; }
+
+        /// <summary>
+        /// Form parameters to be sent along with the request.
+        /// </summary>
+        public Dictionary<string, string> FormParameters { get; set; }
+
+        /// <summary>
+        /// File parameters to be sent along with the request.
+        /// </summary>
+        public Dictionary<string, FileParameter> FileParameters { get; set; }
+
+        /// <summary>
+        /// Cookies to be sent along with the request.
+        /// </summary>
+        public List<Cookie> Cookies { get; set; }
+
+        /// <summary>
+        /// Any data associated with a request body.
+        /// </summary>
+        public Object Data { get; set; }
+
+        /// <summary>
+        /// Constructs a new instance of <see cref="RequestOptions"/>
+        /// </summary>
+        public RequestOptions()
+        {
+            PathParameters = new Dictionary<string, string>();
+            QueryParameters = new Multimap<string, string>();
+            HeaderParameters = new Multimap<string, string>();
+            FormParameters = new Dictionary<string, string>();
+            FileParameters = new Dictionary<string, FileParameter>();
+            Cookies = new List<Cookie>();
+        }
+    }
+}
diff --git a/openapi/templates/csharp-netcore/libraries/httpclient/api.mustache b/openapi/templates/csharp-netcore/libraries/httpclient/api.mustache
new file mode 100644
index 00000000000..f1f8a890d3d
--- /dev/null
+++ b/openapi/templates/csharp-netcore/libraries/httpclient/api.mustache
@@ -0,0 +1,748 @@
+{{>partial_header}}
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Net.Mime;
+using {{packageName}}.Client;
+{{#hasImport}}using {{packageName}}.{{modelPackage}};
+{{/hasImport}}
+
+namespace {{packageName}}.{{apiPackage}}
+{
+    {{#operations}}
+
+    /// <summary>
+    /// Represents a collection of functions to interact with the API endpoints
+    /// </summary>
+    {{>visibility}} interface {{interfacePrefix}}{{classname}}Sync : IApiAccessor
+    {
+        #region Synchronous Operations
+        {{#operation}}
+        /// <summary>
+        /// {{summary}}
+        /// </summary>
+        {{#notes}}
+        /// <remarks>
+        /// {{.}}
+        /// </remarks>
+        {{/notes}}
+        /// <exception cref="{{packageName}}.Client.ApiException">Thrown when fails to make API call</exception>
+        {{#allParams}}/// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}</param>
+        {{/allParams}}/// <returns>{{returnType}}</returns>
+        {{#isDeprecated}}
+        [Obsolete]
+        {{/isDeprecated}}
+        {{{returnType}}}{{^returnType}}void{{/returnType}} {{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}});
+
+        /// <summary>
+        /// {{summary}}
+        /// </summary>
+        /// <remarks>
+        /// {{notes}}
+        /// </remarks>
+        /// <exception cref="{{packageName}}.Client.ApiException">Thrown when fails to make API call</exception>
+        {{#allParams}}/// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}</param>
+        {{/allParams}}/// <returns>ApiResponse of {{returnType}}{{^returnType}}Object(void){{/returnType}}</returns>
+        {{#isDeprecated}}
+        [Obsolete]
+        {{/isDeprecated}}
+        ApiResponse<{{{returnType}}}{{^returnType}}Object{{/returnType}}> {{operationId}}WithHttpInfo({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}});
+        {{/operation}}
+        #endregion Synchronous Operations
+    }
+
+    {{#supportsAsync}}
+    /// <summary>
+    /// Represents a collection of functions to interact with the API endpoints
+    /// </summary>
+    {{>visibility}} interface {{interfacePrefix}}{{classname}}Async : IApiAccessor
+    {
+        #region Asynchronous Operations
+        {{#operation}}
+        /// <summary>
+        /// {{summary}}
+        /// </summary>
+        /// <remarks>
+        /// {{notes}}
+        /// </remarks>
+        /// <exception cref="{{packageName}}.Client.ApiException">Thrown when fails to make API call</exception>
+        {{#allParams}}
+        /// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}</param>
+        {{/allParams}}
+        /// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
+        /// <returns>Task of {{returnType}}{{^returnType}}void{{/returnType}}</returns>
+        {{#isDeprecated}}
+        [Obsolete]
+        {{/isDeprecated}}
+        {{#returnType}}System.Threading.Tasks.Task<{{{.}}}>{{/returnType}}{{^returnType}}System.Threading.Tasks.Task{{/returnType}} {{operationId}}Async({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+
+        /// <summary>
+        /// {{summary}}
+        /// </summary>
+        /// <remarks>
+        /// {{notes}}
+        /// </remarks>
+        /// <exception cref="{{packageName}}.Client.ApiException">Thrown when fails to make API call</exception>
+        {{#allParams}}
+        /// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}</param>
+        {{/allParams}}
+        /// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
+        /// <returns>Task of ApiResponse{{#returnType}} ({{.}}){{/returnType}}</returns>
+        {{#isDeprecated}}
+        [Obsolete]
+        {{/isDeprecated}}
+        System.Threading.Tasks.Task<ApiResponse<{{{returnType}}}{{^returnType}}Object{{/returnType}}>> {{operationId}}WithHttpInfoAsync({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+        {{/operation}}
+        #endregion Asynchronous Operations
+    }
+    {{/supportsAsync}}
+
+    /// <summary>
+    /// Represents a collection of functions to interact with the API endpoints
+    /// </summary>
+    {{>visibility}} interface {{interfacePrefix}}{{classname}} : {{interfacePrefix}}{{classname}}Sync{{#supportsAsync}}, {{interfacePrefix}}{{classname}}Async{{/supportsAsync}}
+    {
+
+    }
+
+    /// <summary>
+    /// Represents a collection of functions to interact with the API endpoints
+    /// </summary>
+    {{>visibility}} partial class {{classname}} : IDisposable, {{interfacePrefix}}{{classname}}
+    {
+        private {{packageName}}.Client.ExceptionFactory _exceptionFactory = (name, response) => null;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="{{classname}}"/> class.
+        /// **IMPORTANT** This will also create an instance of HttpClient, which is less than ideal.
+        /// It's better to reuse the <see href="https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#issues-with-the-original-httpclient-class-available-in-net">HttpClient and HttpClientHandler</see>.
+        /// </summary>
+        /// <returns></returns>
+        public {{classname}}() : this((string)null)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="{{classname}}"/> class.
+        /// **IMPORTANT** This will also create an instance of HttpClient, which is less than ideal.
+        /// It's better to reuse the <see href="https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#issues-with-the-original-httpclient-class-available-in-net">HttpClient and HttpClientHandler</see>.
+        /// </summary>
+        /// <param name="basePath">The target service's base path in URL format.</param>
+        /// <exception cref="ArgumentException"></exception>
+        /// <returns></returns>
+        public {{classname}}(string basePath)
+        {
+            this.Configuration = {{packageName}}.Client.Configuration.MergeConfigurations(
+                {{packageName}}.Client.GlobalConfiguration.Instance,
+                new {{packageName}}.Client.Configuration { BasePath = basePath }
+            );
+            this.ApiClient = new {{packageName}}.Client.ApiClient(this.Configuration.BasePath);
+            this.Client =  this.ApiClient;
+            {{#supportsAsync}}
+            this.AsynchronousClient = this.ApiClient;
+            {{/supportsAsync}}
+            this.ExceptionFactory = {{packageName}}.Client.Configuration.DefaultExceptionFactory;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="{{classname}}"/> class using Configuration object.
+        /// **IMPORTANT** This will also create an instance of HttpClient, which is less than ideal.
+        /// It's better to reuse the <see href="https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#issues-with-the-original-httpclient-class-available-in-net">HttpClient and HttpClientHandler</see>.
+        /// </summary>
+        /// <param name="configuration">An instance of Configuration.</param>
+        /// <exception cref="ArgumentNullException"></exception>
+        /// <returns></returns>
+        public {{classname}}({{packageName}}.Client.Configuration configuration)
+        {
+            if (configuration == null) throw new ArgumentNullException("configuration");
+
+            this.Configuration = {{packageName}}.Client.Configuration.MergeConfigurations(
+                {{packageName}}.Client.GlobalConfiguration.Instance,
+                configuration
+            );
+            this.ApiClient = new {{packageName}}.Client.ApiClient(this.Configuration.BasePath);
+            this.Client = this.ApiClient;
+            {{#supportsAsync}}
+            this.AsynchronousClient = this.ApiClient;
+            {{/supportsAsync}}
+            ExceptionFactory = {{packageName}}.Client.Configuration.DefaultExceptionFactory;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="{{classname}}"/> class.
+        /// </summary>
+        /// <param name="client">An instance of HttpClient.</param>
+        /// <param name="handler">An optional instance of HttpClientHandler that is used by HttpClient.</param>
+        /// <exception cref="ArgumentNullException"></exception>
+        /// <returns></returns>
+        /// <remarks>
+        /// Some configuration settings will not be applied without passing an HttpClientHandler.
+        /// The features affected are: Setting and Retrieving Cookies, Client Certificates, Proxy settings.
+        /// </remarks>
+        public {{classname}}(HttpClient client, HttpClientHandler handler = null) : this(client, (string)null, handler)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="{{classname}}"/> class.
+        /// </summary>
+        /// <param name="client">An instance of HttpClient.</param>
+        /// <param name="basePath">The target service's base path in URL format.</param>
+        /// <param name="handler">An optional instance of HttpClientHandler that is used by HttpClient.</param>
+        /// <exception cref="ArgumentNullException"></exception>
+        /// <exception cref="ArgumentException"></exception>
+        /// <returns></returns>
+        /// <remarks>
+        /// Some configuration settings will not be applied without passing an HttpClientHandler.
+        /// The features affected are: Setting and Retrieving Cookies, Client Certificates, Proxy settings.
+        /// </remarks>
+        public {{classname}}(HttpClient client, string basePath, HttpClientHandler handler = null)
+        {
+            if (client == null) throw new ArgumentNullException("client");
+
+            this.Configuration = {{packageName}}.Client.Configuration.MergeConfigurations(
+                {{packageName}}.Client.GlobalConfiguration.Instance,
+                new {{packageName}}.Client.Configuration { BasePath = basePath }
+            );
+            this.ApiClient = new {{packageName}}.Client.ApiClient(client, this.Configuration.BasePath, handler);
+            this.Client =  this.ApiClient;
+            {{#supportsAsync}}
+            this.AsynchronousClient = this.ApiClient;
+            {{/supportsAsync}}
+            this.ExceptionFactory = {{packageName}}.Client.Configuration.DefaultExceptionFactory;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="{{classname}}"/> class using Configuration object.
+        /// </summary>
+        /// <param name="client">An instance of HttpClient.</param>
+        /// <param name="configuration">An instance of Configuration.</param>
+        /// <param name="handler">An optional instance of HttpClientHandler that is used by HttpClient.</param>
+        /// <exception cref="ArgumentNullException"></exception>
+        /// <returns></returns>
+        /// <remarks>
+        /// Some configuration settings will not be applied without passing an HttpClientHandler.
+        /// The features affected are: Setting and Retrieving Cookies, Client Certificates, Proxy settings.
+        /// </remarks>
+        public {{classname}}(HttpClient client, {{packageName}}.Client.Configuration configuration, HttpClientHandler handler = null)
+        {
+            if (configuration == null) throw new ArgumentNullException("configuration");
+            if (client == null) throw new ArgumentNullException("client");
+
+            this.Configuration = {{packageName}}.Client.Configuration.MergeConfigurations(
+                {{packageName}}.Client.GlobalConfiguration.Instance,
+                configuration
+            );
+            this.ApiClient = new {{packageName}}.Client.ApiClient(client, this.Configuration.BasePath, handler);
+            this.Client = this.ApiClient;
+            {{#supportsAsync}}
+            this.AsynchronousClient = this.ApiClient;
+            {{/supportsAsync}}
+            ExceptionFactory = {{packageName}}.Client.Configuration.DefaultExceptionFactory;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="{{classname}}"/> class
+        /// using a Configuration object and client instance.
+        /// </summary>
+        /// <param name="client">The client interface for synchronous API access.</param>{{#supportsAsync}}
+        /// <param name="asyncClient">The client interface for asynchronous API access.</param>{{/supportsAsync}}
+        /// <param name="configuration">The configuration object.</param>
+        /// <exception cref="ArgumentNullException"></exception>
+        public {{classname}}({{packageName}}.Client.ISynchronousClient client, {{#supportsAsync}}{{packageName}}.Client.IAsynchronousClient asyncClient, {{/supportsAsync}}{{packageName}}.Client.IReadableConfiguration configuration)
+        {
+            if (client == null) throw new ArgumentNullException("client");
+            {{#supportsAsync}}
+            if (asyncClient == null) throw new ArgumentNullException("asyncClient");
+            {{/supportsAsync}}
+            if (configuration == null) throw new ArgumentNullException("configuration");
+
+            this.Client = client;
+            {{#supportsAsync}}
+            this.AsynchronousClient = asyncClient;
+            {{/supportsAsync}}
+            this.Configuration = configuration;
+            this.ExceptionFactory = {{packageName}}.Client.Configuration.DefaultExceptionFactory;
+        }
+
+        /// <summary>
+        /// Disposes resources if they were created by us
+        /// </summary>
+        public void Dispose()
+        {
+            this.ApiClient?.Dispose();
+        }
+
+        /// <summary>
+        /// Holds the ApiClient if created
+        /// </summary>
+        public {{packageName}}.Client.ApiClient ApiClient { get; set; } = null;
+
+        {{#supportsAsync}}
+        /// <summary>
+        /// The client for accessing this underlying API asynchronously.
+        /// </summary>
+        public {{packageName}}.Client.IAsynchronousClient AsynchronousClient { get; set; }
+        {{/supportsAsync}}
+
+        /// <summary>
+        /// The client for accessing this underlying API synchronously.
+        /// </summary>
+        public {{packageName}}.Client.ISynchronousClient Client { get; set; }
+
+        /// <summary>
+        /// Gets the base path of the API client.
+        /// </summary>
+        /// <value>The base path</value>
+        public string GetBasePath()
+        {
+            return this.Configuration.BasePath;
+        }
+
+        /// <summary>
+        /// Gets or sets the configuration object
+        /// </summary>
+        /// <value>An instance of the Configuration</value>
+        public {{packageName}}.Client.IReadableConfiguration Configuration { get; set; }
+
+        /// <summary>
+        /// Provides a factory method hook for the creation of exceptions.
+        /// </summary>
+        public {{packageName}}.Client.ExceptionFactory ExceptionFactory
+        {
+            get
+            {
+                if (_exceptionFactory != null && _exceptionFactory.GetInvocationList().Length > 1)
+                {
+                    throw new InvalidOperationException("Multicast delegate for ExceptionFactory is unsupported.");
+                }
+                return _exceptionFactory;
+            }
+            set { _exceptionFactory = value; }
+        }
+
+        {{#operation}}
+        /// <summary>
+        /// {{summary}} {{notes}}
+        /// </summary>
+        /// <exception cref="{{packageName}}.Client.ApiException">Thrown when fails to make API call</exception>
+        {{#allParams}}/// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}</param>
+        {{/allParams}}/// <returns>{{returnType}}</returns>
+        {{#isDeprecated}}
+        [Obsolete]
+        {{/isDeprecated}}
+        public {{{returnType}}}{{^returnType}}void{{/returnType}} {{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}})
+        {
+            {{#returnType}}{{packageName}}.Client.ApiResponse<{{{returnType}}}> localVarResponse = {{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}});
+            return localVarResponse.Data;{{/returnType}}{{^returnType}}{{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}});{{/returnType}}
+        }
+
+        /// <summary>
+        /// {{summary}} {{notes}}
+        /// </summary>
+        /// <exception cref="{{packageName}}.Client.ApiException">Thrown when fails to make API call</exception>
+        {{#allParams}}/// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}</param>
+        {{/allParams}}/// <returns>ApiResponse of {{returnType}}{{^returnType}}Object(void){{/returnType}}</returns>
+        {{#isDeprecated}}
+        [Obsolete]
+        {{/isDeprecated}}
+        public {{packageName}}.Client.ApiResponse<{{{returnType}}}{{^returnType}}Object{{/returnType}}> {{operationId}}WithHttpInfo({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}})
+        {
+            {{#allParams}}
+            {{#required}}
+            {{^vendorExtensions.x-csharp-value-type}}
+            // verify the required parameter '{{paramName}}' is set
+            if ({{paramName}} == null)
+                throw new {{packageName}}.Client.ApiException(400, "Missing required parameter '{{paramName}}' when calling {{classname}}->{{operationId}}");
+
+            {{/vendorExtensions.x-csharp-value-type}}
+            {{/required}}
+            {{/allParams}}
+            {{packageName}}.Client.RequestOptions localVarRequestOptions = new {{packageName}}.Client.RequestOptions();
+
+            string[] _contentTypes = new string[] {
+                {{#consumes}}
+                "{{{mediaType}}}"{{^-last}},{{/-last}}
+                {{/consumes}}
+            };
+
+            // to determine the Accept header
+            string[] _accepts = new string[] {
+                {{#produces}}
+                "{{{mediaType}}}"{{^-last}},{{/-last}}
+                {{/produces}}
+            };
+
+            var localVarContentType = {{packageName}}.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
+            if (localVarContentType != null) localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
+
+            var localVarAccept = {{packageName}}.Client.ClientUtils.SelectHeaderAccept(_accepts);
+            if (localVarAccept != null) localVarRequestOptions.HeaderParameters.Add("Accept", localVarAccept);
+
+            {{#pathParams}}
+            {{#required}}
+            localVarRequestOptions.PathParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // path parameter
+            {{/required}}
+            {{^required}}
+            if ({{paramName}} != null)
+            {
+                localVarRequestOptions.PathParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // path parameter
+            }
+            {{/required}}
+            {{/pathParams}}
+            {{#queryParams}}
+            {{#required}}
+            {{#isDeepObject}}
+            {{#items.vars}}
+            localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{baseName}}", {{paramName}}.{{name}}));
+            {{/items.vars}}
+            {{^items}}
+            localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("deepObject", "{{baseName}}", {{paramName}}));
+            {{/items}}
+            {{/isDeepObject}}
+            {{^isDeepObject}}
+            localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{baseName}}", {{paramName}}));
+            {{/isDeepObject}}
+            {{/required}}
+            {{^required}}
+            if ({{paramName}} != null)
+            {
+                {{#isDeepObject}}
+                    {{#items.vars}}
+                if ({{paramName}}.{{name}} != null)
+                {
+                    localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{baseName}}", {{paramName}}.{{name}}));
+                }
+                    {{/items.vars}}
+                {{^items}}
+                localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("deepObject", "{{baseName}}", {{paramName}}));
+                {{/items}}
+                {{/isDeepObject}}
+                {{^isDeepObject}}
+                localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{baseName}}", {{paramName}}));
+                {{/isDeepObject}}
+            }
+            {{/required}}
+            {{/queryParams}}
+            {{#headerParams}}
+            {{#required}}
+            localVarRequestOptions.HeaderParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // header parameter
+            {{/required}}
+            {{^required}}
+            if ({{paramName}} != null)
+            {
+                localVarRequestOptions.HeaderParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // header parameter
+            }
+            {{/required}}
+            {{/headerParams}}
+            {{#formParams}}
+            {{#required}}
+            {{#isFile}}
+            localVarRequestOptions.FileParameters.Add("{{baseName}}", {{paramName}});
+            {{/isFile}}
+            {{^isFile}}
+            localVarRequestOptions.FormParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // form parameter
+            {{/isFile}}
+            {{/required}}
+            {{^required}}
+            if ({{paramName}} != null)
+            {
+                {{#isFile}}
+                localVarRequestOptions.FileParameters.Add("{{baseName}}", {{paramName}});
+                {{/isFile}}
+                {{^isFile}}
+                localVarRequestOptions.FormParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // form parameter
+                {{/isFile}}
+            }
+            {{/required}}
+            {{/formParams}}
+            {{#bodyParam}}
+            localVarRequestOptions.Data = {{paramName}};
+            {{/bodyParam}}
+
+            {{#authMethods}}
+            // authentication ({{name}}) required
+            {{#isApiKey}}
+            {{#isKeyInCookie}}
+            // cookie parameter support
+            if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
+            {
+                localVarRequestOptions.Cookies.Add(new Cookie("{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")));
+            }
+            {{/isKeyInCookie}}
+            {{#isKeyInHeader}}
+            if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
+            {
+                localVarRequestOptions.HeaderParameters.Add("{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}"));
+            }
+            {{/isKeyInHeader}}
+            {{#isKeyInQuery}}
+            if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
+            {
+                localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("", "{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")));
+            }
+            {{/isKeyInQuery}}
+            {{/isApiKey}}
+            {{#isBasicBasic}}
+            // http basic authentication required
+            if (!string.IsNullOrEmpty(this.Configuration.Username) || !string.IsNullOrEmpty(this.Configuration.Password))
+            {
+                localVarRequestOptions.HeaderParameters.Add("Authorization", "Basic " + {{packageName}}.Client.ClientUtils.Base64Encode(this.Configuration.Username + ":" + this.Configuration.Password));
+            }
+            {{/isBasicBasic}}
+            {{#isBasicBearer}}
+            // bearer authentication required
+            if (!string.IsNullOrEmpty(this.Configuration.AccessToken))
+            {
+                localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken);
+            }
+            {{/isBasicBearer}}
+            {{#isOAuth}}
+            // oauth required
+            if (!string.IsNullOrEmpty(this.Configuration.AccessToken))
+            {
+                localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken);
+            }
+            {{/isOAuth}}
+            {{#isHttpSignature}}
+            if (this.Configuration.HttpSigningConfiguration != null)
+            {
+                var HttpSigningHeaders = this.Configuration.HttpSigningConfiguration.GetHttpSignedHeader(this.Configuration.BasePath, "{{{httpMethod}}}", "{{{path}}}", localVarRequestOptions);
+                foreach (var headerItem in HttpSigningHeaders)
+                {
+                    if (localVarRequestOptions.HeaderParameters.ContainsKey(headerItem.Key))
+                    {
+                        localVarRequestOptions.HeaderParameters[headerItem.Key] = new List<string>() { headerItem.Value };
+                    }
+                    else
+                    {
+                        localVarRequestOptions.HeaderParameters.Add(headerItem.Key, headerItem.Value);
+                    }
+                }
+            }
+            {{/isHttpSignature}}
+            {{/authMethods}}
+
+            // make the HTTP request
+            var localVarResponse = this.Client.{{#lambda.titlecase}}{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}{{/lambda.titlecase}}<{{{returnType}}}{{^returnType}}Object{{/returnType}}>("{{{path}}}", localVarRequestOptions, this.Configuration);
+
+            if (this.ExceptionFactory != null)
+            {
+                Exception _exception = this.ExceptionFactory("{{operationId}}", localVarResponse);
+                if (_exception != null) throw _exception;
+            }
+
+            return localVarResponse;
+        }
+
+        {{#supportsAsync}}
+        /// <summary>
+        /// {{summary}} {{notes}}
+        /// </summary>
+        /// <exception cref="{{packageName}}.Client.ApiException">Thrown when fails to make API call</exception>
+        {{#allParams}}
+        /// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}</param>
+        {{/allParams}}
+        /// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
+        /// <returns>Task of {{returnType}}{{^returnType}}void{{/returnType}}</returns>
+        {{#isDeprecated}}
+        [Obsolete]
+        {{/isDeprecated}}
+        {{#returnType}}public async System.Threading.Tasks.Task<{{{.}}}>{{/returnType}}{{^returnType}}public async System.Threading.Tasks.Task{{/returnType}} {{operationId}}Async({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            {{#returnType}}{{packageName}}.Client.ApiResponse<{{{returnType}}}> localVarResponse = await {{operationId}}WithHttpInfoAsync({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}cancellationToken).ConfigureAwait(false);
+            return localVarResponse.Data;{{/returnType}}{{^returnType}}await {{operationId}}WithHttpInfoAsync({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}cancellationToken).ConfigureAwait(false);{{/returnType}}
+        }
+
+        /// <summary>
+        /// {{summary}} {{notes}}
+        /// </summary>
+        /// <exception cref="{{packageName}}.Client.ApiException">Thrown when fails to make API call</exception>
+        {{#allParams}}
+        /// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}</param>
+        {{/allParams}}
+        /// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
+        /// <returns>Task of ApiResponse{{#returnType}} ({{.}}){{/returnType}}</returns>
+        {{#isDeprecated}}
+        [Obsolete]
+        {{/isDeprecated}}
+        public async System.Threading.Tasks.Task<{{packageName}}.Client.ApiResponse<{{{returnType}}}{{^returnType}}Object{{/returnType}}>> {{operationId}}WithHttpInfoAsync({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+        {
+            {{#allParams}}
+            {{#required}}
+            {{^vendorExtensions.x-csharp-value-type}}
+            // verify the required parameter '{{paramName}}' is set
+            if ({{paramName}} == null)
+                throw new {{packageName}}.Client.ApiException(400, "Missing required parameter '{{paramName}}' when calling {{classname}}->{{operationId}}");
+
+            {{/vendorExtensions.x-csharp-value-type}}
+            {{/required}}
+            {{/allParams}}
+
+            {{packageName}}.Client.RequestOptions localVarRequestOptions = new {{packageName}}.Client.RequestOptions();
+
+            string[] _contentTypes = new string[] {
+                {{#consumes}}
+                "{{{mediaType}}}"{{^-last}}, {{/-last}}
+                {{/consumes}}
+            };
+
+            // to determine the Accept header
+            string[] _accepts = new string[] {
+                {{#produces}}
+                "{{{mediaType}}}"{{^-last}},{{/-last}}
+                {{/produces}}
+            };
+
+
+            var localVarContentType = {{packageName}}.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
+            if (localVarContentType != null) localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
+
+            var localVarAccept = {{packageName}}.Client.ClientUtils.SelectHeaderAccept(_accepts);
+            if (localVarAccept != null) localVarRequestOptions.HeaderParameters.Add("Accept", localVarAccept);
+
+            {{#pathParams}}
+            {{#required}}
+            localVarRequestOptions.PathParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // path parameter
+            {{/required}}
+            {{^required}}
+            if ({{paramName}} != null)
+            {
+                localVarRequestOptions.PathParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // path parameter
+            }
+            {{/required}}
+            {{/pathParams}}
+            {{#queryParams}}
+            {{#required}}
+            localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{baseName}}", {{paramName}}));
+            {{/required}}
+            {{^required}}
+            if ({{paramName}} != null)
+            {
+                localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{baseName}}", {{paramName}}));
+            }
+            {{/required}}
+            {{/queryParams}}
+            {{#headerParams}}
+            {{#required}}
+            localVarRequestOptions.HeaderParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // header parameter
+            {{/required}}
+            {{^required}}
+            if ({{paramName}} != null)
+            {
+                localVarRequestOptions.HeaderParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // header parameter
+            }
+            {{/required}}
+            {{/headerParams}}
+            {{#formParams}}
+            {{#required}}
+            {{#isFile}}
+            localVarRequestOptions.FileParameters.Add("{{baseName}}", {{paramName}});
+            {{/isFile}}
+            {{^isFile}}
+            localVarRequestOptions.FormParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // form parameter
+            {{/isFile}}
+            {{/required}}
+            {{^required}}
+            if ({{paramName}} != null)
+            {
+                {{#isFile}}
+                localVarRequestOptions.FileParameters.Add("{{baseName}}", {{paramName}});
+                {{/isFile}}
+                {{^isFile}}
+                localVarRequestOptions.FormParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // form parameter
+                {{/isFile}}
+            }
+            {{/required}}
+            {{/formParams}}
+            {{#bodyParam}}
+            localVarRequestOptions.Data = {{paramName}};
+            {{/bodyParam}}
+
+            {{#authMethods}}
+            // authentication ({{name}}) required
+            {{#isApiKey}}
+            {{#isKeyInCookie}}
+            // cookie parameter support
+            if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
+            {
+                localVarRequestOptions.Cookies.Add(new Cookie("{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")));
+            }
+            {{/isKeyInCookie}}
+            {{#isKeyInHeader}}
+            if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
+            {
+                localVarRequestOptions.HeaderParameters.Add("{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}"));
+            }
+            {{/isKeyInHeader}}
+            {{#isKeyInQuery}}
+            if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
+            {
+                localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("", "{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")));
+            }
+            {{/isKeyInQuery}}
+            {{/isApiKey}}
+            {{#isBasic}}
+            {{#isBasicBasic}}
+            // http basic authentication required
+            if (!string.IsNullOrEmpty(this.Configuration.Username) || !string.IsNullOrEmpty(this.Configuration.Password))
+            {
+                localVarRequestOptions.HeaderParameters.Add("Authorization", "Basic " + {{packageName}}.Client.ClientUtils.Base64Encode(this.Configuration.Username + ":" + this.Configuration.Password));
+            }
+            {{/isBasicBasic}}
+            {{#isBasicBearer}}
+            // bearer authentication required
+            if (!string.IsNullOrEmpty(this.Configuration.AccessToken))
+            {
+                localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken);
+            }
+            {{/isBasicBearer}}
+            {{/isBasic}}
+            {{#isOAuth}}
+            // oauth required
+            if (!string.IsNullOrEmpty(this.Configuration.AccessToken))
+            {
+                localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken);
+            }
+            {{/isOAuth}}
+            {{#isHttpSignature}}
+            if (this.Configuration.HttpSigningConfiguration != null)
+            {
+                var HttpSigningHeaders = this.Configuration.HttpSigningConfiguration.GetHttpSignedHeader(this.Configuration.BasePath, "{{{httpMethod}}}", "{{{path}}}", localVarRequestOptions);
+                foreach (var headerItem in HttpSigningHeaders)
+                {
+                    if (localVarRequestOptions.HeaderParameters.ContainsKey(headerItem.Key))
+                    {
+                        localVarRequestOptions.HeaderParameters[headerItem.Key] = new List<string>() { headerItem.Value };
+                    }
+                    else
+                    {
+                        localVarRequestOptions.HeaderParameters.Add(headerItem.Key, headerItem.Value);
+                    }
+                }
+            }
+            {{/isHttpSignature}}
+            {{/authMethods}}
+
+            // make the HTTP request
+
+            var localVarResponse = await this.AsynchronousClient.{{#lambda.titlecase}}{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}{{/lambda.titlecase}}Async<{{{returnType}}}{{^returnType}}Object{{/returnType}}>("{{{path}}}", localVarRequestOptions, this.Configuration, cancellationToken).ConfigureAwait(false);
+
+            if (this.ExceptionFactory != null)
+            {
+                Exception _exception = this.ExceptionFactory("{{operationId}}", localVarResponse);
+                if (_exception != null) throw _exception;
+            }
+
+            return localVarResponse;
+        }
+
+        {{/supportsAsync}}
+        {{/operation}}
+    }
+    {{/operations}}
+}
diff --git a/openapi/templates/csharp-netcore/libraries/httpclient/model.mustache b/openapi/templates/csharp-netcore/libraries/httpclient/model.mustache
new file mode 100644
index 00000000000..0de1e05e514
--- /dev/null
+++ b/openapi/templates/csharp-netcore/libraries/httpclient/model.mustache
@@ -0,0 +1,48 @@
+{{>partial_header}}
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.IO;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Text.RegularExpressions;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using Newtonsoft.Json.Linq;
+{{#models}}
+{{#model}}
+{{#discriminator}}
+using JsonSubTypes;
+{{/discriminator}}
+{{/model}}
+{{/models}}
+{{#validatable}}
+using System.ComponentModel.DataAnnotations;
+{{/validatable}}
+using FileParameter = {{packageName}}.Client.FileParameter;
+using OpenAPIDateConverter = {{packageName}}.Client.OpenAPIDateConverter;
+{{#useCompareNetObjects}}
+using OpenAPIClientUtils = {{packageName}}.Client.ClientUtils;
+{{/useCompareNetObjects}}
+{{#models}}
+{{#model}}
+{{#oneOf}}
+{{#-first}}
+using System.Reflection;
+{{/-first}}
+{{/oneOf}}
+{{#aneOf}}
+{{#-first}}
+using System.Reflection;
+{{/-first}}
+{{/aneOf}}
+
+namespace {{packageName}}.{{modelPackage}}
+{
+{{#isEnum}}{{>modelEnum}}{{/isEnum}}{{^isEnum}}{{#oneOf}}{{#-first}}{{>modelOneOf}}{{/-first}}{{/oneOf}}{{#anyOf}}{{#-first}}{{>modelAnyOf}}{{/-first}}{{/anyOf}}{{^oneOf}}{{^anyOf}}{{>modelGeneric}}{{/anyOf}}{{/oneOf}}{{/isEnum}}
+{{/model}}
+{{/models}}
+}
diff --git a/openapi/templates/csharp-netcore/model.mustache b/openapi/templates/csharp-netcore/model.mustache
new file mode 100644
index 00000000000..339a6d2f76b
--- /dev/null
+++ b/openapi/templates/csharp-netcore/model.mustache
@@ -0,0 +1,47 @@
+{{>partial_header}}
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.IO;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Text.RegularExpressions;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using Newtonsoft.Json.Linq;
+{{#models}}
+{{#model}}
+{{#discriminator}}
+using JsonSubTypes;
+{{/discriminator}}
+{{/model}}
+{{/models}}
+{{#validatable}}
+using System.ComponentModel.DataAnnotations;
+{{/validatable}}
+using OpenAPIDateConverter = {{packageName}}.Client.OpenAPIDateConverter;
+{{#useCompareNetObjects}}
+using OpenAPIClientUtils = {{packageName}}.Client.ClientUtils;
+{{/useCompareNetObjects}}
+{{#models}}
+{{#model}}
+{{#oneOf}}
+{{#-first}}
+using System.Reflection;
+{{/-first}}
+{{/oneOf}}
+{{#aneOf}}
+{{#-first}}
+using System.Reflection;
+{{/-first}}
+{{/aneOf}}
+
+namespace {{packageName}}.{{modelPackage}}
+{
+{{#isEnum}}{{>modelEnum}}{{/isEnum}}{{^isEnum}}{{#oneOf}}{{#-first}}{{>modelOneOf}}{{/-first}}{{/oneOf}}{{#anyOf}}{{#-first}}{{>modelAnyOf}}{{/-first}}{{/anyOf}}{{^oneOf}}{{^anyOf}}{{>modelGeneric}}{{/anyOf}}{{/oneOf}}{{/isEnum}}
+{{/model}}
+{{/models}}
+}
diff --git a/openapi/templates/csharp-netcore/modelAnyOf.mustache b/openapi/templates/csharp-netcore/modelAnyOf.mustache
new file mode 100644
index 00000000000..7c3b0cc2d94
--- /dev/null
+++ b/openapi/templates/csharp-netcore/modelAnyOf.mustache
@@ -0,0 +1,227 @@
+    /// <summary>
+    /// {{description}}{{^description}}{{classname}}{{/description}}
+    /// </summary>
+    [JsonConverter(typeof({{classname}}JsonConverter))]
+    [DataContract(Name = "{{{name}}}")]
+    {{>visibility}} partial class {{classname}} : AbstractOpenAPISchema, {{#parent}}{{{.}}}, {{/parent}}IEquatable<{{classname}}>{{#validatable}}, IValidatableObject{{/validatable}}
+    {
+        {{#isNullable}}
+        /// <summary>
+        /// Initializes a new instance of the <see cref="{{classname}}" /> class.
+        /// </summary>
+        public {{classname}}()
+        {
+            this.IsNullable = true;
+            this.SchemaType= "anyOf";
+        }
+
+        {{/isNullable}}
+        {{#anyOf}}
+        /// <summary>
+        /// Initializes a new instance of the <see cref="{{classname}}" /> class
+        /// with the <see cref="{{{.}}}" /> class
+        /// </summary>
+        /// <param name="actualInstance">An instance of {{{.}}}.</param>
+        public {{classname}}({{{.}}} actualInstance)
+        {
+            this.IsNullable = {{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}};
+            this.SchemaType= "anyOf";
+            this.ActualInstance = actualInstance{{^isNullable}} ?? throw new ArgumentException("Invalid instance found. Must not be null."){{/isNullable}};
+        }
+
+        {{/anyOf}}
+
+        private Object _actualInstance;
+
+        /// <summary>
+        /// Gets or Sets ActualInstance
+        /// </summary>
+        public override Object ActualInstance
+        {
+            get
+            {
+                return _actualInstance;
+            }
+            set
+            {
+                {{#anyOf}}
+                {{^-first}}else {{/-first}}if (value.GetType() == typeof({{{.}}}))
+                {
+                    this._actualInstance = value;
+                }
+                {{/anyOf}}
+                else
+                {
+                    throw new ArgumentException("Invalid instance found. Must be the following types:{{#anyOf}} {{{.}}}{{^-last}},{{/-last}}{{/anyOf}}");
+                }
+            }
+        }
+        {{#anyOf}}
+
+        /// <summary>
+        /// Get the actual instance of `{{{.}}}`. If the actual instance is not `{{{.}}}`,
+        /// the InvalidClassException will be thrown
+        /// </summary>
+        /// <returns>An instance of {{{.}}}</returns>
+        public {{{.}}} Get{{{.}}}()
+        {
+            return ({{{.}}})this.ActualInstance;
+        }
+        {{/anyOf}}
+
+        /// <summary>
+        /// Returns the string presentation of the object
+        /// </summary>
+        /// <returns>String presentation of the object</returns>
+        public override string ToString()
+        {
+            var sb = new StringBuilder();
+            sb.Append("class {{classname}} {\n");
+            sb.Append("  ActualInstance: ").Append(this.ActualInstance).Append("\n");
+            sb.Append("}\n");
+            return sb.ToString();
+        }
+
+        /// <summary>
+        /// Returns the JSON string presentation of the object
+        /// </summary>
+        /// <returns>JSON string presentation of the object</returns>
+        public override string ToJson()
+        {
+            return JsonConvert.SerializeObject(this.ActualInstance, {{classname}}.SerializerSettings);
+        }
+
+        /// <summary>
+        /// Converts the JSON string into an instance of {{classname}}
+        /// </summary>
+        /// <param name="jsonString">JSON string</param>
+        /// <returns>An instance of {{classname}}</returns>
+        public static {{classname}} FromJson(string jsonString)
+        {
+            {{classname}} new{{classname}} = null;
+
+            if (string.IsNullOrEmpty(jsonString))
+            {
+                return new{{classname}};
+            }
+            {{#anyOf}}
+
+            try
+            {
+                new{{classname}} = new {{classname}}(JsonConvert.DeserializeObject<{{{.}}}>(jsonString, {{classname}}.SerializerSettings));
+                // deserialization is considered successful at this point if no exception has been thrown.
+                return new{{classname}};
+            }
+            catch (Exception exception)
+            {
+                // deserialization failed, try the next one
+                System.Diagnostics.Debug.WriteLine(string.Format("Failed to deserialize `{0}` into {{{.}}}: {1}", jsonString, exception.ToString()));
+            }
+            {{/anyOf}}
+
+            // no match found, throw an exception
+            throw new InvalidDataException("The JSON string `" + jsonString + "` cannot be deserialized into any schema defined.");
+        }
+
+        /// <summary>
+        /// Returns true if objects are equal
+        /// </summary>
+        /// <param name="input">Object to be compared</param>
+        /// <returns>Boolean</returns>
+        public override bool Equals(object input)
+        {
+            {{#useCompareNetObjects}}
+            return OpenAPIClientUtils.compareLogic.Compare(this, input as {{classname}}).AreEqual;
+            {{/useCompareNetObjects}}
+            {{^useCompareNetObjects}}
+            return this.Equals(input as {{classname}});
+            {{/useCompareNetObjects}}
+        }
+
+        /// <summary>
+        /// Returns true if {{classname}} instances are equal
+        /// </summary>
+        /// <param name="input">Instance of {{classname}} to be compared</param>
+        /// <returns>Boolean</returns>
+        public bool Equals({{classname}} input)
+        {
+            {{#useCompareNetObjects}}
+            return OpenAPIClientUtils.compareLogic.Compare(this, input).AreEqual;
+            {{/useCompareNetObjects}}
+            {{^useCompareNetObjects}}
+            if (input == null)
+                return false;
+
+            return this.ActualInstance.Equals(input.ActualInstance);
+            {{/useCompareNetObjects}}
+        }
+
+        /// <summary>
+        /// Gets the hash code
+        /// </summary>
+        /// <returns>Hash code</returns>
+        public override int GetHashCode()
+        {
+            unchecked // Overflow is fine, just wrap
+            {
+                int hashCode = 41;
+                if (this.ActualInstance != null)
+                    hashCode = hashCode * 59 + this.ActualInstance.GetHashCode();
+                return hashCode;
+            }
+        }
+
+        /// <summary>
+        /// To validate all properties of the instance
+        /// </summary>
+        /// <param name="validationContext">Validation context</param>
+        /// <returns>Validation Result</returns>
+        IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)
+        {
+            yield break;
+        }
+    }
+
+    /// <summary>
+    /// Custom JSON converter for {{classname}}
+    /// </summary>
+    public class {{classname}}JsonConverter : JsonConverter
+    {
+        /// <summary>
+        /// To write the JSON string
+        /// </summary>
+        /// <param name="writer">JSON writer</param>
+        /// <param name="value">Object to be converted into a JSON string</param>
+        /// <param name="serializer">JSON Serializer</param>
+        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+        {
+            writer.WriteRawValue((string)(typeof({{classname}}).GetMethod("ToJson").Invoke(value, null)));
+        }
+
+        /// <summary>
+        /// To convert a JSON string into an object
+        /// </summary>
+        /// <param name="reader">JSON reader</param>
+        /// <param name="objectType">Object type</param>
+        /// <param name="existingValue">Existing value</param>
+        /// <param name="serializer">JSON Serializer</param>
+        /// <returns>The object converted from the JSON string</returns>
+        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+        {
+            if(reader.TokenType != JsonToken.Null)
+            {
+                return {{classname}}.FromJson(JObject.Load(reader).ToString(Formatting.None));
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Check if the object can be converted
+        /// </summary>
+        /// <param name="objectType">Object type</param>
+        /// <returns>True if the object can be converted</returns>
+        public override bool CanConvert(Type objectType)
+        {
+            return false;
+        }
+    }
diff --git a/openapi/templates/csharp-netcore/modelEnum.mustache b/openapi/templates/csharp-netcore/modelEnum.mustache
new file mode 100644
index 00000000000..d781952b4e0
--- /dev/null
+++ b/openapi/templates/csharp-netcore/modelEnum.mustache
@@ -0,0 +1,30 @@
+    /// <summary>
+    /// {{description}}{{^description}}Defines {{{name}}}{{/description}}
+    /// </summary>
+    {{#description}}
+    /// <value>{{.}}</value>
+    {{/description}}
+    {{#allowableValues}}
+    {{#enumVars}}
+    {{#-first}}
+    {{#isString}}
+    [JsonConverter(typeof(StringEnumConverter))]
+    {{/isString}}
+    {{/-first}}
+    {{/enumVars}}
+    {{/allowableValues}}
+    {{>visibility}} enum {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}{{#vendorExtensions.x-enum-byte}}: byte{{/vendorExtensions.x-enum-byte}}
+    {
+        {{#allowableValues}}
+        {{#enumVars}}
+        /// <summary>
+        /// Enum {{name}} for value: {{value}}
+        /// </summary>
+        {{#isString}}
+        [EnumMember(Value = "{{{value}}}")]
+        {{/isString}}
+        {{name}}{{^isString}} = {{{value}}}{{/isString}}{{#isString}} = {{-index}}{{/isString}}{{^-last}},{{/-last}}
+
+        {{/enumVars}}
+        {{/allowableValues}}
+    }{{! NOTE: This model's enumVars is modified to look like CodegenProperty}}
diff --git a/openapi/templates/csharp-netcore/modelGeneric.mustache b/openapi/templates/csharp-netcore/modelGeneric.mustache
new file mode 100644
index 00000000000..4d80ca9783c
--- /dev/null
+++ b/openapi/templates/csharp-netcore/modelGeneric.mustache
@@ -0,0 +1,480 @@
+    /// <summary>
+    /// {{description}}{{^description}}{{classname}}{{/description}}
+    /// </summary>
+    [DataContract(Name = "{{{name}}}")]
+    {{#discriminator}}
+    [JsonConverter(typeof(JsonSubtypes), "{{{discriminatorName}}}")]
+    {{#mappedModels}}
+    [JsonSubtypes.KnownSubType(typeof({{{modelName}}}), "{{^vendorExtensions.x-discriminator-value}}{{{mappingName}}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{.}}}{{/vendorExtensions.x-discriminator-value}}")]
+    {{/mappedModels}}
+    {{/discriminator}}
+    {{>visibility}} partial class {{classname}} : {{#parent}}{{{.}}}, {{/parent}}IEquatable<{{classname}}>{{#validatable}}, IValidatableObject{{/validatable}}
+    {
+        {{#vars}}
+        {{#items.isEnum}}
+        {{#items}}
+        {{^complexType}}
+{{>modelInnerEnum}}
+        {{/complexType}}
+        {{/items}}
+        {{/items.isEnum}}
+        {{#isEnum}}
+        {{^complexType}}
+{{>modelInnerEnum}}
+        {{/complexType}}
+        {{/isEnum}}
+        {{#isEnum}}
+
+        /// <summary>
+        /// {{description}}{{^description}}Gets or Sets {{{name}}}{{/description}}
+        /// </summary>
+        {{#description}}
+        /// <value>{{.}}</value>
+        {{/description}}
+        {{^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}}{{#isBoolean}}true{{/isBoolean}}{{^isBoolean}}{{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/isBoolean}}{{/vendorExtensions.x-emit-default-value}})]
+        {{#deprecated}}
+        [Obsolete]
+        {{/deprecated}}
+        public {{{complexType}}}{{^complexType}}{{{datatypeWithEnum}}}{{/complexType}}{{^isContainer}}{{^required}}?{{/required}}{{/isContainer}} {{name}} { get; set; }
+        {{#isReadOnly}}
+
+        /// <summary>
+        /// Returns false as {{name}} should not be serialized given that it's read-only.
+        /// </summary>
+        /// <returns>false (boolean)</returns>
+        public bool ShouldSerialize{{name}}()
+        {
+            return false;
+        }
+        {{/isReadOnly}}
+        {{/conditionalSerialization}}
+        {{#conditionalSerialization}}
+        {{#isReadOnly}}
+        [DataMember(Name = "{{baseName}}"{{#required}}, IsRequired = true{{/required}}, EmitDefaultValue = {{#vendorExtensions.x-emit-default-value}}true{{/vendorExtensions.x-emit-default-value}}{{^vendorExtensions.x-emit-default-value}}{{#isBoolean}}true{{/isBoolean}}{{^isBoolean}}{{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/isBoolean}}{{/vendorExtensions.x-emit-default-value}})]
+        {{#deprecated}}
+        [Obsolete]
+        {{/deprecated}}
+        public {{{complexType}}}{{^complexType}}{{{datatypeWithEnum}}}{{/complexType}}{{^isContainer}}{{^required}}?{{/required}}{{/isContainer}} {{name}} { get; set; }
+
+
+        /// <summary>
+        /// Returns false as {{name}} should not be serialized given that it's read-only.
+        /// </summary>
+        /// <returns>false (boolean)</returns>
+        public bool ShouldSerialize{{name}}()
+        {
+            return false;
+        }
+        {{/isReadOnly}}
+
+        {{^isReadOnly}}
+        [DataMember(Name = "{{baseName}}"{{#required}}, IsRequired = true{{/required}}, EmitDefaultValue = {{#vendorExtensions.x-emit-default-value}}true{{/vendorExtensions.x-emit-default-value}}{{^vendorExtensions.x-emit-default-value}}{{#isBoolean}}true{{/isBoolean}}{{^isBoolean}}{{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/isBoolean}}{{/vendorExtensions.x-emit-default-value}})]
+        {{#deprecated}}
+        [Obsolete]
+        {{/deprecated}}
+        public {{{complexType}}}{{^complexType}}{{{datatypeWithEnum}}}{{/complexType}}{{^isContainer}}{{^required}}?{{/required}}{{/isContainer}} {{name}}
+        {
+            get{ return _{{name}};}
+            set
+            {
+                _{{name}} = value;
+                _flag{{name}} = true;
+            }
+        }
+        private {{{complexType}}}{{^complexType}}{{{datatypeWithEnum}}}{{/complexType}}{{^isContainer}}{{^required}}?{{/required}}{{/isContainer}} _{{name}};
+        private bool _flag{{name}};
+
+        /// <summary>
+        /// Returns false as {{name}} should not be serialized given that it's read-only.
+        /// </summary>
+        /// <returns>false (boolean)</returns>
+        public bool ShouldSerialize{{name}}()
+        {
+            return _flag{{name}};
+        }
+        {{/isReadOnly}}
+        {{/conditionalSerialization}}
+        {{/isEnum}}
+        {{/vars}}
+    {{#hasRequired}}
+    {{^hasOnlyReadOnly}}
+        /// <summary>
+        /// Initializes a new instance of the <see cref="{{classname}}" /> class.
+        /// </summary>
+        [JsonConstructorAttribute]
+        {{^isAdditionalPropertiesTrue}}
+        protected {{classname}}() { }
+        {{/isAdditionalPropertiesTrue}}
+        {{#isAdditionalPropertiesTrue}}
+        protected {{classname}}()
+        {
+            this.AdditionalProperties = new Dictionary<string, object>();
+        }
+        {{/isAdditionalPropertiesTrue}}
+    {{/hasOnlyReadOnly}}
+    {{/hasRequired}}
+        /// <summary>
+        /// Initializes a new instance of the <see cref="{{classname}}" /> class.
+        /// </summary>
+        {{#readWriteVars}}
+        /// <param name="{{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}">{{description}}{{^description}}{{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}{{/description}}{{#required}} (required){{/required}}{{#defaultValue}} (default to {{.}}){{/defaultValue}}.</param>
+        {{/readWriteVars}}
+    {{#hasOnlyReadOnly}}
+        [JsonConstructorAttribute]
+    {{/hasOnlyReadOnly}}
+        public {{classname}}({{#readWriteVars}}{{{datatypeWithEnum}}}{{#isEnum}}{{^isContainer}}{{^required}}?{{/required}}{{/isContainer}}{{/isEnum}} {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}} = {{#defaultValue}}{{^isDateTime}}{{{defaultValue}}}{{/isDateTime}}{{#isDateTime}}default({{{datatypeWithEnum}}}){{/isDateTime}}{{/defaultValue}}{{^defaultValue}}default({{{datatypeWithEnum}}}{{#isEnum}}{{^isContainer}}{{^required}}?{{/required}}{{/isContainer}}{{/isEnum}}){{/defaultValue}}{{^-last}}, {{/-last}}{{/readWriteVars}}){{#parent}} : base({{#parentVars}}{{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}{{^-last}}, {{/-last}}{{/parentVars}}){{/parent}}
+        {
+            {{#vars}}
+            {{^isInherited}}
+            {{^isReadOnly}}
+            {{#required}}
+            {{^conditionalSerialization}}
+            {{^vendorExtensions.x-csharp-value-type}}
+            // to ensure "{{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}" is required (not null)
+            if ({{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}} == null) {
+                throw new ArgumentNullException("{{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}} is a required property for {{classname}} and cannot be null");
+            }
+            this.{{name}} = {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}};
+            {{/vendorExtensions.x-csharp-value-type}}
+            {{#vendorExtensions.x-csharp-value-type}}
+            this.{{name}} = {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}};
+            {{/vendorExtensions.x-csharp-value-type}}
+            {{/conditionalSerialization}}
+            {{#conditionalSerialization}}
+            {{^vendorExtensions.x-csharp-value-type}}
+            // to ensure "{{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}" is required (not null)
+            if ({{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}} == null) {
+                throw new ArgumentNullException("{{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}} is a required property for {{classname}} and cannot be null");
+            }
+            this._{{name}} = {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}};
+            {{/vendorExtensions.x-csharp-value-type}}
+            {{#vendorExtensions.x-csharp-value-type}}
+            this._{{name}} = {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}};
+            {{/vendorExtensions.x-csharp-value-type}}
+            {{/conditionalSerialization}}
+            {{/required}}
+            {{/isReadOnly}}
+            {{/isInherited}}
+            {{/vars}}
+            {{#vars}}
+            {{^isInherited}}
+            {{^isReadOnly}}
+            {{^required}}
+            {{#defaultValue}}
+            {{^vendorExtensions.x-csharp-value-type}}
+            // use default value if no "{{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}" provided
+            this.{{name}} = {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}} ?? {{{defaultValue}}};
+            {{/vendorExtensions.x-csharp-value-type}}
+            {{#vendorExtensions.x-csharp-value-type}}
+            this.{{name}} = {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}};
+            {{/vendorExtensions.x-csharp-value-type}}
+            {{/defaultValue}}
+            {{^defaultValue}}
+            {{^conditionalSerialization}}
+            this.{{name}} = {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}};
+            {{/conditionalSerialization}}
+            {{#conditionalSerialization}}
+            this._{{name}} = {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}};
+            {{/conditionalSerialization}}
+            {{/defaultValue}}
+            {{/required}}
+            {{/isReadOnly}}
+            {{/isInherited}}
+            {{/vars}}
+            {{#isAdditionalPropertiesTrue}}
+            this.AdditionalProperties = new Dictionary<string, object>();
+            {{/isAdditionalPropertiesTrue}}
+        }
+
+        {{#vars}}
+        {{^isInherited}}
+        {{^isEnum}}
+        /// <summary>
+        /// {{description}}{{^description}}Gets or Sets {{{name}}}{{/description}}
+        /// </summary>{{#description}}
+        /// <value>{{.}}</value>{{/description}}
+        {{^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}}{{#isBoolean}}true{{/isBoolean}}{{^isBoolean}}{{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/isBoolean}}{{/vendorExtensions.x-emit-default-value}})]
+        {{#isDate}}
+        [JsonConverter(typeof(OpenAPIDateConverter))]
+        {{/isDate}}
+        {{#deprecated}}
+        [Obsolete]
+        {{/deprecated}}
+        public {{{dataType}}} {{name}} { get; {{#isReadOnly}}private {{/isReadOnly}}set; }
+
+        {{#isReadOnly}}
+        /// <summary>
+        /// Returns false as {{name}} should not be serialized given that it's read-only.
+        /// </summary>
+        /// <returns>false (boolean)</returns>
+        public bool ShouldSerialize{{name}}()
+        {
+            return false;
+        }
+        {{/isReadOnly}}
+        {{/conditionalSerialization}}
+        {{#conditionalSerialization}}
+        {{#isReadOnly}}
+        [DataMember(Name = "{{baseName}}"{{#required}}, IsRequired = true{{/required}}, EmitDefaultValue = {{#vendorExtensions.x-emit-default-value}}true{{/vendorExtensions.x-emit-default-value}}{{^vendorExtensions.x-emit-default-value}}{{#isBoolean}}true{{/isBoolean}}{{^isBoolean}}{{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/isBoolean}}{{/vendorExtensions.x-emit-default-value}})]
+        {{#isDate}}
+        [JsonConverter(typeof(OpenAPIDateConverter))]
+        {{/isDate}}
+        {{#deprecated}}
+        [Obsolete]
+        {{/deprecated}}
+        public {{{dataType}}} {{name}} { get; private set; }
+
+        /// <summary>
+        /// Returns false as {{name}} should not be serialized given that it's read-only.
+        /// </summary>
+        /// <returns>false (boolean)</returns>
+        public bool ShouldSerialize{{name}}()
+        {
+            return false;
+        }
+        {{/isReadOnly}}
+        {{^isReadOnly}}
+        {{#isDate}}
+        [JsonConverter(typeof(OpenAPIDateConverter))]
+        {{/isDate}}
+        [DataMember(Name = "{{baseName}}"{{#required}}, IsRequired = true{{/required}}, EmitDefaultValue = {{#vendorExtensions.x-emit-default-value}}true{{/vendorExtensions.x-emit-default-value}}{{^vendorExtensions.x-emit-default-value}}{{#isBoolean}}true{{/isBoolean}}{{^isBoolean}}{{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/isBoolean}}{{/vendorExtensions.x-emit-default-value}})]
+        {{#deprecated}}
+        [Obsolete]
+        {{/deprecated}}
+        public {{{dataType}}} {{name}}
+        {
+            get{ return _{{name}};}
+            set
+            {
+                _{{name}} = value;
+                _flag{{name}} = true;
+            }
+        }
+        private {{{dataType}}} _{{name}};
+        private bool _flag{{name}};
+
+        /// <summary>
+        /// Returns false as {{name}} should not be serialized given that it's read-only.
+        /// </summary>
+        /// <returns>false (boolean)</returns>
+        public bool ShouldSerialize{{name}}()
+        {
+            return _flag{{name}};
+        }
+        {{/isReadOnly}}
+        {{/conditionalSerialization}}
+        {{/isEnum}}
+        {{/isInherited}}
+        {{/vars}}
+        {{#isAdditionalPropertiesTrue}}
+        /// <summary>
+        /// Gets or Sets additional properties
+        /// </summary>
+        [JsonExtensionData]
+        public IDictionary<string, object> AdditionalProperties { get; set; }
+
+        {{/isAdditionalPropertiesTrue}}
+        /// <summary>
+        /// Returns the string presentation of the object
+        /// </summary>
+        /// <returns>String presentation of the object</returns>
+        public override string ToString()
+        {
+            var sb = new StringBuilder();
+            sb.Append("class {{classname}} {\n");
+            {{#parent}}
+            sb.Append("  ").Append(base.ToString().Replace("\n", "\n  ")).Append("\n");
+            {{/parent}}
+            {{#vars}}
+            sb.Append("  {{name}}: ").Append({{name}}).Append("\n");
+            {{/vars}}
+            {{#isAdditionalPropertiesTrue}}
+            sb.Append("  AdditionalProperties: ").Append(AdditionalProperties).Append("\n");
+            {{/isAdditionalPropertiesTrue}}
+            sb.Append("}\n");
+            return sb.ToString();
+        }
+
+        /// <summary>
+        /// Returns the JSON string presentation of the object
+        /// </summary>
+        /// <returns>JSON string presentation of the object</returns>
+        public {{#parent}}{{^isArray}}{{^isMap}}override {{/isMap}}{{/isArray}}{{/parent}}{{^parent}}virtual {{/parent}}string ToJson()
+        {
+            return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented);
+        }
+
+        /// <summary>
+        /// Returns true if objects are equal
+        /// </summary>
+        /// <param name="input">Object to be compared</param>
+        /// <returns>Boolean</returns>
+        public override bool Equals(object input)
+        {
+            {{#useCompareNetObjects}}
+            return OpenAPIClientUtils.compareLogic.Compare(this, input as {{classname}}).AreEqual;
+            {{/useCompareNetObjects}}
+            {{^useCompareNetObjects}}
+            return this.Equals(input as {{classname}});
+            {{/useCompareNetObjects}}
+        }
+
+        /// <summary>
+        /// Returns true if {{classname}} instances are equal
+        /// </summary>
+        /// <param name="input">Instance of {{classname}} to be compared</param>
+        /// <returns>Boolean</returns>
+        public bool Equals({{classname}} input)
+        {
+            {{#useCompareNetObjects}}
+            return OpenAPIClientUtils.compareLogic.Compare(this, input).AreEqual;
+            {{/useCompareNetObjects}}
+            {{^useCompareNetObjects}}
+            if (input == null)
+                return false;
+
+            return {{#vars}}{{#parent}}base.Equals(input) && {{/parent}}{{^isContainer}}
+                (
+                    this.{{name}} == input.{{name}} ||
+                    {{^vendorExtensions.x-is-value-type}}
+                    (this.{{name}} != null &&
+                    this.{{name}}.Equals(input.{{name}}))
+                    {{/vendorExtensions.x-is-value-type}}
+                    {{#vendorExtensions.x-is-value-type}}
+                    this.{{name}}.Equals(input.{{name}})
+                    {{/vendorExtensions.x-is-value-type}}
+                ){{^-last}} && {{/-last}}{{/isContainer}}{{#isContainer}}
+                (
+                    this.{{name}} == input.{{name}} ||
+                    {{^vendorExtensions.x-is-value-type}}this.{{name}} != null &&
+                    input.{{name}} != null &&
+                    {{/vendorExtensions.x-is-value-type}}this.{{name}}.SequenceEqual(input.{{name}})
+                ){{^-last}} && {{/-last}}{{/isContainer}}{{/vars}}{{^vars}}{{#parent}}base.Equals(input){{/parent}}{{^parent}}false{{/parent}}{{/vars}}{{^isAdditionalPropertiesTrue}};{{/isAdditionalPropertiesTrue}}
+                {{#isAdditionalPropertiesTrue}}
+                && (this.AdditionalProperties.Count == input.AdditionalProperties.Count && !this.AdditionalProperties.Except(input.AdditionalProperties).Any());
+                {{/isAdditionalPropertiesTrue}}
+            {{/useCompareNetObjects}}
+        }
+
+        /// <summary>
+        /// Gets the hash code
+        /// </summary>
+        /// <returns>Hash code</returns>
+        public override int GetHashCode()
+        {
+            unchecked // Overflow is fine, just wrap
+            {
+                {{#parent}}
+                int hashCode = base.GetHashCode();
+                {{/parent}}
+                {{^parent}}
+                int hashCode = 41;
+                {{/parent}}
+                {{#vars}}
+                {{^vendorExtensions.x-is-value-type}}
+                if (this.{{name}} != null)
+                    hashCode = hashCode * 59 + this.{{name}}.GetHashCode();
+                {{/vendorExtensions.x-is-value-type}}
+                {{#vendorExtensions.x-is-value-type}}
+                hashCode = hashCode * 59 + this.{{name}}.GetHashCode();
+                {{/vendorExtensions.x-is-value-type}}
+                {{/vars}}
+                {{#isAdditionalPropertiesTrue}}
+                if (this.AdditionalProperties != null)
+                    hashCode = hashCode * 59 + this.AdditionalProperties.GetHashCode();
+                {{/isAdditionalPropertiesTrue}}
+                return hashCode;
+            }
+        }
+
+{{#validatable}}
+{{#discriminator}}
+        /// <summary>
+        /// To validate all properties of the instance
+        /// </summary>
+        /// <param name="validationContext">Validation context</param>
+        /// <returns>Validation Result</returns>
+        IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)
+        {
+            return this.BaseValidate(validationContext);
+        }
+
+        /// <summary>
+        /// To validate all properties of the instance
+        /// </summary>
+        /// <param name="validationContext">Validation context</param>
+        /// <returns>Validation Result</returns>
+        protected IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult> BaseValidate(ValidationContext validationContext)
+        {
+{{/discriminator}}
+{{^discriminator}}
+        /// <summary>
+        /// To validate all properties of the instance
+        /// </summary>
+        /// <param name="validationContext">Validation context</param>
+        /// <returns>Validation Result</returns>
+        IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)
+        {
+{{/discriminator}}
+            {{#parent}}
+            {{^isArray}}
+            {{^isMap}}
+            foreach(var x in BaseValidate(validationContext)) yield return x;
+            {{/isMap}}
+            {{/isArray}}
+            {{/parent}}
+            {{#vars}}
+            {{#hasValidation}}
+            {{^isEnum}}
+            {{#maxLength}}
+            // {{{name}}} ({{{dataType}}}) maxLength
+            if(this.{{{name}}} != null && this.{{{name}}}.Length > {{maxLength}})
+            {
+                yield return new System.ComponentModel.DataAnnotations.ValidationResult("Invalid value for {{{name}}}, length must be less than {{maxLength}}.", new [] { "{{{name}}}" });
+            }
+
+            {{/maxLength}}
+            {{#minLength}}
+            // {{{name}}} ({{{dataType}}}) minLength
+            if(this.{{{name}}} != null && this.{{{name}}}.Length < {{minLength}})
+            {
+                yield return new System.ComponentModel.DataAnnotations.ValidationResult("Invalid value for {{{name}}}, length must be greater than {{minLength}}.", new [] { "{{{name}}}" });
+            }
+
+            {{/minLength}}
+            {{#maximum}}
+            // {{{name}}} ({{{dataType}}}) maximum
+            if(this.{{{name}}} > ({{{dataType}}}){{maximum}})
+            {
+                yield return new System.ComponentModel.DataAnnotations.ValidationResult("Invalid value for {{{name}}}, must be a value less than or equal to {{maximum}}.", new [] { "{{{name}}}" });
+            }
+
+            {{/maximum}}
+            {{#minimum}}
+            // {{{name}}} ({{{dataType}}}) minimum
+            if(this.{{{name}}} < ({{{dataType}}}){{minimum}})
+            {
+                yield return new System.ComponentModel.DataAnnotations.ValidationResult("Invalid value for {{{name}}}, must be a value greater than or equal to {{minimum}}.", new [] { "{{{name}}}" });
+            }
+
+            {{/minimum}}
+            {{#pattern}}
+            {{^isByteArray}}
+            // {{{name}}} ({{{dataType}}}) pattern
+            Regex regex{{{name}}} = new Regex(@"{{{vendorExtensions.x-regex}}}"{{#vendorExtensions.x-modifiers}}{{#-first}}, {{/-first}}RegexOptions.{{{.}}}{{^-last}} | {{/-last}}{{/vendorExtensions.x-modifiers}});
+            if (false == regex{{{name}}}.Match(this.{{{name}}}).Success)
+            {
+                yield return new System.ComponentModel.DataAnnotations.ValidationResult("Invalid value for {{{name}}}, must match a pattern of " + regex{{{name}}}, new [] { "{{{name}}}" });
+            }
+
+            {{/isByteArray}}
+            {{/pattern}}
+            {{/isEnum}}
+            {{/hasValidation}}
+            {{/vars}}
+            yield break;
+        }
+{{/validatable}}
+    }
diff --git a/openapi/templates/csharp-netcore/modelInnerEnum.mustache b/openapi/templates/csharp-netcore/modelInnerEnum.mustache
new file mode 100644
index 00000000000..6af921832af
--- /dev/null
+++ b/openapi/templates/csharp-netcore/modelInnerEnum.mustache
@@ -0,0 +1,26 @@
+        {{^isContainer}}
+        /// <summary>
+        /// {{description}}{{^description}}Defines {{{name}}}{{/description}}
+        /// </summary>
+        {{#description}}
+        /// <value>{{.}}</value>
+        {{/description}}
+        {{#isString}}
+        [JsonConverter(typeof(StringEnumConverter))]
+        {{/isString}}
+        {{>visibility}} enum {{datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}{{#vendorExtensions.x-enum-byte}}: byte{{/vendorExtensions.x-enum-byte}}
+        {
+            {{#allowableValues}}
+            {{#enumVars}}
+            /// <summary>
+            /// Enum {{name}} for value: {{value}}
+            /// </summary>
+            {{#isString}}
+            [EnumMember(Value = "{{{value}}}")]
+            {{/isString}}
+            {{name}}{{^isString}} = {{{value}}}{{/isString}}{{#isString}} = {{-index}}{{/isString}}{{^-last}},{{/-last}}
+
+            {{/enumVars}}
+            {{/allowableValues}}
+        }
+        {{/isContainer}}
diff --git a/openapi/templates/csharp-netcore/modelOneOf.mustache b/openapi/templates/csharp-netcore/modelOneOf.mustache
new file mode 100644
index 00000000000..d99b82a1e9e
--- /dev/null
+++ b/openapi/templates/csharp-netcore/modelOneOf.mustache
@@ -0,0 +1,272 @@
+    /// <summary>
+    /// {{description}}{{^description}}{{classname}}{{/description}}
+    /// </summary>
+    [JsonConverter(typeof({{classname}}JsonConverter))]
+    [DataContract(Name = "{{{name}}}")]
+    {{>visibility}} partial class {{classname}} : AbstractOpenAPISchema, {{#parent}}{{{.}}}, {{/parent}}IEquatable<{{classname}}>{{#validatable}}, IValidatableObject{{/validatable}}
+    {
+        {{#isNullable}}
+        /// <summary>
+        /// Initializes a new instance of the <see cref="{{classname}}" /> class.
+        /// </summary>
+        public {{classname}}()
+        {
+            this.IsNullable = true;
+            this.SchemaType= "oneOf";
+        }
+
+        {{/isNullable}}
+        {{#oneOf}}
+        /// <summary>
+        /// Initializes a new instance of the <see cref="{{classname}}" /> class
+        /// with the <see cref="{{{.}}}" /> class
+        /// </summary>
+        /// <param name="actualInstance">An instance of {{{.}}}.</param>
+        public {{classname}}({{{.}}} actualInstance)
+        {
+            this.IsNullable = {{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}};
+            this.SchemaType= "oneOf";
+            this.ActualInstance = actualInstance{{^isNullable}} ?? throw new ArgumentException("Invalid instance found. Must not be null."){{/isNullable}};
+        }
+
+        {{/oneOf}}
+
+        private Object _actualInstance;
+
+        /// <summary>
+        /// Gets or Sets ActualInstance
+        /// </summary>
+        public override Object ActualInstance
+        {
+            get
+            {
+                return _actualInstance;
+            }
+            set
+            {
+                {{#oneOf}}
+                {{^-first}}else {{/-first}}if (value.GetType() == typeof({{{.}}}))
+                {
+                    this._actualInstance = value;
+                }
+                {{/oneOf}}
+                else
+                {
+                    throw new ArgumentException("Invalid instance found. Must be the following types:{{#oneOf}} {{{.}}}{{^-last}},{{/-last}}{{/oneOf}}");
+                }
+            }
+        }
+        {{#oneOf}}
+
+        /// <summary>
+        /// Get the actual instance of `{{{.}}}`. If the actual instance is not `{{{.}}}`,
+        /// the InvalidClassException will be thrown
+        /// </summary>
+        /// <returns>An instance of {{{.}}}</returns>
+        public {{{.}}} Get{{{.}}}()
+        {
+            return ({{{.}}})this.ActualInstance;
+        }
+        {{/oneOf}}
+
+        /// <summary>
+        /// Returns the string presentation of the object
+        /// </summary>
+        /// <returns>String presentation of the object</returns>
+        public override string ToString()
+        {
+            var sb = new StringBuilder();
+            sb.Append("class {{classname}} {\n");
+            sb.Append("  ActualInstance: ").Append(this.ActualInstance).Append("\n");
+            sb.Append("}\n");
+            return sb.ToString();
+        }
+
+        /// <summary>
+        /// Returns the JSON string presentation of the object
+        /// </summary>
+        /// <returns>JSON string presentation of the object</returns>
+        public override string ToJson()
+        {
+            return JsonConvert.SerializeObject(this.ActualInstance, {{classname}}.SerializerSettings);
+        }
+
+        /// <summary>
+        /// Converts the JSON string into an instance of {{classname}}
+        /// </summary>
+        /// <param name="jsonString">JSON string</param>
+        /// <returns>An instance of {{classname}}</returns>
+        public static {{classname}} FromJson(string jsonString)
+        {
+            {{classname}} new{{classname}} = null;
+
+            if (string.IsNullOrEmpty(jsonString))
+            {
+                return new{{classname}};
+            }
+            {{#useOneOfDiscriminatorLookup}}
+            {{#discriminator}}
+
+            try
+            {
+                var discriminatorObj = JObject.Parse(jsonString)["{{{propertyBaseName}}}"];
+                string discriminatorValue =  discriminatorObj == null ?string.Empty :discriminatorObj.ToString();
+                switch (discriminatorValue)
+                {
+                    {{#mappedModels}}
+                    case "{{{mappingName}}}":
+                        new{{classname}} = new {{classname}}(JsonConvert.DeserializeObject<{{{modelName}}}>(jsonString, {{classname}}.AdditionalPropertiesSerializerSettings));
+                        return new{{classname}};
+                    {{/mappedModels}}
+                    default:
+                        System.Diagnostics.Debug.WriteLine(string.Format("Failed to lookup discriminator value `{0}` for {{classname}}. Possible values:{{#mappedModels}} {{{mappingName}}}{{/mappedModels}}", discriminatorValue));
+                        break;
+                }
+            }
+            catch (Exception ex)
+            {
+                System.Diagnostics.Debug.WriteLine(string.Format("Failed to parse the json data : `{0}` {1}", jsonString, ex.ToString()));
+            }
+
+            {{/discriminator}}
+            {{/useOneOfDiscriminatorLookup}}
+            int match = 0;
+            List<string> matchedTypes = new List<string>();
+            {{#oneOf}}
+
+            try
+            {
+                // if it does not contains "AdditionalProperties", use SerializerSettings to deserialize
+                if (typeof({{{.}}}).GetProperty("AdditionalProperties") == null)
+                {
+                    new{{classname}} = new {{classname}}(JsonConvert.DeserializeObject<{{{.}}}>(jsonString, {{classname}}.SerializerSettings));
+                }
+                else
+                {
+                    new{{classname}} = new {{classname}}(JsonConvert.DeserializeObject<{{{.}}}>(jsonString, {{classname}}.AdditionalPropertiesSerializerSettings));
+                }
+                matchedTypes.Add("{{{.}}}");
+                match++;
+            }
+            catch (Exception exception)
+            {
+                // deserialization failed, try the next one
+                System.Diagnostics.Debug.WriteLine(string.Format("Failed to deserialize `{0}` into {{{.}}}: {1}", jsonString, exception.ToString()));
+            }
+            {{/oneOf}}
+
+            if (match == 0)
+            {
+                throw new InvalidDataException("The JSON string `" + jsonString + "` cannot be deserialized into any schema defined.");
+            }
+            else if (match > 1)
+            {
+                throw new InvalidDataException("The JSON string `" + jsonString + "` incorrectly matches more than one schema (should be exactly one match): " + matchedTypes);
+            }
+
+            // deserialization is considered successful at this point if no exception has been thrown.
+            return new{{classname}};
+        }
+
+        /// <summary>
+        /// Returns true if objects are equal
+        /// </summary>
+        /// <param name="input">Object to be compared</param>
+        /// <returns>Boolean</returns>
+        public override bool Equals(object input)
+        {
+            {{#useCompareNetObjects}}
+            return OpenAPIClientUtils.compareLogic.Compare(this, input as {{classname}}).AreEqual;
+            {{/useCompareNetObjects}}
+            {{^useCompareNetObjects}}
+            return this.Equals(input as {{classname}});
+            {{/useCompareNetObjects}}
+        }
+
+        /// <summary>
+        /// Returns true if {{classname}} instances are equal
+        /// </summary>
+        /// <param name="input">Instance of {{classname}} to be compared</param>
+        /// <returns>Boolean</returns>
+        public bool Equals({{classname}} input)
+        {
+            {{#useCompareNetObjects}}
+            return OpenAPIClientUtils.compareLogic.Compare(this, input).AreEqual;
+            {{/useCompareNetObjects}}
+            {{^useCompareNetObjects}}
+            if (input == null)
+                return false;
+
+            return this.ActualInstance.Equals(input.ActualInstance);
+            {{/useCompareNetObjects}}
+        }
+
+        /// <summary>
+        /// Gets the hash code
+        /// </summary>
+        /// <returns>Hash code</returns>
+        public override int GetHashCode()
+        {
+            unchecked // Overflow is fine, just wrap
+            {
+                int hashCode = 41;
+                if (this.ActualInstance != null)
+                    hashCode = hashCode * 59 + this.ActualInstance.GetHashCode();
+                return hashCode;
+            }
+        }
+
+        /// <summary>
+        /// To validate all properties of the instance
+        /// </summary>
+        /// <param name="validationContext">Validation context</param>
+        /// <returns>Validation Result</returns>
+        IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)
+        {
+            yield break;
+        }
+    }
+
+    /// <summary>
+    /// Custom JSON converter for {{classname}}
+    /// </summary>
+    public class {{classname}}JsonConverter : JsonConverter
+    {
+        /// <summary>
+        /// To write the JSON string
+        /// </summary>
+        /// <param name="writer">JSON writer</param>
+        /// <param name="value">Object to be converted into a JSON string</param>
+        /// <param name="serializer">JSON Serializer</param>
+        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+        {
+            writer.WriteRawValue((string)(typeof({{classname}}).GetMethod("ToJson").Invoke(value, null)));
+        }
+
+        /// <summary>
+        /// To convert a JSON string into an object
+        /// </summary>
+        /// <param name="reader">JSON reader</param>
+        /// <param name="objectType">Object type</param>
+        /// <param name="existingValue">Existing value</param>
+        /// <param name="serializer">JSON Serializer</param>
+        /// <returns>The object converted from the JSON string</returns>
+        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+        {
+            if(reader.TokenType != JsonToken.Null)
+            {
+                return {{classname}}.FromJson(JObject.Load(reader).ToString(Formatting.None));
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Check if the object can be converted
+        /// </summary>
+        /// <param name="objectType">Object type</param>
+        /// <returns>True if the object can be converted</returns>
+        public override bool CanConvert(Type objectType)
+        {
+            return false;
+        }
+    }
diff --git a/openapi/templates/csharp-netcore/model_doc.mustache b/openapi/templates/csharp-netcore/model_doc.mustache
new file mode 100644
index 00000000000..babf25481c4
--- /dev/null
+++ b/openapi/templates/csharp-netcore/model_doc.mustache
@@ -0,0 +1,22 @@
+{{#models}}
+{{#model}}
+# {{{packageName}}}.{{modelPackage}}.{{{classname}}}
+{{#description}}{{&description}}
+{{/description}}
+
+## Properties
+
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+{{#parent}}
+{{#parentVars}}
+**{{name}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{complexType}}.md){{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#isReadOnly}}[readonly] {{/isReadOnly}}{{#defaultValue}}[default to {{{.}}}]{{/defaultValue}}
+{{/parentVars}}
+{{/parent}}
+{{#vars}}**{{name}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{complexType}}.md){{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#isReadOnly}}[readonly] {{/isReadOnly}}{{#defaultValue}}[default to {{{.}}}]{{/defaultValue}}
+{{/vars}}
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+{{/model}}
+{{/models}}
diff --git a/openapi/templates/csharp-netcore/netcore_project.mustache b/openapi/templates/csharp-netcore/netcore_project.mustache
new file mode 100644
index 00000000000..3be35749bea
--- /dev/null
+++ b/openapi/templates/csharp-netcore/netcore_project.mustache
@@ -0,0 +1,44 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+    <TargetFramework{{#multiTarget}}s{{/multiTarget}}>{{targetFramework}}</TargetFramework{{#multiTarget}}s{{/multiTarget}}>
+    <AssemblyName>{{packageName}}</AssemblyName>
+    <PackageId>{{packageName}}</PackageId>
+    <OutputType>Library</OutputType>
+    <Authors>{{packageAuthors}}</Authors>
+    <Company>{{packageCompany}}</Company>
+    <AssemblyTitle>{{packageTitle}}</AssemblyTitle>
+    <Description>{{packageDescription}}</Description>
+    <Copyright>{{packageCopyright}}</Copyright>
+    <RootNamespace>{{packageName}}</RootNamespace>
+    <Version>{{packageVersion}}</Version>
+    <DocumentationFile>bin\$(Configuration)\$(TargetFramework)\{{packageName}}.xml</DocumentationFile>{{#licenseId}}
+    <PackageLicenseExpression>{{.}}</PackageLicenseExpression>{{/licenseId}}
+    <RepositoryUrl>https://{{{gitHost}}}/{{{gitUserId}}}/{{{gitRepoId}}}.git</RepositoryUrl>
+    <RepositoryType>git</RepositoryType>{{#releaseNote}}
+    <PackageReleaseNotes>{{.}}</PackageReleaseNotes>{{/releaseNote}}{{#packageTags}}
+    <PackageTags>{{{.}}}</PackageTags>{{/packageTags}}
+    {{#nullableReferenceTypes}}
+    <Nullable>annotations</Nullable>
+    {{/nullableReferenceTypes}}
+  </PropertyGroup>
+
+  <ItemGroup>
+    {{#useCompareNetObjects}}
+    <PackageReference Include="CompareNETObjects" Version="4.61.0" />
+    {{/useCompareNetObjects}}
+    <PackageReference Include="JsonSubTypes" Version="1.8.0" />
+    <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
+      {{#useRestSharp}}
+    <PackageReference Include="RestSharp" Version="106.11.7" />
+      {{/useRestSharp}}
+      {{#supportsRetry}}
+    <PackageReference Include="Polly" Version="7.2.1" />
+      {{/supportsRetry}}
+    {{#validatable}}
+    <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
+    {{/validatable}}
+  </ItemGroup>
+
+</Project>
diff --git a/openapi/templates/csharp-netcore/nuspec.mustache b/openapi/templates/csharp-netcore/nuspec.mustache
new file mode 100644
index 00000000000..4ac44f8d347
--- /dev/null
+++ b/openapi/templates/csharp-netcore/nuspec.mustache
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
+    <metadata>
+        <!-- The identifier that must be unique within the hosting gallery -->
+        <id>$id$</id>
+        <title>{{packageTitle}}</title>
+
+        <!-- The package version number that is used when resolving dependencies -->
+        <version>$version$</version>
+
+        <!-- Authors contain text that appears directly on the gallery -->
+        <authors>$author$</authors>
+
+        <!-- Owners are typically nuget.org identities that allow gallery
+             users to easily find other packages by the same owners.  -->
+        <owners>$author$</owners>
+        <requireLicenseAcceptance>false</requireLicenseAcceptance>
+        <developmentDependency>false</developmentDependency>
+
+        <!-- The description can be used in package manager UI. Note that the
+             nuget.org gallery uses information you add in the portal. -->
+        <description>{{packageDescription}}</description>
+        {{#termsOfService}}
+        <copyright>{{.}}</copyright>
+        {{/termsOfService}}
+        {{#licenseUrl}}
+        <licenseUrl>{{.}}</licenseUrl>
+        {{/licenseUrl}}
+
+        <!-- Dependencies are automatically installed when the package is installed -->
+        <dependencies>
+
+            <dependency id="Newtonsoft.Json" version="12.0.3" />
+            <dependency id="RestSharp" version="106.11.7" />
+            {{#useCompareNetObjects}}
+            <dependency id="CompareNETObjects" version="4.61.0" />
+            {{/useCompareNetObjects}}
+            <dependency id="JsonSubTypes" version="1.8.0" />
+            {{#validatable}}
+            <dependency id="System.ComponentModel.Annotations" version="5.0.0" />
+            {{/validatable}}
+
+        </dependencies>
+    </metadata>
+    <files>
+
+        <!-- A readme.txt will be displayed when the package is installed -->
+        <file src="..\..\README.md" target="" />
+        <file src="..\..\docs\**\*.*" target="docs" />
+
+    </files>
+</package>
diff --git a/openapi/templates/csharp-netcore/partial_header.mustache b/openapi/templates/csharp-netcore/partial_header.mustache
new file mode 100644
index 00000000000..1562058ad8a
--- /dev/null
+++ b/openapi/templates/csharp-netcore/partial_header.mustache
@@ -0,0 +1,17 @@
+/*
+ {{#appName}}
+ * {{{.}}}
+ *
+ {{/appName}}
+ {{#appDescription}}
+ * {{{.}}}
+ *
+ {{/appDescription}}
+ {{#version}}
+ * The version of the OpenAPI document: {{{.}}}
+ {{/version}}
+ {{#infoEmail}}
+ * Contact: {{{.}}}
+ {{/infoEmail}}
+ * Generated by: https://github.com/openapitools/openapi-generator.git
+ */
diff --git a/openapi/templates/csharp-netcore/visibility.mustache b/openapi/templates/csharp-netcore/visibility.mustache
new file mode 100644
index 00000000000..a1d1f4163d4
--- /dev/null
+++ b/openapi/templates/csharp-netcore/visibility.mustache
@@ -0,0 +1,1 @@
+{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}}
\ No newline at end of file
diff --git a/scripts/generate.sh b/scripts/generate.sh
index 58174037349..409c01c63eb 100755
--- a/scripts/generate.sh
+++ b/scripts/generate.sh
@@ -203,6 +203,7 @@ dotnet () {
 
   dir="clients/${PROJECT}/dotnet"
 
+  # Workaround until https://github.com/OpenAPITools/openapi-generator/pull/10333 is merged
   openapi-generator-cli generate -i "${SPEC_FILE}" \
     -g csharp-netcore \
     -o "$dir" \
@@ -210,6 +211,7 @@ dotnet () {
     --git-user-id ory \
     --git-repo-id sdk \
     --git-host github.com \
+    -t openapi/templates/csharp-netcore \
     -c ./config/client/dotnet.yml.proc.yml
 }
 


Test patch:
diff --git a/openapi/templates/csharp-netcore/api_test.mustache b/openapi/templates/csharp-netcore/api_test.mustache
new file mode 100644
index 00000000000..3f2f1de5bb3
--- /dev/null
+++ b/openapi/templates/csharp-netcore/api_test.mustache
@@ -0,0 +1,73 @@
+{{>partial_header}}
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reflection;
+{{#useRestSharp}}
+using RestSharp;
+{{/useRestSharp}}
+using Xunit;
+
+using {{packageName}}.Client;
+using {{packageName}}.{{apiPackage}};
+{{#hasImport}}
+// uncomment below to import models
+//using {{packageName}}.{{modelPackage}};
+{{/hasImport}}
+
+namespace {{packageName}}.Test.Api
+{
+    /// <summary>
+    ///  Class for testing {{classname}}
+    /// </summary>
+    /// <remarks>
+    /// This file is automatically generated by OpenAPI Generator (https://openapi-generator.tech).
+    /// Please update the test case below to test the API endpoint.
+    /// </remarks>
+    public class {{classname}}Tests : IDisposable
+    {
+        private {{classname}} instance;
+
+        public {{classname}}Tests()
+        {
+            instance = new {{classname}}();
+        }
+
+        public void Dispose()
+        {
+            // Cleanup when everything is done.
+        }
+
+        /// <summary>
+        /// Test an instance of {{classname}}
+        /// </summary>
+        [Fact]
+        public void {{operationId}}InstanceTest()
+        {
+            // TODO uncomment below to test 'IsType' {{classname}}
+            //Assert.IsType<{{classname}}>(instance);
+        }
+        {{#operations}}
+        {{#operation}}
+
+        /// <summary>
+        /// Test {{operationId}}
+        /// </summary>
+        [Fact]
+        public void {{operationId}}Test()
+        {
+            // TODO uncomment below to test the method and replace null with proper value
+            {{#allParams}}
+            //{{{dataType}}} {{paramName}} = null;
+            {{/allParams}}
+            //{{#returnType}}var response = {{/returnType}}instance.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}});
+            {{#returnType}}
+            //Assert.IsType<{{{.}}}>(response);
+            {{/returnType}}
+        }
+        {{/operation}}
+        {{/operations}}
+    }
+}
diff --git a/openapi/templates/csharp-netcore/model_test.mustache b/openapi/templates/csharp-netcore/model_test.mustache
new file mode 100644
index 00000000000..b4aefe51005
--- /dev/null
+++ b/openapi/templates/csharp-netcore/model_test.mustache
@@ -0,0 +1,81 @@
+{{>partial_header}}
+
+using Xunit;
+
+using System;
+using System.Linq;
+using System.IO;
+using System.Collections.Generic;
+using {{packageName}}.{{apiPackage}};
+using {{packageName}}.{{modelPackage}};
+using {{packageName}}.Client;
+using System.Reflection;
+using Newtonsoft.Json;
+
+{{#models}}
+{{#model}}
+namespace {{packageName}}.Test.Model
+{
+    /// <summary>
+    ///  Class for testing {{classname}}
+    /// </summary>
+    /// <remarks>
+    /// This file is automatically generated by OpenAPI Generator (https://openapi-generator.tech).
+    /// Please update the test case below to test the model.
+    /// </remarks>
+    public class {{classname}}Tests : IDisposable
+    {
+        // TODO uncomment below to declare an instance variable for {{classname}}
+        //private {{classname}} instance;
+
+        public {{classname}}Tests()
+        {
+            // TODO uncomment below to create an instance of {{classname}}
+            //instance = new {{classname}}();
+        }
+
+        public void Dispose()
+        {
+            // Cleanup when everything is done.
+        }
+
+        /// <summary>
+        /// Test an instance of {{classname}}
+        /// </summary>
+        [Fact]
+        public void {{classname}}InstanceTest()
+        {
+            // TODO uncomment below to test "IsType" {{classname}}
+            //Assert.IsType<{{classname}}>(instance);
+        }
+
+        {{#discriminator}}
+        {{#children}}
+        /// <summary>
+        /// Test deserialize a {{classname}} from type {{parent}}
+        /// </summary>
+        [Fact]
+        public void {{classname}}DeserializeFrom{{parent}}Test()
+        {
+            // TODO uncomment below to test deserialize a {{classname}} from type {{parent}}
+            //Assert.IsType<{{parent}}>(JsonConvert.DeserializeObject<{{parent}}>(new {{classname}}().ToJson()));
+        }
+        {{/children}}
+        {{/discriminator}}
+
+        {{#vars}}
+        /// <summary>
+        /// Test the property '{{name}}'
+        /// </summary>
+        [Fact]
+        public void {{name}}Test()
+        {
+            // TODO unit test for the property '{{name}}'
+        }
+        {{/vars}}
+
+    }
+
+}
+{{/model}}
+{{/models}}
diff --git a/openapi/templates/csharp-netcore/netcore_testproject.mustache b/openapi/templates/csharp-netcore/netcore_testproject.mustache
new file mode 100644
index 00000000000..b20a94c76c1
--- /dev/null
+++ b/openapi/templates/csharp-netcore/netcore_testproject.mustache
@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <AssemblyName>{{testPackageName}}</AssemblyName>
+    <RootNamespace>{{testPackageName}}</RootNamespace>
+    <TargetFramework{{#multiTarget}}s{{/multiTarget}}>{{testTargetFramework}}</TargetFramework{{#multiTarget}}s{{/multiTarget}}>
+    <IsPackable>false</IsPackable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.2" />
+    <PackageReference Include="xunit" Version="2.4.1" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\{{packageName}}\{{packageName}}.csproj" />
+  </ItemGroup>
+
+</Project>