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 f1bd73de50..22de7a1e6f 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 a6b2aa9426..bc76e3accc 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 da388c8d4d..d857a4a0f9 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 3d95d167ea..5b95562dcd 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 9f850f71d9..e11aaa5d27 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 963f934dc5..07cfabe83c 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 dc3592f50e..f3c528eada 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 798d2cef26..a0e2cb6871 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 e37b2adfc2..b62913042e 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(); }); }