From 889bdb89a0136fc4fc4e97781837e3026afeb95f Mon Sep 17 00:00:00 2001 From: HugoMario Date: Mon, 6 Apr 2020 19:16:30 -0500 Subject: [PATCH 1/2] removed unused imports --- .../codegen/v3/generators/dotnet/AbstractCSharpCodegen.java | 1 - .../v3/generators/dotnet/AspNetCoreServerCodegen.java | 5 ----- 2 files changed, 6 deletions(-) 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..aa2b7406d7 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; From 50c9d261e6e2306ad85a30f2df2b189a3f196bee Mon Sep 17 00:00:00 2001 From: HugoMario Date: Wed, 8 Apr 2020 22:00:32 -0500 Subject: [PATCH 2/2] added support for aspnet core 3 --- .../dotnet/AspNetCoreServerCodegen.java | 27 ++- src/main/resources/arguments/aspnetcore.yaml | 2 +- .../aspnetcore/3.0/Dockerfile.mustache | 18 ++ .../3.0/Filters/BasePathFilter.mustache | 51 ++++++ ...eneratePathParamsValidationFilter.mustache | 96 +++++++++++ .../aspnetcore/3.0/Program.mustache | 29 ++++ .../aspnetcore/3.0/Project.csproj.mustache | 20 +++ .../aspnetcore/3.0/Startup.mustache | 154 ++++++++++++++++++ .../aspnetcore/3.0/controller.mustache | 73 +++++++++ .../aspnetcore/3.0/Dockerfile.mustache | 18 ++ .../3.0/Filters/BasePathFilter.mustache | 51 ++++++ ...eneratePathParamsValidationFilter.mustache | 96 +++++++++++ .../mustache/aspnetcore/3.0/Program.mustache | 29 ++++ .../aspnetcore/3.0/Project.csproj.mustache | 20 +++ .../mustache/aspnetcore/3.0/Startup.mustache | 154 ++++++++++++++++++ .../aspnetcore/3.0/controller.mustache | 73 +++++++++ 16 files changed, 905 insertions(+), 6 deletions(-) create mode 100644 src/main/resources/handlebars/aspnetcore/3.0/Dockerfile.mustache create mode 100644 src/main/resources/handlebars/aspnetcore/3.0/Filters/BasePathFilter.mustache create mode 100644 src/main/resources/handlebars/aspnetcore/3.0/Filters/GeneratePathParamsValidationFilter.mustache create mode 100644 src/main/resources/handlebars/aspnetcore/3.0/Program.mustache create mode 100644 src/main/resources/handlebars/aspnetcore/3.0/Project.csproj.mustache create mode 100644 src/main/resources/handlebars/aspnetcore/3.0/Startup.mustache create mode 100644 src/main/resources/handlebars/aspnetcore/3.0/controller.mustache create mode 100644 src/main/resources/mustache/aspnetcore/3.0/Dockerfile.mustache create mode 100644 src/main/resources/mustache/aspnetcore/3.0/Filters/BasePathFilter.mustache create mode 100644 src/main/resources/mustache/aspnetcore/3.0/Filters/GeneratePathParamsValidationFilter.mustache create mode 100644 src/main/resources/mustache/aspnetcore/3.0/Program.mustache create mode 100644 src/main/resources/mustache/aspnetcore/3.0/Project.csproj.mustache create mode 100644 src/main/resources/mustache/aspnetcore/3.0/Startup.mustache create mode 100644 src/main/resources/mustache/aspnetcore/3.0/controller.mustache 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 aa2b7406d7..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 @@ -30,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") @@ -146,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)) { @@ -173,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}} +}