diff --git a/src/main/java/io/swagger/codegen/v3/generators/dotnet/AbstractCSharpCodegen.java b/src/main/java/io/swagger/codegen/v3/generators/dotnet/AbstractCSharpCodegen.java
index f07b54ab09..dd4a697d3f 100644
--- a/src/main/java/io/swagger/codegen/v3/generators/dotnet/AbstractCSharpCodegen.java
+++ b/src/main/java/io/swagger/codegen/v3/generators/dotnet/AbstractCSharpCodegen.java
@@ -8,7 +8,6 @@
import io.swagger.codegen.v3.CodegenModel;
import io.swagger.codegen.v3.CodegenOperation;
import io.swagger.codegen.v3.CodegenProperty;
-import io.swagger.codegen.v3.ISchemaHandler;
import io.swagger.codegen.v3.generators.DefaultCodegenConfig;
import io.swagger.codegen.v3.generators.handlebars.csharp.CsharpHelper;
import io.swagger.codegen.v3.generators.handlebars.lambda.CamelCaseLambda;
diff --git a/src/main/java/io/swagger/codegen/v3/generators/dotnet/AspNetCoreServerCodegen.java b/src/main/java/io/swagger/codegen/v3/generators/dotnet/AspNetCoreServerCodegen.java
index a4c28b3f64..8702b9c09a 100644
--- a/src/main/java/io/swagger/codegen/v3/generators/dotnet/AspNetCoreServerCodegen.java
+++ b/src/main/java/io/swagger/codegen/v3/generators/dotnet/AspNetCoreServerCodegen.java
@@ -2,15 +2,12 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.samskivert.mustache.Mustache;
-import io.swagger.codegen.v3.CodegenArgument;
import io.swagger.codegen.v3.CodegenConstants;
import io.swagger.codegen.v3.CodegenContent;
import io.swagger.codegen.v3.CodegenOperation;
import io.swagger.codegen.v3.CodegenSecurity;
import io.swagger.codegen.v3.CodegenType;
import io.swagger.codegen.v3.SupportingFile;
-import io.swagger.codegen.v3.generators.handlebars.ExtensionHelper;
-import io.swagger.codegen.v3.utils.URLPathUtil;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.security.SecurityScheme;
@@ -19,11 +16,9 @@
import org.slf4j.LoggerFactory;
import java.io.File;
-import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.stream.Collectors;
import static io.swagger.codegen.v3.generators.handlebars.ExtensionHelper.getBooleanValue;
@@ -35,7 +30,7 @@ public class AspNetCoreServerCodegen extends AbstractCSharpCodegen {
private static final String ASP_NET_CORE_VERSION_OPTION = "--aspnet-core-version";
private static final String INTERFACE_ONLY_OPTION = "--interface-only";
private static final String INTERFACE_CONTROLLER_OPTION = "--interface-controller";
- private final String DEFAULT_ASP_NET_CORE_VERSION = "2.2";
+ private final String DEFAULT_ASP_NET_CORE_VERSION = "3.0";
private String aspNetCoreVersion;
@SuppressWarnings("hiding")
@@ -151,13 +146,30 @@ public void processOpts() {
supportingFiles.add(new SupportingFile("Program.mustache", packageFolder, "Program.cs"));
supportingFiles.add(new SupportingFile("Project.csproj.mustache", packageFolder, this.packageName + ".csproj"));
supportingFiles.add(new SupportingFile("Dockerfile.mustache", packageFolder, "Dockerfile"));
- } else{
+ supportingFiles.add(new SupportingFile("Filters" + File.separator + "BasePathFilter.mustache", packageFolder + File.separator + "Filters", "BasePathFilter.cs"));
+ supportingFiles.add(new SupportingFile("Filters" + File.separator + "GeneratePathParamsValidationFilter.mustache", packageFolder + File.separator + "Filters", "GeneratePathParamsValidationFilter.cs"));
+ supportingFiles.add(new SupportingFile("Startup.mustache", packageFolder, "Startup.cs"));
+ } else if (aspNetCoreVersion.equals("2.1")) {
apiTemplateFiles.put("2.1/controller.mustache", ".cs");
addInterfaceControllerTemplate();
supportingFiles.add(new SupportingFile("2.1/Program.mustache", packageFolder, "Program.cs"));
supportingFiles.add(new SupportingFile("2.1/Project.csproj.mustache", packageFolder, this.packageName + ".csproj"));
supportingFiles.add(new SupportingFile("2.1/Dockerfile.mustache", packageFolder, "Dockerfile"));
+ supportingFiles.add(new SupportingFile("Filters" + File.separator + "BasePathFilter.mustache", packageFolder + File.separator + "Filters", "BasePathFilter.cs"));
+ supportingFiles.add(new SupportingFile("Filters" + File.separator + "GeneratePathParamsValidationFilter.mustache", packageFolder + File.separator + "Filters", "GeneratePathParamsValidationFilter.cs"));
+ supportingFiles.add(new SupportingFile("Startup.mustache", packageFolder, "Startup.cs"));
+ } else {
+ apiTemplateFiles.put("3.0/controller.mustache", ".cs");
+ addInterfaceControllerTemplate();
+
+ supportingFiles.add(new SupportingFile("3.0" + File.separator + "Filters" + File.separator + "BasePathFilter.mustache", packageFolder + File.separator + "Filters", "BasePathFilter.cs"));
+ supportingFiles.add(new SupportingFile("3.0" + File.separator + "Filters" + File.separator + "GeneratePathParamsValidationFilter.mustache", packageFolder + File.separator + "Filters", "GeneratePathParamsValidationFilter.cs"));
+
+ supportingFiles.add(new SupportingFile("3.0/Startup.mustache", packageFolder, "Startup.cs"));
+ supportingFiles.add(new SupportingFile("3.0/Program.mustache", packageFolder, "Program.cs"));
+ supportingFiles.add(new SupportingFile("3.0/Project.csproj.mustache", packageFolder, this.packageName + ".csproj"));
+ supportingFiles.add(new SupportingFile("3.0/Dockerfile.mustache", packageFolder, "Dockerfile"));
}
if (!additionalProperties.containsKey(CodegenConstants.API_PACKAGE)) {
@@ -178,15 +190,15 @@ public void processOpts() {
supportingFiles.add(new SupportingFile("gitignore", packageFolder, ".gitignore"));
supportingFiles.add(new SupportingFile("appsettings.json", packageFolder, "appsettings.json"));
- supportingFiles.add(new SupportingFile("Startup.mustache", packageFolder, "Startup.cs"));
+ //supportingFiles.add(new SupportingFile("Startup.mustache", packageFolder, "Startup.cs"));
supportingFiles.add(new SupportingFile("validateModel.mustache", packageFolder + File.separator + "Attributes", "ValidateModelStateAttribute.cs"));
supportingFiles.add(new SupportingFile("web.config", packageFolder, "web.config"));
supportingFiles.add(new SupportingFile("Properties" + File.separator + "launchSettings.json", packageFolder + File.separator + "Properties", "launchSettings.json"));
- supportingFiles.add(new SupportingFile("Filters" + File.separator + "BasePathFilter.mustache", packageFolder + File.separator + "Filters", "BasePathFilter.cs"));
- supportingFiles.add(new SupportingFile("Filters" + File.separator + "GeneratePathParamsValidationFilter.mustache", packageFolder + File.separator + "Filters", "GeneratePathParamsValidationFilter.cs"));
+// supportingFiles.add(new SupportingFile("Filters" + File.separator + "BasePathFilter.mustache", packageFolder + File.separator + "Filters", "BasePathFilter.cs"));
+// supportingFiles.add(new SupportingFile("Filters" + File.separator + "GeneratePathParamsValidationFilter.mustache", packageFolder + File.separator + "Filters", "GeneratePathParamsValidationFilter.cs"));
supportingFiles.add(new SupportingFile("wwwroot" + File.separator + "README.md", packageFolder + File.separator + "wwwroot", "README.md"));
supportingFiles.add(new SupportingFile("wwwroot" + File.separator + "index.html", packageFolder + File.separator + "wwwroot", "index.html"));
diff --git a/src/main/resources/arguments/aspnetcore.yaml b/src/main/resources/arguments/aspnetcore.yaml
index 57f7f7c83a..f239a9524c 100644
--- a/src/main/resources/arguments/aspnetcore.yaml
+++ b/src/main/resources/arguments/aspnetcore.yaml
@@ -1,6 +1,6 @@
arguments:
- option: "--aspnet-core-version"
- description: "aspnetcore version to use, current options are: 2.0, 2.1 and 2.2 (default)"
+ description: "aspnetcore version to use, current options are: 2.0, 2.1, 2.2 and 3.0 (default)"
type: "string"
- option: "--interface-only"
description: "creates interfaces controller only"
diff --git a/src/main/resources/handlebars/aspnetcore/3.0/Dockerfile.mustache b/src/main/resources/handlebars/aspnetcore/3.0/Dockerfile.mustache
new file mode 100644
index 0000000000..a466136810
--- /dev/null
+++ b/src/main/resources/handlebars/aspnetcore/3.0/Dockerfile.mustache
@@ -0,0 +1,18 @@
+FROM mcr.microsoft.com/dotnet/core/sdk:{{aspNetCoreVersion}} AS build-env
+WORKDIR /app
+
+ENV DOTNET_CLI_TELEMETRY_OPTOUT 1
+
+# copy csproj and restore as distinct layers
+COPY *.csproj ./
+RUN dotnet restore
+
+# copy everything else and build
+COPY . ./
+RUN dotnet publish -c Release -o out
+
+# build runtime image
+FROM mcr.microsoft.com/dotnet/core/aspnet:{{aspNetCoreVersion}}
+WORKDIR /app
+COPY --from=build-env /app/out .
+ENTRYPOINT ["dotnet", "{{packageName}}.dll"]
\ No newline at end of file
diff --git a/src/main/resources/handlebars/aspnetcore/3.0/Filters/BasePathFilter.mustache b/src/main/resources/handlebars/aspnetcore/3.0/Filters/BasePathFilter.mustache
new file mode 100644
index 0000000000..a86d7ed6b5
--- /dev/null
+++ b/src/main/resources/handlebars/aspnetcore/3.0/Filters/BasePathFilter.mustache
@@ -0,0 +1,51 @@
+using System.Linq;
+using System.Text.RegularExpressions;
+using Swashbuckle.AspNetCore.Swagger;
+using Swashbuckle.AspNetCore.SwaggerGen;
+using Microsoft.OpenApi.Models;
+
+namespace {{packageName}}.Filters
+{
+ ///
+ /// BasePath Document Filter sets BasePath property of Swagger and removes it from the individual URL paths
+ ///
+ public class BasePathFilter : IDocumentFilter
+ {
+ ///
+ /// Constructor
+ ///
+ /// BasePath to remove from Operations
+ public BasePathFilter(string basePath)
+ {
+ BasePath = basePath;
+ }
+
+ ///
+ /// Gets the BasePath of the Swagger Doc
+ ///
+ /// The BasePath of the Swagger Doc
+ public string BasePath { get; }
+
+ ///
+ /// Apply the filter
+ ///
+ /// OpenApiDocument
+ /// FilterContext
+ public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
+ {
+ swaggerDoc.Servers.Add(new OpenApiServer() { Url = this.BasePath });
+
+ var pathsToModify = swaggerDoc.Paths.Where(p => p.Key.StartsWith(this.BasePath)).ToList();
+
+ foreach (var path in pathsToModify)
+ {
+ if (path.Key.StartsWith(this.BasePath))
+ {
+ string newKey = Regex.Replace(path.Key, $"^{this.BasePath}", string.Empty);
+ swaggerDoc.Paths.Remove(path.Key);
+ swaggerDoc.Paths.Add(newKey, path.Value);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/handlebars/aspnetcore/3.0/Filters/GeneratePathParamsValidationFilter.mustache b/src/main/resources/handlebars/aspnetcore/3.0/Filters/GeneratePathParamsValidationFilter.mustache
new file mode 100644
index 0000000000..1aa62090b2
--- /dev/null
+++ b/src/main/resources/handlebars/aspnetcore/3.0/Filters/GeneratePathParamsValidationFilter.mustache
@@ -0,0 +1,96 @@
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.SwaggerGen;
+
+namespace {{packageName}}.Filters
+{
+ ///
+ /// Path Parameter Validation Rules Filter
+ ///
+ public class GeneratePathParamsValidationFilter : IOperationFilter
+ {
+ ///
+ /// Constructor
+ ///
+ /// Operation
+ /// OperationFilterContext
+ public void Apply(OpenApiOperation operation, OperationFilterContext context)
+ {
+ var pars = context.ApiDescription.ParameterDescriptions;
+
+ foreach (var par in pars)
+ {
+ var swaggerParam = operation.Parameters.SingleOrDefault(p => p.Name == par.Name);
+
+ var attributes = ((ControllerParameterDescriptor)par.ParameterDescriptor).ParameterInfo.CustomAttributes;
+
+ if (attributes != null && attributes.Count() > 0 && swaggerParam != null)
+ {
+ // Required - [Required]
+ var requiredAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(RequiredAttribute));
+ if (requiredAttr != null)
+ {
+ swaggerParam.Required = true;
+ }
+
+ // Regex Pattern [RegularExpression]
+ var regexAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(RegularExpressionAttribute));
+ if (regexAttr != null)
+ {
+ string regex = (string)regexAttr.ConstructorArguments[0].Value;
+ if (swaggerParam is OpenApiParameter)
+ {
+ ((OpenApiParameter)swaggerParam).Schema.Pattern = regex;
+ }
+ }
+
+ // String Length [StringLength]
+ int? minLenght = null, maxLength = null;
+ var stringLengthAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(StringLengthAttribute));
+ if (stringLengthAttr != null)
+ {
+ if (stringLengthAttr.NamedArguments.Count == 1)
+ {
+ minLenght = (int)stringLengthAttr.NamedArguments.Single(p => p.MemberName == "MinimumLength").TypedValue.Value;
+ }
+ maxLength = (int)stringLengthAttr.ConstructorArguments[0].Value;
+ }
+
+ var minLengthAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(MinLengthAttribute));
+ if (minLengthAttr != null)
+ {
+ minLenght = (int)minLengthAttr.ConstructorArguments[0].Value;
+ }
+
+ var maxLengthAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(MaxLengthAttribute));
+ if (maxLengthAttr != null)
+ {
+ maxLength = (int)maxLengthAttr.ConstructorArguments[0].Value;
+ }
+
+ if (swaggerParam is OpenApiParameter)
+ {
+ ((OpenApiParameter)swaggerParam).Schema.MinLength = minLenght;
+ ((OpenApiParameter)swaggerParam).Schema.MaxLength = maxLength;
+ }
+
+ // Range [Range]
+ var rangeAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(RangeAttribute));
+ if (rangeAttr != null)
+ {
+ int rangeMin = (int)rangeAttr.ConstructorArguments[0].Value;
+ int rangeMax = (int)rangeAttr.ConstructorArguments[1].Value;
+
+ if (swaggerParam is OpenApiParameter)
+ {
+ ((OpenApiParameter)swaggerParam).Schema.Minimum = rangeMin;
+ ((OpenApiParameter)swaggerParam).Schema.Maximum = rangeMax;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/resources/handlebars/aspnetcore/3.0/Program.mustache b/src/main/resources/handlebars/aspnetcore/3.0/Program.mustache
new file mode 100644
index 0000000000..b6f0758325
--- /dev/null
+++ b/src/main/resources/handlebars/aspnetcore/3.0/Program.mustache
@@ -0,0 +1,29 @@
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore;
+
+namespace {{packageName}}
+{
+ ///
+ /// Program
+ ///
+ public class Program
+ {
+ ///
+ /// Main
+ ///
+ ///
+ public static void Main(string[] args)
+ {
+ CreateWebHostBuilder(args).Build().Run();
+ }
+
+ ///
+ /// Create the web host builder.
+ ///
+ ///
+ /// IWebHostBuilder
+ public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
+ WebHost.CreateDefaultBuilder(args)
+ .UseStartup();
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/handlebars/aspnetcore/3.0/Project.csproj.mustache b/src/main/resources/handlebars/aspnetcore/3.0/Project.csproj.mustache
new file mode 100644
index 0000000000..6fdcb63123
--- /dev/null
+++ b/src/main/resources/handlebars/aspnetcore/3.0/Project.csproj.mustache
@@ -0,0 +1,20 @@
+
+
+ {{packageName}}
+ {{packageName}}
+ netcoreapp{{aspNetCoreVersion}}
+ true
+ true
+ {{packageName}}
+ {{packageName}}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/handlebars/aspnetcore/3.0/Startup.mustache b/src/main/resources/handlebars/aspnetcore/3.0/Startup.mustache
new file mode 100644
index 0000000000..c27f240580
--- /dev/null
+++ b/src/main/resources/handlebars/aspnetcore/3.0/Startup.mustache
@@ -0,0 +1,154 @@
+{{>partial_header}}
+using System;
+using System.IO;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.OpenApi.Models;
+using Newtonsoft.Json.Converters;
+using Newtonsoft.Json.Serialization;
+using Swashbuckle.AspNetCore.Swagger;
+using Swashbuckle.AspNetCore.SwaggerGen;
+using {{packageName}}.Filters;
+using {{packageName}}.Security;
+
+namespace {{packageName}}
+{
+ ///
+ /// Startup
+ ///
+ public class Startup
+ {
+ private readonly IWebHostEnvironment _hostingEnv;
+
+ private IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor
+ ///
+ ///
+ ///
+ public Startup(IWebHostEnvironment env, IConfiguration configuration)
+ {
+ _hostingEnv = env;
+ Configuration = configuration;
+ }
+
+ ///
+ /// This method gets called by the runtime. Use this method to add services to the container.
+ ///
+ ///
+ public void ConfigureServices(IServiceCollection services)
+ {
+ // Add framework services.
+ services
+ .AddMvc(options =>
+ {
+ options.InputFormatters.RemoveType();
+ options.OutputFormatters.RemoveType();
+ })
+ .AddNewtonsoftJson(opts =>
+ {
+ opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
+ opts.SerializerSettings.Converters.Add(new StringEnumConverter(new CamelCaseNamingStrategy()));
+ })
+ .AddXmlSerializerFormatters();
+
+ {{#authMethods}}
+ {{#isBasic}}
+ services.AddAuthentication(BasicAuthenticationHandler.SchemeName)
+ .AddScheme(BasicAuthenticationHandler.SchemeName, null);
+
+ {{/isBasic}}
+ {{#isBearer}}
+ services.AddAuthentication(BearerAuthenticationHandler.SchemeName)
+ .AddScheme(BearerAuthenticationHandler.SchemeName, null);
+
+ {{/isBearer}}
+ {{#isApiKey}}
+ services.AddAuthentication(ApiKeyAuthenticationHandler.SchemeName)
+ .AddScheme(ApiKeyAuthenticationHandler.SchemeName, null);
+
+ {{/isApiKey}}
+ {{/authMethods}}
+
+ services
+ .AddSwaggerGen(c =>
+ {
+ c.SwaggerDoc("{{#version}}{{{version}}}{{/version}}{{^version}}v1{{/version}}", new OpenApiInfo
+ {
+ Version = "{{#version}}{{{version}}}{{/version}}{{^version}}v1{{/version}}",
+ Title = "{{#appName}}{{{appName}}}{{/appName}}{{^appName}}{{packageName}}{{/appName}}",
+ Description = "{{#appName}}{{{appName}}}{{/appName}}{{^appName}}{{packageName}}{{/appName}} (ASP.NET Core {{aspNetCoreVersion}})",
+ Contact = new OpenApiContact()
+ {
+ Name = "{{#infoName}}{{{infoName}}}{{/infoName}}{{^infoName}}Swagger Codegen Contributors{{/infoName}}",
+ Url = new Uri("{{#infoUrl}}{{{infoUrl}}}{{/infoUrl}}{{^infoUrl}}https://github.com/swagger-api/swagger-codegen{{/infoUrl}}"),
+ Email = "{{#infoEmail}}{{{infoEmail}}}{{/infoEmail}}"
+ },
+ TermsOfService = new Uri("{{#termsOfService}}{{{termsOfService}}}{{/termsOfService}}")
+ });
+ c.CustomSchemaIds(type => type.FullName);
+ c.IncludeXmlComments($"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}{_hostingEnv.ApplicationName}.xml");
+ {{#basePathWithoutHost}}
+ // Sets the basePath property in the Swagger document generated
+ c.DocumentFilter("{{{basePathWithoutHost}}}");
+ {{/basePathWithoutHost}}
+
+ // Include DataAnnotation attributes on Controller Action parameters as Swagger validation rules (e.g required, pattern, ..)
+ // Use [ValidateModelState] on Actions to actually validate it in C# as well!
+ c.OperationFilter();
+ });
+ }
+
+ ///
+ /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ ///
+ ///
+ ///
+ ///
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
+ {
+ app.UseRouting();
+
+ //TODO: Uncomment this if you need wwwroot folder
+ // app.UseStaticFiles();
+
+ app.UseAuthorization();
+
+ app.UseSwagger();
+ app.UseSwaggerUI(c =>
+ {
+ //TODO: Either use the SwaggerGen generated Swagger contract (generated from C# classes)
+ c.SwaggerEndpoint("/swagger/{{#version}}{{{version}}}{{/version}}{{^version}}v1{{/version}}/swagger.json", "{{#appName}}{{{appName}}}{{/appName}}{{^appName}}{{packageName}}{{/appName}}");
+
+ //TODO: Or alternatively use the original Swagger contract that's included in the static files
+ // c.SwaggerEndpoint("/swagger-original.json", "{{#appName}}{{{appName}}}{{/appName}}{{^appName}}{{packageName}}{{/appName}} Original");
+ });
+
+ //TODO: Use Https Redirection
+ // app.UseHttpsRedirection();
+
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapControllers();
+ });
+
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+ else
+ {
+ //TODO: Enable production exception handling (https://docs.microsoft.com/en-us/aspnet/core/fundamentals/error-handling)
+ app.UseExceptionHandler("/Error");
+
+ app.UseHsts();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/handlebars/aspnetcore/3.0/controller.mustache b/src/main/resources/handlebars/aspnetcore/3.0/controller.mustache
new file mode 100644
index 0000000000..a61042ac8f
--- /dev/null
+++ b/src/main/resources/handlebars/aspnetcore/3.0/controller.mustache
@@ -0,0 +1,73 @@
+{{>partial_header}}
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Mvc;
+using Swashbuckle.AspNetCore.Annotations;
+using Swashbuckle.AspNetCore.SwaggerGen;
+using Newtonsoft.Json;
+using System.ComponentModel.DataAnnotations;
+using {{packageName}}.Attributes;
+{{#hasAuthMethods}}using {{packageName}}.Security;{{/hasAuthMethods}}
+using Microsoft.AspNetCore.Authorization;
+using {{modelPackage}};
+
+namespace {{packageName}}.Controllers
+{ {{#operations}}
+ ///
+ /// {{description}}
+ /// {{#description}}
+ [Description("{{description}}")]{{/description}}
+ [ApiController]
+ public class {{classname}}Controller : ControllerBase{{#interfaceController}}, I{{classname}}Controller{{/interfaceController}}
+ { {{#operation}}
+ {{#contents}}
+ ///
+ /// {{#summary}}{{summary}}{{/summary}}
+ ///
+ {{#notes}}/// {{notes}}{{/notes}}{{#parameters}}
+ /// {{description}}{{/parameters}}{{#responses}}
+ /// {{message}}{{/responses}}
+ [{{httpMethod}}]
+ [Route("{{{basePathWithoutHost}}}{{{path}}}")]
+ {{#authMethods}}
+ {{#@first}}
+ {{#isBasic}}
+ [Authorize(AuthenticationSchemes = BasicAuthenticationHandler.SchemeName)]
+ {{/isBasic}}
+ {{#isBearer}}
+ [Authorize(AuthenticationSchemes = BearerAuthenticationHandler.SchemeName)]
+ {{/isBearer}}
+ {{#isApiKey}}
+ [Authorize(AuthenticationSchemes = ApiKeyAuthenticationHandler.SchemeName)]
+ {{/isApiKey}}
+ {{/@first}}
+ {{/authMethods}}
+ [ValidateModelState]
+ [SwaggerOperation("{{operationId}}")]{{#responses}}{{#dataType}}
+ [SwaggerResponse(statusCode: {{code}}, type: typeof({{&dataType}}), description: "{{message}}")]{{/dataType}}{{^dataType}}{{/dataType}}{{/responses}}
+ public virtual IActionResult {{operationId}}({{#parameters}}{{>pathParam}}{{>queryParam}}{{>bodyParam}}{{>formParam}}{{>headerParam}}{{#hasMore}}, {{/hasMore}}{{/parameters}})
+ { {{#responses}}
+{{#dataType}}
+ //TODO: Uncomment the next line to return response {{code}} or use other options such as return this.NotFound(), return this.BadRequest(..), ...
+ // return StatusCode({{code}}, default({{&dataType}}));
+{{/dataType}}
+{{^dataType}}
+ //TODO: Uncomment the next line to return response {{code}} or use other options such as return this.NotFound(), return this.BadRequest(..), ...
+ // return StatusCode({{code}});
+{{/dataType}}{{/responses}}
+{{#returnType}}
+ string exampleJson = null;
+ {{#examples}}
+ exampleJson = "{{{example}}}";
+ {{/examples}}
+ {{#isListCollection}}{{>listReturn}}{{/isListCollection}}{{^isListCollection}}{{#isMapContainer}}{{>mapReturn}}{{/isMapContainer}}{{^isMapContainer}}{{>objectReturn}}{{/isMapContainer}}{{/isListCollection}}
+ {{!TODO: defaultResponse, examples, auth, consumes, produces, nickname, externalDocs, imports, security}}
+ //TODO: Change the data returned
+ return new ObjectResult(example);{{/returnType}}{{^returnType}}
+ throw new NotImplementedException();{{/returnType}}
+ }
+ {{/contents}}
+ {{/operation}}
+ }
+{{/operations}}
+}
diff --git a/src/main/resources/mustache/aspnetcore/3.0/Dockerfile.mustache b/src/main/resources/mustache/aspnetcore/3.0/Dockerfile.mustache
new file mode 100644
index 0000000000..a466136810
--- /dev/null
+++ b/src/main/resources/mustache/aspnetcore/3.0/Dockerfile.mustache
@@ -0,0 +1,18 @@
+FROM mcr.microsoft.com/dotnet/core/sdk:{{aspNetCoreVersion}} AS build-env
+WORKDIR /app
+
+ENV DOTNET_CLI_TELEMETRY_OPTOUT 1
+
+# copy csproj and restore as distinct layers
+COPY *.csproj ./
+RUN dotnet restore
+
+# copy everything else and build
+COPY . ./
+RUN dotnet publish -c Release -o out
+
+# build runtime image
+FROM mcr.microsoft.com/dotnet/core/aspnet:{{aspNetCoreVersion}}
+WORKDIR /app
+COPY --from=build-env /app/out .
+ENTRYPOINT ["dotnet", "{{packageName}}.dll"]
\ No newline at end of file
diff --git a/src/main/resources/mustache/aspnetcore/3.0/Filters/BasePathFilter.mustache b/src/main/resources/mustache/aspnetcore/3.0/Filters/BasePathFilter.mustache
new file mode 100644
index 0000000000..a86d7ed6b5
--- /dev/null
+++ b/src/main/resources/mustache/aspnetcore/3.0/Filters/BasePathFilter.mustache
@@ -0,0 +1,51 @@
+using System.Linq;
+using System.Text.RegularExpressions;
+using Swashbuckle.AspNetCore.Swagger;
+using Swashbuckle.AspNetCore.SwaggerGen;
+using Microsoft.OpenApi.Models;
+
+namespace {{packageName}}.Filters
+{
+ ///
+ /// BasePath Document Filter sets BasePath property of Swagger and removes it from the individual URL paths
+ ///
+ public class BasePathFilter : IDocumentFilter
+ {
+ ///
+ /// Constructor
+ ///
+ /// BasePath to remove from Operations
+ public BasePathFilter(string basePath)
+ {
+ BasePath = basePath;
+ }
+
+ ///
+ /// Gets the BasePath of the Swagger Doc
+ ///
+ /// The BasePath of the Swagger Doc
+ public string BasePath { get; }
+
+ ///
+ /// Apply the filter
+ ///
+ /// OpenApiDocument
+ /// FilterContext
+ public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
+ {
+ swaggerDoc.Servers.Add(new OpenApiServer() { Url = this.BasePath });
+
+ var pathsToModify = swaggerDoc.Paths.Where(p => p.Key.StartsWith(this.BasePath)).ToList();
+
+ foreach (var path in pathsToModify)
+ {
+ if (path.Key.StartsWith(this.BasePath))
+ {
+ string newKey = Regex.Replace(path.Key, $"^{this.BasePath}", string.Empty);
+ swaggerDoc.Paths.Remove(path.Key);
+ swaggerDoc.Paths.Add(newKey, path.Value);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/mustache/aspnetcore/3.0/Filters/GeneratePathParamsValidationFilter.mustache b/src/main/resources/mustache/aspnetcore/3.0/Filters/GeneratePathParamsValidationFilter.mustache
new file mode 100644
index 0000000000..1aa62090b2
--- /dev/null
+++ b/src/main/resources/mustache/aspnetcore/3.0/Filters/GeneratePathParamsValidationFilter.mustache
@@ -0,0 +1,96 @@
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.SwaggerGen;
+
+namespace {{packageName}}.Filters
+{
+ ///
+ /// Path Parameter Validation Rules Filter
+ ///
+ public class GeneratePathParamsValidationFilter : IOperationFilter
+ {
+ ///
+ /// Constructor
+ ///
+ /// Operation
+ /// OperationFilterContext
+ public void Apply(OpenApiOperation operation, OperationFilterContext context)
+ {
+ var pars = context.ApiDescription.ParameterDescriptions;
+
+ foreach (var par in pars)
+ {
+ var swaggerParam = operation.Parameters.SingleOrDefault(p => p.Name == par.Name);
+
+ var attributes = ((ControllerParameterDescriptor)par.ParameterDescriptor).ParameterInfo.CustomAttributes;
+
+ if (attributes != null && attributes.Count() > 0 && swaggerParam != null)
+ {
+ // Required - [Required]
+ var requiredAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(RequiredAttribute));
+ if (requiredAttr != null)
+ {
+ swaggerParam.Required = true;
+ }
+
+ // Regex Pattern [RegularExpression]
+ var regexAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(RegularExpressionAttribute));
+ if (regexAttr != null)
+ {
+ string regex = (string)regexAttr.ConstructorArguments[0].Value;
+ if (swaggerParam is OpenApiParameter)
+ {
+ ((OpenApiParameter)swaggerParam).Schema.Pattern = regex;
+ }
+ }
+
+ // String Length [StringLength]
+ int? minLenght = null, maxLength = null;
+ var stringLengthAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(StringLengthAttribute));
+ if (stringLengthAttr != null)
+ {
+ if (stringLengthAttr.NamedArguments.Count == 1)
+ {
+ minLenght = (int)stringLengthAttr.NamedArguments.Single(p => p.MemberName == "MinimumLength").TypedValue.Value;
+ }
+ maxLength = (int)stringLengthAttr.ConstructorArguments[0].Value;
+ }
+
+ var minLengthAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(MinLengthAttribute));
+ if (minLengthAttr != null)
+ {
+ minLenght = (int)minLengthAttr.ConstructorArguments[0].Value;
+ }
+
+ var maxLengthAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(MaxLengthAttribute));
+ if (maxLengthAttr != null)
+ {
+ maxLength = (int)maxLengthAttr.ConstructorArguments[0].Value;
+ }
+
+ if (swaggerParam is OpenApiParameter)
+ {
+ ((OpenApiParameter)swaggerParam).Schema.MinLength = minLenght;
+ ((OpenApiParameter)swaggerParam).Schema.MaxLength = maxLength;
+ }
+
+ // Range [Range]
+ var rangeAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(RangeAttribute));
+ if (rangeAttr != null)
+ {
+ int rangeMin = (int)rangeAttr.ConstructorArguments[0].Value;
+ int rangeMax = (int)rangeAttr.ConstructorArguments[1].Value;
+
+ if (swaggerParam is OpenApiParameter)
+ {
+ ((OpenApiParameter)swaggerParam).Schema.Minimum = rangeMin;
+ ((OpenApiParameter)swaggerParam).Schema.Maximum = rangeMax;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/resources/mustache/aspnetcore/3.0/Program.mustache b/src/main/resources/mustache/aspnetcore/3.0/Program.mustache
new file mode 100644
index 0000000000..b6f0758325
--- /dev/null
+++ b/src/main/resources/mustache/aspnetcore/3.0/Program.mustache
@@ -0,0 +1,29 @@
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore;
+
+namespace {{packageName}}
+{
+ ///
+ /// Program
+ ///
+ public class Program
+ {
+ ///
+ /// Main
+ ///
+ ///
+ public static void Main(string[] args)
+ {
+ CreateWebHostBuilder(args).Build().Run();
+ }
+
+ ///
+ /// Create the web host builder.
+ ///
+ ///
+ /// IWebHostBuilder
+ public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
+ WebHost.CreateDefaultBuilder(args)
+ .UseStartup();
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/mustache/aspnetcore/3.0/Project.csproj.mustache b/src/main/resources/mustache/aspnetcore/3.0/Project.csproj.mustache
new file mode 100644
index 0000000000..6fdcb63123
--- /dev/null
+++ b/src/main/resources/mustache/aspnetcore/3.0/Project.csproj.mustache
@@ -0,0 +1,20 @@
+
+
+ {{packageName}}
+ {{packageName}}
+ netcoreapp{{aspNetCoreVersion}}
+ true
+ true
+ {{packageName}}
+ {{packageName}}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/mustache/aspnetcore/3.0/Startup.mustache b/src/main/resources/mustache/aspnetcore/3.0/Startup.mustache
new file mode 100644
index 0000000000..c27f240580
--- /dev/null
+++ b/src/main/resources/mustache/aspnetcore/3.0/Startup.mustache
@@ -0,0 +1,154 @@
+{{>partial_header}}
+using System;
+using System.IO;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.OpenApi.Models;
+using Newtonsoft.Json.Converters;
+using Newtonsoft.Json.Serialization;
+using Swashbuckle.AspNetCore.Swagger;
+using Swashbuckle.AspNetCore.SwaggerGen;
+using {{packageName}}.Filters;
+using {{packageName}}.Security;
+
+namespace {{packageName}}
+{
+ ///
+ /// Startup
+ ///
+ public class Startup
+ {
+ private readonly IWebHostEnvironment _hostingEnv;
+
+ private IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor
+ ///
+ ///
+ ///
+ public Startup(IWebHostEnvironment env, IConfiguration configuration)
+ {
+ _hostingEnv = env;
+ Configuration = configuration;
+ }
+
+ ///
+ /// This method gets called by the runtime. Use this method to add services to the container.
+ ///
+ ///
+ public void ConfigureServices(IServiceCollection services)
+ {
+ // Add framework services.
+ services
+ .AddMvc(options =>
+ {
+ options.InputFormatters.RemoveType();
+ options.OutputFormatters.RemoveType();
+ })
+ .AddNewtonsoftJson(opts =>
+ {
+ opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
+ opts.SerializerSettings.Converters.Add(new StringEnumConverter(new CamelCaseNamingStrategy()));
+ })
+ .AddXmlSerializerFormatters();
+
+ {{#authMethods}}
+ {{#isBasic}}
+ services.AddAuthentication(BasicAuthenticationHandler.SchemeName)
+ .AddScheme(BasicAuthenticationHandler.SchemeName, null);
+
+ {{/isBasic}}
+ {{#isBearer}}
+ services.AddAuthentication(BearerAuthenticationHandler.SchemeName)
+ .AddScheme(BearerAuthenticationHandler.SchemeName, null);
+
+ {{/isBearer}}
+ {{#isApiKey}}
+ services.AddAuthentication(ApiKeyAuthenticationHandler.SchemeName)
+ .AddScheme(ApiKeyAuthenticationHandler.SchemeName, null);
+
+ {{/isApiKey}}
+ {{/authMethods}}
+
+ services
+ .AddSwaggerGen(c =>
+ {
+ c.SwaggerDoc("{{#version}}{{{version}}}{{/version}}{{^version}}v1{{/version}}", new OpenApiInfo
+ {
+ Version = "{{#version}}{{{version}}}{{/version}}{{^version}}v1{{/version}}",
+ Title = "{{#appName}}{{{appName}}}{{/appName}}{{^appName}}{{packageName}}{{/appName}}",
+ Description = "{{#appName}}{{{appName}}}{{/appName}}{{^appName}}{{packageName}}{{/appName}} (ASP.NET Core {{aspNetCoreVersion}})",
+ Contact = new OpenApiContact()
+ {
+ Name = "{{#infoName}}{{{infoName}}}{{/infoName}}{{^infoName}}Swagger Codegen Contributors{{/infoName}}",
+ Url = new Uri("{{#infoUrl}}{{{infoUrl}}}{{/infoUrl}}{{^infoUrl}}https://github.com/swagger-api/swagger-codegen{{/infoUrl}}"),
+ Email = "{{#infoEmail}}{{{infoEmail}}}{{/infoEmail}}"
+ },
+ TermsOfService = new Uri("{{#termsOfService}}{{{termsOfService}}}{{/termsOfService}}")
+ });
+ c.CustomSchemaIds(type => type.FullName);
+ c.IncludeXmlComments($"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}{_hostingEnv.ApplicationName}.xml");
+ {{#basePathWithoutHost}}
+ // Sets the basePath property in the Swagger document generated
+ c.DocumentFilter("{{{basePathWithoutHost}}}");
+ {{/basePathWithoutHost}}
+
+ // Include DataAnnotation attributes on Controller Action parameters as Swagger validation rules (e.g required, pattern, ..)
+ // Use [ValidateModelState] on Actions to actually validate it in C# as well!
+ c.OperationFilter();
+ });
+ }
+
+ ///
+ /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ ///
+ ///
+ ///
+ ///
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
+ {
+ app.UseRouting();
+
+ //TODO: Uncomment this if you need wwwroot folder
+ // app.UseStaticFiles();
+
+ app.UseAuthorization();
+
+ app.UseSwagger();
+ app.UseSwaggerUI(c =>
+ {
+ //TODO: Either use the SwaggerGen generated Swagger contract (generated from C# classes)
+ c.SwaggerEndpoint("/swagger/{{#version}}{{{version}}}{{/version}}{{^version}}v1{{/version}}/swagger.json", "{{#appName}}{{{appName}}}{{/appName}}{{^appName}}{{packageName}}{{/appName}}");
+
+ //TODO: Or alternatively use the original Swagger contract that's included in the static files
+ // c.SwaggerEndpoint("/swagger-original.json", "{{#appName}}{{{appName}}}{{/appName}}{{^appName}}{{packageName}}{{/appName}} Original");
+ });
+
+ //TODO: Use Https Redirection
+ // app.UseHttpsRedirection();
+
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapControllers();
+ });
+
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+ else
+ {
+ //TODO: Enable production exception handling (https://docs.microsoft.com/en-us/aspnet/core/fundamentals/error-handling)
+ app.UseExceptionHandler("/Error");
+
+ app.UseHsts();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/mustache/aspnetcore/3.0/controller.mustache b/src/main/resources/mustache/aspnetcore/3.0/controller.mustache
new file mode 100644
index 0000000000..a61042ac8f
--- /dev/null
+++ b/src/main/resources/mustache/aspnetcore/3.0/controller.mustache
@@ -0,0 +1,73 @@
+{{>partial_header}}
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Mvc;
+using Swashbuckle.AspNetCore.Annotations;
+using Swashbuckle.AspNetCore.SwaggerGen;
+using Newtonsoft.Json;
+using System.ComponentModel.DataAnnotations;
+using {{packageName}}.Attributes;
+{{#hasAuthMethods}}using {{packageName}}.Security;{{/hasAuthMethods}}
+using Microsoft.AspNetCore.Authorization;
+using {{modelPackage}};
+
+namespace {{packageName}}.Controllers
+{ {{#operations}}
+ ///
+ /// {{description}}
+ /// {{#description}}
+ [Description("{{description}}")]{{/description}}
+ [ApiController]
+ public class {{classname}}Controller : ControllerBase{{#interfaceController}}, I{{classname}}Controller{{/interfaceController}}
+ { {{#operation}}
+ {{#contents}}
+ ///
+ /// {{#summary}}{{summary}}{{/summary}}
+ ///
+ {{#notes}}/// {{notes}}{{/notes}}{{#parameters}}
+ /// {{description}}{{/parameters}}{{#responses}}
+ /// {{message}}{{/responses}}
+ [{{httpMethod}}]
+ [Route("{{{basePathWithoutHost}}}{{{path}}}")]
+ {{#authMethods}}
+ {{#@first}}
+ {{#isBasic}}
+ [Authorize(AuthenticationSchemes = BasicAuthenticationHandler.SchemeName)]
+ {{/isBasic}}
+ {{#isBearer}}
+ [Authorize(AuthenticationSchemes = BearerAuthenticationHandler.SchemeName)]
+ {{/isBearer}}
+ {{#isApiKey}}
+ [Authorize(AuthenticationSchemes = ApiKeyAuthenticationHandler.SchemeName)]
+ {{/isApiKey}}
+ {{/@first}}
+ {{/authMethods}}
+ [ValidateModelState]
+ [SwaggerOperation("{{operationId}}")]{{#responses}}{{#dataType}}
+ [SwaggerResponse(statusCode: {{code}}, type: typeof({{&dataType}}), description: "{{message}}")]{{/dataType}}{{^dataType}}{{/dataType}}{{/responses}}
+ public virtual IActionResult {{operationId}}({{#parameters}}{{>pathParam}}{{>queryParam}}{{>bodyParam}}{{>formParam}}{{>headerParam}}{{#hasMore}}, {{/hasMore}}{{/parameters}})
+ { {{#responses}}
+{{#dataType}}
+ //TODO: Uncomment the next line to return response {{code}} or use other options such as return this.NotFound(), return this.BadRequest(..), ...
+ // return StatusCode({{code}}, default({{&dataType}}));
+{{/dataType}}
+{{^dataType}}
+ //TODO: Uncomment the next line to return response {{code}} or use other options such as return this.NotFound(), return this.BadRequest(..), ...
+ // return StatusCode({{code}});
+{{/dataType}}{{/responses}}
+{{#returnType}}
+ string exampleJson = null;
+ {{#examples}}
+ exampleJson = "{{{example}}}";
+ {{/examples}}
+ {{#isListCollection}}{{>listReturn}}{{/isListCollection}}{{^isListCollection}}{{#isMapContainer}}{{>mapReturn}}{{/isMapContainer}}{{^isMapContainer}}{{>objectReturn}}{{/isMapContainer}}{{/isListCollection}}
+ {{!TODO: defaultResponse, examples, auth, consumes, produces, nickname, externalDocs, imports, security}}
+ //TODO: Change the data returned
+ return new ObjectResult(example);{{/returnType}}{{^returnType}}
+ throw new NotImplementedException();{{/returnType}}
+ }
+ {{/contents}}
+ {{/operation}}
+ }
+{{/operations}}
+}