diff --git a/docs/generators/aspnetcore.md b/docs/generators/aspnetcore.md index fdb73387f3f7..64a0b8d9a3a9 100644 --- a/docs/generators/aspnetcore.md +++ b/docs/generators/aspnetcore.md @@ -23,7 +23,11 @@ sidebar_label: aspnetcore |useCollection|Deserialize array types to Collection<T> instead of List<T>.| |false| |returnICollection|Return ICollection<T> instead of the concrete type.| |false| |useSwashbuckle|Uses the Swashbuckle.AspNetCore NuGet package for documentation.| |true| +|isLibrary|Is the build a library| |false| |classModifier|Class Modifier can be empty, abstract| || |operationModifier|Operation Modifier can be virtual, abstract or partial| |virtual| |buildTarget|Target to build an application or library| |program| |generateBody|Generates method body.| |true| +|operationIsAsync|Set methods to async or sync.| |false| +|operationResultTask|Set methods result to Task<>.| |false| +|modelClassModifier|Model Class Modifier can be nothing or partial| |partial| diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AspNetCoreServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AspNetCoreServerCodegen.java index 842d7b023388..962da0f3d91b 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AspNetCoreServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AspNetCoreServerCodegen.java @@ -20,6 +20,7 @@ import com.samskivert.mustache.Mustache; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.parser.util.SchemaTypeUtil; import org.openapitools.codegen.*; import org.openapitools.codegen.utils.URLPathUtils; import org.slf4j.Logger; @@ -39,13 +40,17 @@ public class AspNetCoreServerCodegen extends AbstractCSharpCodegen { public static final String ASPNET_CORE_VERSION = "aspnetCoreVersion"; public static final String CLASS_MODIFIER = "classModifier"; public static final String OPERATION_MODIFIER = "operationModifier"; + public static final String OPERATION_IS_ASYNC = "operationIsAsync"; + public static final String OPERATION_RESULT_TASK = "operationResultTask"; public static final String GENERATE_BODY = "generateBody"; public static final String BUILD_TARGET = "buildTarget"; + public static final String MODEL_CLASS_MODIFIER = "modelClassModifier"; public static final String PROJECT_SDK = "projectSdk"; public static final String SDK_WEB = "Microsoft.NET.Sdk.Web"; public static final String SDK_LIB = "Microsoft.NET.Sdk"; public static final String COMPATIBILITY_VERSION = "compatibilityVersion"; + public static final String IS_LIBRARY = "isLibrary"; private String packageGuid = "{" + randomUUID().toString().toUpperCase(Locale.ROOT) + "}"; @@ -55,13 +60,18 @@ public class AspNetCoreServerCodegen extends AbstractCSharpCodegen { private boolean useSwashbuckle = true; protected int serverPort = 8080; protected String serverHost = "0.0.0.0"; - private CliOption aspnetCoreVersion = new CliOption(ASPNET_CORE_VERSION, "ASP.NET Core version: 2.2 (default), 2.1, 2.0 (deprecated)"); + protected CliOption aspnetCoreVersion = new CliOption(ASPNET_CORE_VERSION, "ASP.NET Core version: 2.2 (default), 2.1, 2.0 (deprecated)"); + ; // default to 2.1 private CliOption classModifier = new CliOption(CLASS_MODIFIER, "Class Modifier can be empty, abstract"); private CliOption operationModifier = new CliOption(OPERATION_MODIFIER, "Operation Modifier can be virtual, abstract or partial"); + private CliOption modelClassModifier = new CliOption(MODEL_CLASS_MODIFIER, "Model Class Modifier can be nothing or partial"); private boolean generateBody = true; private CliOption buildTarget = new CliOption("buildTarget", "Target to build an application or library"); private String projectSdk = SDK_WEB; private String compatibilityVersion = "Version_2_1"; + private boolean operationIsAsync = false; + private boolean operationResultTask = false; + private boolean isLibrary = false; public AspNetCoreServerCodegen() { super(); @@ -160,6 +170,10 @@ public AspNetCoreServerCodegen() { "Uses the Swashbuckle.AspNetCore NuGet package for documentation.", useSwashbuckle); + addSwitch(IS_LIBRARY, + "Is the build a library", + isLibrary); + classModifier.addEnum("", "Keep class default with no modifier"); classModifier.addEnum("abstract", "Make class abstract"); classModifier.setDefault(""); @@ -182,6 +196,21 @@ public AspNetCoreServerCodegen() { "Generates method body.", generateBody); + addSwitch(OPERATION_IS_ASYNC, + "Set methods to async or sync.", + operationIsAsync); + + addSwitch(OPERATION_RESULT_TASK, + "Set methods result to Task<>.", + operationResultTask); + + modelClassModifier.setType("String"); + modelClassModifier.addEnum("", "Keep model class default with no modifier"); + modelClassModifier.addEnum("partial", "Make model class partial"); + modelClassModifier.setDefault("partial"); + modelClassModifier.setOptValue(modelClassModifier.getDefault()); + addOption(modelClassModifier.getOpt(), modelClassModifier.getDescription(), modelClassModifier.getOptValue()); + } @Override @@ -210,34 +239,36 @@ public void preprocessOpenAPI(OpenAPI openAPI) { @Override public void processOpts() { super.processOpts(); - boolean isLibrary = false; if (additionalProperties.containsKey(CodegenConstants.OPTIONAL_PROJECT_GUID)) { setPackageGuid((String) additionalProperties.get(CodegenConstants.OPTIONAL_PROJECT_GUID)); } additionalProperties.put("packageGuid", packageGuid); - if (additionalProperties.containsKey(USE_SWASHBUCKLE)) { - useSwashbuckle = convertPropertyToBooleanAndWriteBack(USE_SWASHBUCKLE); - } else { - additionalProperties.put(USE_SWASHBUCKLE, useSwashbuckle); - } - // CHeck for the modifiers etc. // The order of the checks is important. - isLibrary = setBuildTarget(); + setBuildTarget(); setClassModifier(); setOperationModifier(); - + setModelClassModifier(); + setUseSwashbuckle(); + setOperationIsAsync(); // CHeck for class modifier if not present set the default value. additionalProperties.put(PROJECT_SDK, projectSdk); additionalProperties.put("dockerTag", packageName.toLowerCase(Locale.ROOT)); - apiPackage = packageName + ".Controllers"; - modelPackage = packageName + ".Models"; + if (!additionalProperties.containsKey(CodegenConstants.API_PACKAGE)) { + apiPackage = packageName + ".Controllers"; + additionalProperties.put(CodegenConstants.API_PACKAGE, apiPackage); + } + + if (!additionalProperties.containsKey(CodegenConstants.MODEL_PACKAGE)) { + modelPackage = packageName + ".Models"; + additionalProperties.put(CodegenConstants.MODEL_PACKAGE, modelPackage); + } String packageFolder = sourceFolder + File.separator + packageName; @@ -259,8 +290,6 @@ public void processOpts() { supportingFiles.add(new SupportingFile("Program.mustache", packageFolder, "Program.cs")); supportingFiles.add(new SupportingFile("Properties" + File.separator + "launchSettings.json", packageFolder + File.separator + "Properties", "launchSettings.json")); - } else { - supportingFiles.add(new SupportingFile("Project.nuspec.mustache", packageFolder, packageName + ".nuspec")); // wwwroot files. 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")); @@ -268,6 +297,8 @@ public void processOpts() { supportingFiles.add(new SupportingFile("wwwroot" + File.separator + "openapi-original.mustache", packageFolder + File.separator + "wwwroot", "openapi-original.json")); + } else { + supportingFiles.add(new SupportingFile("Project.nuspec.mustache", packageFolder, packageName + ".nuspec")); } @@ -342,15 +373,23 @@ public String getNullableType(Schema p, String type) { private void setCliOption(CliOption cliOption) throws IllegalArgumentException { if (additionalProperties.containsKey(cliOption.getOpt())) { - cliOption.setOptValue(additionalProperties.get(cliOption.getOpt()).toString()); - if (cliOption.getOptValue() == null) { - cliOption.setOptValue(cliOption.getDefault()); - throw new IllegalArgumentException(cliOption.getOpt() + ": Invalid value '" + additionalProperties.get(cliOption.getOpt()).toString() + "'" + - ". " + cliOption.getDescription()); + // TODO Hack - not sure why the empty strings become boolean. + Object obj = additionalProperties.get(cliOption.getOpt()); + if (!SchemaTypeUtil.BOOLEAN_TYPE.equals(cliOption.getType())) { + if (obj instanceof Boolean) { + obj = ""; + additionalProperties.put(cliOption.getOpt(), obj); + } } + cliOption.setOptValue(obj.toString()); } else { additionalProperties.put(cliOption.getOpt(), cliOption.getOptValue()); } + if (cliOption.getOptValue() == null) { + cliOption.setOptValue(cliOption.getDefault()); + throw new IllegalArgumentException(cliOption.getOpt() + ": Invalid value '" + additionalProperties.get(cliOption.getOpt()).toString() + "'" + + ". " + cliOption.getDescription()); + } } private void setClassModifier() { @@ -362,8 +401,6 @@ private void setClassModifier() { operationModifier.setOptValue(classModifier.getOptValue()); additionalProperties.put(OPERATION_MODIFIER, operationModifier.getOptValue()); LOGGER.warn("classModifier is " + classModifier.getOptValue() + " so forcing operatonModifier to " + operationModifier.getOptValue()); - } else { - setCliOption(operationModifier); } } @@ -382,15 +419,28 @@ private void setOperationModifier() { } } - private boolean setBuildTarget() { - boolean isLibrary = false; + private void setModelClassModifier() { + setCliOption(modelClassModifier); + + // If operation modifier is abstract then dont generate any body + if (isLibrary) { + modelClassModifier.setOptValue(""); + additionalProperties.put(MODEL_CLASS_MODIFIER, modelClassModifier.getOptValue()); + LOGGER.warn("buildTarget is " + buildTarget.getOptValue() + " so removing any modelClassModifier "); + } + } + + private void setBuildTarget() { setCliOption(buildTarget); if ("library".equals(buildTarget.getOptValue())) { isLibrary = true; projectSdk = SDK_LIB; additionalProperties.put(CLASS_MODIFIER, "abstract"); + } else { + isLibrary = false; + projectSdk = SDK_WEB; } - return isLibrary; + additionalProperties.put(IS_LIBRARY, isLibrary); } private void setAspnetCoreVersion(String packageFolder) { @@ -407,4 +457,29 @@ private void setAspnetCoreVersion(String packageFolder) { } additionalProperties.put(COMPATIBILITY_VERSION, compatibilityVersion); } + + private void setUseSwashbuckle() { + if (isLibrary) { + LOGGER.warn("buildTarget is " + buildTarget.getOptValue() + " so changing default isLibrary to false "); + useSwashbuckle = false; + } else { + useSwashbuckle = true; + } + if (additionalProperties.containsKey(USE_SWASHBUCKLE)) { + useSwashbuckle = convertPropertyToBooleanAndWriteBack(USE_SWASHBUCKLE); + } else { + additionalProperties.put(USE_SWASHBUCKLE, useSwashbuckle); + } + } + + private void setOperationIsAsync() { + if (isLibrary) { + operationIsAsync = false; + additionalProperties.put(OPERATION_IS_ASYNC, operationIsAsync); + } else if (additionalProperties.containsKey(OPERATION_IS_ASYNC)) { + operationIsAsync = convertPropertyToBooleanAndWriteBack(OPERATION_IS_ASYNC); + } else { + additionalProperties.put(OPERATION_IS_ASYNC, operationIsAsync); + } + } } diff --git a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Project.csproj.mustache b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Project.csproj.mustache index 296a332ecd97..3714e878dc47 100644 --- a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Project.csproj.mustache +++ b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Project.csproj.mustache @@ -5,6 +5,9 @@ netcoreapp{{aspnetCoreVersion}} true true +{{#isLibrary}} + Library +{{/isLibrary}} {{packageName}} {{packageName}} diff --git a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/README.mustache b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/README.mustache index f2b4ce6f7b59..3dda0f304b5d 100644 --- a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/README.mustache +++ b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/README.mustache @@ -17,7 +17,7 @@ Windows: ``` build.bat ``` - +{{^isLibrary}} ## Run in Docker ``` @@ -25,3 +25,4 @@ cd {{sourceFolder}}/{{packageName}} docker build -t {{dockerTag}} . docker run -p 5000:8080 {{dockerTag}} ``` +{{/isLibrary}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Startup.mustache b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Startup.mustache index ee628b34ca77..f7d0e084937a 100644 --- a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Startup.mustache +++ b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Startup.mustache @@ -44,7 +44,7 @@ namespace {{packageName}} services .AddMvc() {{#compatibilityVersion}} - .SetCompatibilityVersion(CompatibilityVersion.{{compatibilityVersion}}) + .SetCompatibilityVersion (CompatibilityVersion.{{compatibilityVersion}}) {{/compatibilityVersion}} .AddJsonOptions(opts => { @@ -109,7 +109,7 @@ namespace {{packageName}} // c.SwaggerEndpoint("/openapi-original.json", "{{#appName}}{{{appName}}}{{/appName}}{{^appName}}{{packageName}}{{/appName}} Original"); }){{/useSwashbuckle}}; - if (env.IsDevelopment()) +if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } diff --git a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/controller.mustache b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/controller.mustache index 381d16aabef2..eb6430150c2a 100644 --- a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/controller.mustache +++ b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/controller.mustache @@ -1,21 +1,24 @@ {{>partial_header}} using System; -using System.Collections.Generic; +using System.Collections.Generic;{{#operationResultTask}} +using System.Threading.Tasks; +{{/operationResultTask}} using Microsoft.AspNetCore.Mvc;{{#useSwashbuckle}} using Swashbuckle.AspNetCore.Annotations; -using Swashbuckle.AspNetCore.SwaggerGen;{{/useSwashbuckle}} -using Newtonsoft.Json; +using Swashbuckle.AspNetCore.SwaggerGen;{{/useSwashbuckle}}{{^isLibrary}} +using Newtonsoft.Json;{{/isLibrary}} using System.ComponentModel.DataAnnotations; using {{packageName}}.Attributes; -using {{packageName}}.Models; +using {{modelPackage}}; -namespace {{packageName}}.Controllers +namespace {{apiPackage}} { {{#operations}} /// /// {{description}} /// {{#description}} [Description("{{description}}")]{{/description}} - public {{classModifier}} class {{classname}}Controller : ControllerBase + [ApiController] + public {{#classModifier}}{{classModifier}} {{/classModifier}}class {{classname}}Controller : ControllerBase { {{#operation}} /// /// {{#summary}}{{summary}}{{/summary}} @@ -27,8 +30,9 @@ namespace {{packageName}}.Controllers [Route("{{{basePathWithoutHost}}}{{{path}}}")] [ValidateModelState]{{#useSwashbuckle}} [SwaggerOperation("{{operationId}}")]{{#responses}}{{#dataType}} - [SwaggerResponse(statusCode: {{code}}, type: typeof({{&dataType}}), description: "{{message}}")]{{/dataType}}{{^dataType}}{{/dataType}}{{/responses}}{{/useSwashbuckle}} - public {{operationModifier}} IActionResult {{operationId}}({{#allParams}}{{>pathParam}}{{>queryParam}}{{>bodyParam}}{{>formParam}}{{>headerParam}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{^generateBody}};{{/generateBody}} + [SwaggerResponse(statusCode: {{code}}, type: typeof({{&dataType}}), description: "{{message}}")]{{/dataType}}{{^dataType}}{{/dataType}}{{/responses}}{{/useSwashbuckle}}{{^useSwashbuckle}}{{#responses}}{{#dataType}} + [ProducesResponseType(statusCode: {{code}}, type: typeof({{&dataType}}))]{{/dataType}}{{^dataType}}{{/dataType}}{{/responses}}{{/useSwashbuckle}} + public {{operationModifier}} {{#operationResultTask}}{{#operationIsAsync}}async {{/operationIsAsync}}Task<{{/operationResultTask}}IActionResult{{#operationResultTask}}>{{/operationResultTask}} {{operationId}}({{#allParams}}{{>pathParam}}{{>queryParam}}{{>bodyParam}}{{>formParam}}{{>headerParam}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{^generateBody}};{{/generateBody}} {{#generateBody}} { {{#responses}} {{#dataType}} @@ -47,7 +51,7 @@ namespace {{packageName}}.Controllers {{#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}} + return {{#operationResultTask}}Task.FromResult({{/operationResultTask}}new ObjectResult(example){{#operationResultTask}}){{/operationResultTask}};{{/returnType}}{{^returnType}} throw new NotImplementedException();{{/returnType}} } {{/generateBody}} diff --git a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/model.mustache b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/model.mustache index 670e24f4136e..ee6e5e2db771 100644 --- a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/model.mustache +++ b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/model.mustache @@ -9,13 +9,13 @@ using Newtonsoft.Json; {{#models}} {{#model}} -namespace {{packageName}}.Models +namespace {{modelPackage}} { {{#isEnum}}{{>enumClass}}{{/isEnum}}{{^isEnum}} /// /// {{description}} /// [DataContract] - public partial class {{classname}} : {{#parent}}{{{parent}}}, {{/parent}}IEquatable<{{classname}}> + public {{#modelClassModifier}}{{modelClassModifier}} {{/modelClassModifier}} class {{classname}} : {{#parent}}{{{parent}}}, {{/parent}}IEquatable<{{classname}}> { {{#vars}}{{#isEnum}}{{>enumClass}}{{/isEnum}}{{#items.isEnum}}{{#items}}{{>enumClass}}{{/items}}{{/items.isEnum}} /// /// {{^description}}Gets or Sets {{{name}}}{{/description}}{{#description}}{{description}}{{/description}}