diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/AspNetCoreServerCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/AspNetCoreServerCodegen.java index f1bd73de503..22de7a1e6f3 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/AspNetCoreServerCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/AspNetCoreServerCodegen.java @@ -125,8 +125,8 @@ public class AspNetCoreServerCodegen extends AbstractCSharpCodegen { supportingFiles.add(new SupportingFile("Properties" + File.separator + "launchSettings.json", packageFolder + File.separator + "Properties", "launchSettings.json")); - supportingFiles.add(new SupportingFile("Filters" + File.separator + "BasePathDocumentFilter.mustache", packageFolder + File.separator + "Filters", "BasePathDocumentFilter.cs")); - supportingFiles.add(new SupportingFile("Filters" + File.separator + "PathParameterValidationFilter.mustache", packageFolder + File.separator + "Filters", "PathParameterValidationFilter.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/modules/swagger-codegen/src/main/resources/aspnetcore/Filters/BasePathDocumentFilter.mustache b/modules/swagger-codegen/src/main/resources/aspnetcore/Filters/BasePathFilter.mustache similarity index 93% rename from modules/swagger-codegen/src/main/resources/aspnetcore/Filters/BasePathDocumentFilter.mustache rename to modules/swagger-codegen/src/main/resources/aspnetcore/Filters/BasePathFilter.mustache index a6b2aa9426d..bc76e3accc6 100644 --- a/modules/swagger-codegen/src/main/resources/aspnetcore/Filters/BasePathDocumentFilter.mustache +++ b/modules/swagger-codegen/src/main/resources/aspnetcore/Filters/BasePathFilter.mustache @@ -8,13 +8,13 @@ namespace {{packageName}}.Filters /// /// BasePath Document Filter sets BasePath property of Swagger and removes it from the individual URL paths /// - public class BasePathDocumentFilter : IDocumentFilter + public class BasePathFilter : IDocumentFilter { /// /// Constructor /// /// BasePath to remove from Operations - public BasePathDocumentFilter(string basePath) + public BasePathFilter(string basePath) { BasePath = basePath; } diff --git a/modules/swagger-codegen/src/main/resources/aspnetcore/Filters/PathParameterValidationFilter.mustache b/modules/swagger-codegen/src/main/resources/aspnetcore/Filters/GeneratePathParamsValidationFilter.mustache similarity index 97% rename from modules/swagger-codegen/src/main/resources/aspnetcore/Filters/PathParameterValidationFilter.mustache rename to modules/swagger-codegen/src/main/resources/aspnetcore/Filters/GeneratePathParamsValidationFilter.mustache index da388c8d4d1..d857a4a0f96 100644 --- a/modules/swagger-codegen/src/main/resources/aspnetcore/Filters/PathParameterValidationFilter.mustache +++ b/modules/swagger-codegen/src/main/resources/aspnetcore/Filters/GeneratePathParamsValidationFilter.mustache @@ -7,9 +7,9 @@ using Swashbuckle.AspNetCore.SwaggerGen; namespace {{packageName}}.Filters { /// - /// Path Parameter Validation Filter + /// Path Parameter Validation Rules Filter /// - public class PathParameterValidationFilter : IOperationFilter + public class GeneratePathParamsValidationFilter : IOperationFilter { /// /// Constructor diff --git a/modules/swagger-codegen/src/main/resources/aspnetcore/Startup.mustache b/modules/swagger-codegen/src/main/resources/aspnetcore/Startup.mustache index 3d95d167ea0..5b95562dcdc 100644 --- a/modules/swagger-codegen/src/main/resources/aspnetcore/Startup.mustache +++ b/modules/swagger-codegen/src/main/resources/aspnetcore/Startup.mustache @@ -72,10 +72,12 @@ namespace {{packageName}} c.IncludeXmlComments($"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}{_hostingEnv.ApplicationName}.xml"); {{#basePathWithoutHost}} // Sets the basePath property in the Swagger document generated - c.DocumentFilter("{{{basePathWithoutHost}}}"); + c.DocumentFilter("{{{basePathWithoutHost}}}"); {{/basePathWithoutHost}} - // Do validation of path parameters w. DataAnnotation attributes - c.OperationFilter(); + + // 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(); }); } diff --git a/modules/swagger-codegen/src/main/resources/aspnetcore/validateModel.mustache b/modules/swagger-codegen/src/main/resources/aspnetcore/validateModel.mustache index 9f850f71d93..e11aaa5d270 100644 --- a/modules/swagger-codegen/src/main/resources/aspnetcore/validateModel.mustache +++ b/modules/swagger-codegen/src/main/resources/aspnetcore/validateModel.mustache @@ -1,5 +1,9 @@ +using System.ComponentModel.DataAnnotations; +using System.Reflection; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; namespace {{packageName}}.Attributes { @@ -14,10 +18,44 @@ namespace {{packageName}}.Attributes /// public override void OnActionExecuting(ActionExecutingContext context) { + // Per https://blog.markvincze.com/how-to-validate-action-parameters-with-dataannotation-attributes/ + var descriptor = context.ActionDescriptor as ControllerActionDescriptor; + if (descriptor != null) + { + foreach (var parameter in descriptor.MethodInfo.GetParameters()) + { + object args = null; + if (context.ActionArguments.ContainsKey(parameter.Name)) + { + args = context.ActionArguments[parameter.Name]; + } + + ValidateAttributes(parameter, args, context.ModelState); + } + } + if (!context.ModelState.IsValid) { context.Result = new BadRequestObjectResult(context.ModelState); } } + + private void ValidateAttributes(ParameterInfo parameter, object args, ModelStateDictionary modelState) + { + foreach (var attributeData in parameter.CustomAttributes) + { + var attributeInstance = parameter.GetCustomAttribute(attributeData.AttributeType); + + var validationAttribute = attributeInstance as ValidationAttribute; + if (validationAttribute != null) + { + var isValid = validationAttribute.IsValid(args); + if (!isValid) + { + modelState.AddModelError(parameter.Name, validationAttribute.FormatErrorMessage(parameter.Name)); + } + } + } + } } -} \ No newline at end of file +} diff --git a/samples/server/petstore/aspnetcore/src/IO.Swagger/Attributes/ValidateModelStateAttribute.cs b/samples/server/petstore/aspnetcore/src/IO.Swagger/Attributes/ValidateModelStateAttribute.cs index 963f934dc52..07cfabe83cf 100644 --- a/samples/server/petstore/aspnetcore/src/IO.Swagger/Attributes/ValidateModelStateAttribute.cs +++ b/samples/server/petstore/aspnetcore/src/IO.Swagger/Attributes/ValidateModelStateAttribute.cs @@ -1,5 +1,9 @@ +using System.ComponentModel.DataAnnotations; +using System.Reflection; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; namespace IO.Swagger.Attributes { @@ -14,10 +18,44 @@ namespace IO.Swagger.Attributes /// public override void OnActionExecuting(ActionExecutingContext context) { + // Per https://blog.markvincze.com/how-to-validate-action-parameters-with-dataannotation-attributes/ + var descriptor = context.ActionDescriptor as ControllerActionDescriptor; + if (descriptor != null) + { + foreach (var parameter in descriptor.MethodInfo.GetParameters()) + { + object args = null; + if (context.ActionArguments.ContainsKey(parameter.Name)) + { + args = context.ActionArguments[parameter.Name]; + } + + ValidateAttributes(parameter, args, context.ModelState); + } + } + if (!context.ModelState.IsValid) { context.Result = new BadRequestObjectResult(context.ModelState); } } + + private void ValidateAttributes(ParameterInfo parameter, object args, ModelStateDictionary modelState) + { + foreach (var attributeData in parameter.CustomAttributes) + { + var attributeInstance = parameter.GetCustomAttribute(attributeData.AttributeType); + + var validationAttribute = attributeInstance as ValidationAttribute; + if (validationAttribute != null) + { + var isValid = validationAttribute.IsValid(args); + if (!isValid) + { + modelState.AddModelError(parameter.Name, validationAttribute.FormatErrorMessage(parameter.Name)); + } + } + } + } } -} \ No newline at end of file +} diff --git a/samples/server/petstore/aspnetcore/src/IO.Swagger/Filters/BasePathDocumentFilter.cs b/samples/server/petstore/aspnetcore/src/IO.Swagger/Filters/BasePathFilter.cs similarity index 93% rename from samples/server/petstore/aspnetcore/src/IO.Swagger/Filters/BasePathDocumentFilter.cs rename to samples/server/petstore/aspnetcore/src/IO.Swagger/Filters/BasePathFilter.cs index dc3592f50e5..f3c528eada2 100644 --- a/samples/server/petstore/aspnetcore/src/IO.Swagger/Filters/BasePathDocumentFilter.cs +++ b/samples/server/petstore/aspnetcore/src/IO.Swagger/Filters/BasePathFilter.cs @@ -8,13 +8,13 @@ namespace IO.Swagger.Filters /// /// BasePath Document Filter sets BasePath property of Swagger and removes it from the individual URL paths /// - public class BasePathDocumentFilter : IDocumentFilter + public class BasePathFilter : IDocumentFilter { /// /// Constructor /// /// BasePath to remove from Operations - public BasePathDocumentFilter(string basePath) + public BasePathFilter(string basePath) { BasePath = basePath; } diff --git a/samples/server/petstore/aspnetcore/src/IO.Swagger/Filters/PathParameterValidationFilter.cs b/samples/server/petstore/aspnetcore/src/IO.Swagger/Filters/GeneratePathParamsValidationFilter.cs similarity index 97% rename from samples/server/petstore/aspnetcore/src/IO.Swagger/Filters/PathParameterValidationFilter.cs rename to samples/server/petstore/aspnetcore/src/IO.Swagger/Filters/GeneratePathParamsValidationFilter.cs index 798d2cef266..a0e2cb6871d 100644 --- a/samples/server/petstore/aspnetcore/src/IO.Swagger/Filters/PathParameterValidationFilter.cs +++ b/samples/server/petstore/aspnetcore/src/IO.Swagger/Filters/GeneratePathParamsValidationFilter.cs @@ -7,9 +7,9 @@ using Swashbuckle.AspNetCore.SwaggerGen; namespace IO.Swagger.Filters { /// - /// Path Parameter Validation Filter + /// Path Parameter Validation Rules Filter /// - public class PathParameterValidationFilter : IOperationFilter + public class GeneratePathParamsValidationFilter : IOperationFilter { /// /// Constructor diff --git a/samples/server/petstore/aspnetcore/src/IO.Swagger/Startup.cs b/samples/server/petstore/aspnetcore/src/IO.Swagger/Startup.cs index e37b2adfc27..b62913042eb 100644 --- a/samples/server/petstore/aspnetcore/src/IO.Swagger/Startup.cs +++ b/samples/server/petstore/aspnetcore/src/IO.Swagger/Startup.cs @@ -80,9 +80,11 @@ namespace IO.Swagger c.DescribeAllEnumsAsStrings(); c.IncludeXmlComments($"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}{_hostingEnv.ApplicationName}.xml"); // Sets the basePath property in the Swagger document generated - c.DocumentFilter("/v2"); - // Do validation of path parameters w. DataAnnotation attributes - c.OperationFilter(); + c.DocumentFilter("/v2"); + + // 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(); }); }