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 5f87c4f3dc8..287f0a64c7e 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 @@ -120,6 +120,9 @@ 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("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")); supportingFiles.add(new SupportingFile("wwwroot" + File.separator + "web.config", packageFolder + File.separator + "wwwroot", "web.config")); diff --git a/modules/swagger-codegen/src/main/resources/aspnetcore/Filters/BasePathDocumentFilter.mustache b/modules/swagger-codegen/src/main/resources/aspnetcore/Filters/BasePathDocumentFilter.mustache new file mode 100644 index 00000000000..a6b2aa9426d --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/aspnetcore/Filters/BasePathDocumentFilter.mustache @@ -0,0 +1,50 @@ +using System.Linq; +using System.Text.RegularExpressions; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace {{packageName}}.Filters +{ + /// + /// BasePath Document Filter sets BasePath property of Swagger and removes it from the individual URL paths + /// + public class BasePathDocumentFilter : IDocumentFilter + { + /// + /// Constructor + /// + /// BasePath to remove from Operations + public BasePathDocumentFilter(string basePath) + { + BasePath = basePath; + } + + /// + /// Gets the BasePath of the Swagger Doc + /// + /// The BasePath of the Swagger Doc + public string BasePath { get; } + + /// + /// Apply the filter + /// + /// SwaggerDocument + /// FilterContext + public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context) + { + swaggerDoc.BasePath = 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); + } + } + } + } +} diff --git a/modules/swagger-codegen/src/main/resources/aspnetcore/Filters/PathParameterValidationFilter.mustache b/modules/swagger-codegen/src/main/resources/aspnetcore/Filters/PathParameterValidationFilter.mustache new file mode 100644 index 00000000000..da388c8d4d1 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/aspnetcore/Filters/PathParameterValidationFilter.mustache @@ -0,0 +1,97 @@ +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Controllers; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace {{packageName}}.Filters +{ + /// + /// Path Parameter Validation Filter + /// + public class PathParameterValidationFilter : IOperationFilter + { + /// + /// Constructor + /// + /// Operation + /// OperationFilterContext + public void Apply(Operation 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 NonBodyParameter) + { + ((NonBodyParameter)swaggerParam).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 NonBodyParameter) + { + ((NonBodyParameter)swaggerParam).MinLength = minLenght; + ((NonBodyParameter)swaggerParam).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 NonBodyParameter) + { + ((NonBodyParameter)swaggerParam).Minimum = rangeMin; + ((NonBodyParameter)swaggerParam).Maximum = rangeMax; + } + } + } + } + } + } +} + diff --git a/modules/swagger-codegen/src/main/resources/aspnetcore/Startup.mustache b/modules/swagger-codegen/src/main/resources/aspnetcore/Startup.mustache index 47009226f2d..fd9178dc536 100644 --- a/modules/swagger-codegen/src/main/resources/aspnetcore/Startup.mustache +++ b/modules/swagger-codegen/src/main/resources/aspnetcore/Startup.mustache @@ -10,6 +10,7 @@ using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; using Swashbuckle.AspNetCore.Swagger; using Swashbuckle.AspNetCore.SwaggerGen; +using {{packageName}}.Filters; namespace {{packageName}} { @@ -62,6 +63,12 @@ namespace {{packageName}} c.CustomSchemaIds(type => type.FriendlyId(true)); c.DescribeAllEnumsAsStrings(); c.IncludeXmlComments($"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}{_hostingEnv.ApplicationName}.xml"); + {{#basePathWithoutHost}} + // Sets the basePath property in the Swagger document generated + c.DocumentFilter("{{{basePathWithoutHost}}}"); + {{/basePathWithoutHost}} + // Do validation of path parameters w. DataAnnotation attributes + c.OperationFilter(); }); } diff --git a/modules/swagger-codegen/src/main/resources/aspnetcore/controller.mustache b/modules/swagger-codegen/src/main/resources/aspnetcore/controller.mustache index e89ca80cc0d..05db73f1a0c 100644 --- a/modules/swagger-codegen/src/main/resources/aspnetcore/controller.mustache +++ b/modules/swagger-codegen/src/main/resources/aspnetcore/controller.mustache @@ -11,6 +11,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Swashbuckle.AspNetCore.SwaggerGen; using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; using {{packageName}}.Attributes; using {{packageName}}.Models; diff --git a/modules/swagger-codegen/src/main/resources/aspnetcore/formParam.mustache b/modules/swagger-codegen/src/main/resources/aspnetcore/formParam.mustache index 1e743e1e4c7..2d42dc2916b 100644 --- a/modules/swagger-codegen/src/main/resources/aspnetcore/formParam.mustache +++ b/modules/swagger-codegen/src/main/resources/aspnetcore/formParam.mustache @@ -1 +1 @@ -{{#isFormParam}}[FromForm]{{&dataType}} {{paramName}}{{/isFormParam}} \ No newline at end of file +{{#isFormParam}}[FromForm]{{#required}}[Required()]{{/required}}{{#pattern}}[RegularExpression("{{{pattern}}}")]{{/pattern}}{{#minLength}}{{#maxLength}}[StringLength({{maxLength}}, MinimumLength={{minLength}}){{/maxLength}}{{/minLength}}{{#minLength}}{{^maxLength}} [MinLength({{minLength}})]{{/maxLength}}{{/minLength}}{{^minLength}}{{#maxLength}} [MaxLength({{maxLength}})]{{/maxLength}}{{/minLength}}{{#minimum}}{{#maximum}}[Range({{minimum}}, {{maximum}})]{{/maximum}}{{/minimum}}{{&dataType}} {{paramName}}{{/isFormParam}} \ No newline at end of file diff --git a/modules/swagger-codegen/src/main/resources/aspnetcore/headerParam.mustache b/modules/swagger-codegen/src/main/resources/aspnetcore/headerParam.mustache index e61cadb1131..45a5be9d7b5 100644 --- a/modules/swagger-codegen/src/main/resources/aspnetcore/headerParam.mustache +++ b/modules/swagger-codegen/src/main/resources/aspnetcore/headerParam.mustache @@ -1 +1 @@ -{{#isHeaderParam}}[FromHeader]{{&dataType}} {{paramName}}{{/isHeaderParam}} \ No newline at end of file +{{#isHeaderParam}}[FromHeader]{{#required}}[Required()]{{/required}}{{#pattern}}[RegularExpression("{{{pattern}}}")]{{/pattern}}{{#minLength}}{{#maxLength}}[StringLength({{maxLength}}, MinimumLength={{minLength}}){{/maxLength}}{{/minLength}}{{#minLength}}{{^maxLength}} [MinLength({{minLength}})]{{/maxLength}}{{/minLength}}{{^minLength}}{{#maxLength}} [MaxLength({{maxLength}})]{{/maxLength}}{{/minLength}}{{#minimum}}{{#maximum}}[Range({{minimum}}, {{maximum}})]{{/maximum}}{{/minimum}}{{&dataType}} {{paramName}}{{/isHeaderParam}} \ No newline at end of file diff --git a/modules/swagger-codegen/src/main/resources/aspnetcore/pathParam.mustache b/modules/swagger-codegen/src/main/resources/aspnetcore/pathParam.mustache index 5aa27eb4cb3..70303432d48 100644 --- a/modules/swagger-codegen/src/main/resources/aspnetcore/pathParam.mustache +++ b/modules/swagger-codegen/src/main/resources/aspnetcore/pathParam.mustache @@ -1 +1 @@ -{{#isPathParam}}[FromRoute]{{&dataType}} {{paramName}}{{/isPathParam}} \ No newline at end of file +{{#isPathParam}}[FromRoute]{{#required}}[Required]{{/required}}{{#pattern}}[RegularExpression("{{{pattern}}}")]{{/pattern}}{{#minLength}}{{#maxLength}}[StringLength({{maxLength}}, MinimumLength={{minLength}}){{/maxLength}}{{/minLength}}{{#minLength}}{{^maxLength}} [MinLength({{minLength}})]{{/maxLength}}{{/minLength}}{{^minLength}}{{#maxLength}} [MaxLength({{maxLength}})]{{/maxLength}}{{/minLength}}{{#minimum}}{{#maximum}}[Range({{minimum}}, {{maximum}})]{{/maximum}}{{/minimum}}{{&dataType}} {{paramName}}{{/isPathParam}} \ No newline at end of file diff --git a/modules/swagger-codegen/src/main/resources/aspnetcore/queryParam.mustache b/modules/swagger-codegen/src/main/resources/aspnetcore/queryParam.mustache index 42ce87a2b7f..e9fa09b005d 100644 --- a/modules/swagger-codegen/src/main/resources/aspnetcore/queryParam.mustache +++ b/modules/swagger-codegen/src/main/resources/aspnetcore/queryParam.mustache @@ -1 +1 @@ -{{#isQueryParam}}[FromQuery]{{&dataType}} {{paramName}}{{/isQueryParam}} \ No newline at end of file +{{#isQueryParam}}[FromQuery]{{#required}}[Required()]{{/required}}{{#pattern}}[RegularExpression("{{{pattern}}}")]{{/pattern}}{{#minLength}}{{#maxLength}}[StringLength({{maxLength}}, MinimumLength={{minLength}}){{/maxLength}}{{/minLength}}{{#minLength}}{{^maxLength}} [MinLength({{minLength}})]{{/maxLength}}{{/minLength}}{{^minLength}}{{#maxLength}} [MaxLength({{maxLength}})]{{/maxLength}}{{/minLength}}{{#minimum}}{{#maximum}}[Range({{minimum}}, {{maximum}})]{{/maximum}}{{/minimum}}{{&dataType}} {{paramName}}{{/isQueryParam}} \ No newline at end of file diff --git a/samples/server/petstore/aspnetcore/src/IO.Swagger/Controllers/PetApi.cs b/samples/server/petstore/aspnetcore/src/IO.Swagger/Controllers/PetApi.cs index 60dfd3bbb87..af0b192f661 100644 --- a/samples/server/petstore/aspnetcore/src/IO.Swagger/Controllers/PetApi.cs +++ b/samples/server/petstore/aspnetcore/src/IO.Swagger/Controllers/PetApi.cs @@ -20,6 +20,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Swashbuckle.AspNetCore.SwaggerGen; using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; using IO.Swagger.Attributes; using IO.Swagger.Models; @@ -60,7 +61,7 @@ namespace IO.Swagger.Controllers [Route("/v2/pet/{petId}")] [ValidateModelState] [SwaggerOperation("DeletePet")] - public virtual IActionResult DeletePet([FromRoute]long? petId, [FromHeader]string apiKey) + public virtual IActionResult DeletePet([FromRoute][Required]long? petId, [FromHeader]string apiKey) { //TODO: Uncomment the next line to return response 400 or use other options such as return this.NotFound(), return this.BadRequest(..), ... // return StatusCode(400); @@ -82,7 +83,7 @@ namespace IO.Swagger.Controllers [SwaggerOperation("FindPetsByStatus")] [SwaggerResponse(200, typeof(List), "successful operation")] [SwaggerResponse(400, typeof(List), "Invalid status value")] - public virtual IActionResult FindPetsByStatus([FromQuery]List status) + public virtual IActionResult FindPetsByStatus([FromQuery][Required()]List status) { //TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ... // return StatusCode(200, default(List)); @@ -112,7 +113,7 @@ namespace IO.Swagger.Controllers [SwaggerOperation("FindPetsByTags")] [SwaggerResponse(200, typeof(List), "successful operation")] [SwaggerResponse(400, typeof(List), "Invalid tag value")] - public virtual IActionResult FindPetsByTags([FromQuery]List tags) + public virtual IActionResult FindPetsByTags([FromQuery][Required()]List tags) { //TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ... // return StatusCode(200, default(List)); @@ -144,7 +145,7 @@ namespace IO.Swagger.Controllers [SwaggerResponse(200, typeof(Pet), "successful operation")] [SwaggerResponse(400, typeof(Pet), "Invalid ID supplied")] [SwaggerResponse(404, typeof(Pet), "Pet not found")] - public virtual IActionResult GetPetById([FromRoute]long? petId) + public virtual IActionResult GetPetById([FromRoute][Required]long? petId) { //TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ... // return StatusCode(200, default(Pet)); @@ -203,7 +204,7 @@ namespace IO.Swagger.Controllers [Route("/v2/pet/{petId}")] [ValidateModelState] [SwaggerOperation("UpdatePetWithForm")] - public virtual IActionResult UpdatePetWithForm([FromRoute]long? petId, [FromForm]string name, [FromForm]string status) + public virtual IActionResult UpdatePetWithForm([FromRoute][Required]long? petId, [FromForm]string name, [FromForm]string status) { //TODO: Uncomment the next line to return response 405 or use other options such as return this.NotFound(), return this.BadRequest(..), ... // return StatusCode(405); @@ -225,7 +226,7 @@ namespace IO.Swagger.Controllers [ValidateModelState] [SwaggerOperation("UploadFile")] [SwaggerResponse(200, typeof(ApiResponse), "successful operation")] - public virtual IActionResult UploadFile([FromRoute]long? petId, [FromForm]string additionalMetadata, [FromForm]System.IO.Stream file) + public virtual IActionResult UploadFile([FromRoute][Required]long? petId, [FromForm]string additionalMetadata, [FromForm]System.IO.Stream file) { //TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ... // return StatusCode(200, default(ApiResponse)); diff --git a/samples/server/petstore/aspnetcore/src/IO.Swagger/Controllers/StoreApi.cs b/samples/server/petstore/aspnetcore/src/IO.Swagger/Controllers/StoreApi.cs index edd7cfae6bc..e7dd7610a77 100644 --- a/samples/server/petstore/aspnetcore/src/IO.Swagger/Controllers/StoreApi.cs +++ b/samples/server/petstore/aspnetcore/src/IO.Swagger/Controllers/StoreApi.cs @@ -20,6 +20,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Swashbuckle.AspNetCore.SwaggerGen; using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; using IO.Swagger.Attributes; using IO.Swagger.Models; @@ -41,7 +42,7 @@ namespace IO.Swagger.Controllers [Route("/v2/store/order/{orderId}")] [ValidateModelState] [SwaggerOperation("DeleteOrder")] - public virtual IActionResult DeleteOrder([FromRoute]string orderId) + public virtual IActionResult DeleteOrder([FromRoute][Required]string orderId) { //TODO: Uncomment the next line to return response 400 or use other options such as return this.NotFound(), return this.BadRequest(..), ... // return StatusCode(400); @@ -92,7 +93,7 @@ namespace IO.Swagger.Controllers [SwaggerResponse(200, typeof(Order), "successful operation")] [SwaggerResponse(400, typeof(Order), "Invalid ID supplied")] [SwaggerResponse(404, typeof(Order), "Order not found")] - public virtual IActionResult GetOrderById([FromRoute]long? orderId) + public virtual IActionResult GetOrderById([FromRoute][Required][Range(1, 5)]long? orderId) { //TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ... // return StatusCode(200, default(Order)); diff --git a/samples/server/petstore/aspnetcore/src/IO.Swagger/Controllers/UserApi.cs b/samples/server/petstore/aspnetcore/src/IO.Swagger/Controllers/UserApi.cs index 85d4e840cf6..ab82c41a1df 100644 --- a/samples/server/petstore/aspnetcore/src/IO.Swagger/Controllers/UserApi.cs +++ b/samples/server/petstore/aspnetcore/src/IO.Swagger/Controllers/UserApi.cs @@ -20,6 +20,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Swashbuckle.AspNetCore.SwaggerGen; using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; using IO.Swagger.Attributes; using IO.Swagger.Models; @@ -98,7 +99,7 @@ namespace IO.Swagger.Controllers [Route("/v2/user/{username}")] [ValidateModelState] [SwaggerOperation("DeleteUser")] - public virtual IActionResult DeleteUser([FromRoute]string username) + public virtual IActionResult DeleteUser([FromRoute][Required]string username) { //TODO: Uncomment the next line to return response 400 or use other options such as return this.NotFound(), return this.BadRequest(..), ... // return StatusCode(400); @@ -125,7 +126,7 @@ namespace IO.Swagger.Controllers [SwaggerResponse(200, typeof(User), "successful operation")] [SwaggerResponse(400, typeof(User), "Invalid username supplied")] [SwaggerResponse(404, typeof(User), "User not found")] - public virtual IActionResult GetUserByName([FromRoute]string username) + public virtual IActionResult GetUserByName([FromRoute][Required]string username) { //TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ... // return StatusCode(200, default(User)); @@ -159,7 +160,7 @@ namespace IO.Swagger.Controllers [SwaggerOperation("LoginUser")] [SwaggerResponse(200, typeof(string), "successful operation")] [SwaggerResponse(400, typeof(string), "Invalid username/password supplied")] - public virtual IActionResult LoginUser([FromQuery]string username, [FromQuery]string password) + public virtual IActionResult LoginUser([FromQuery][Required()]string username, [FromQuery][Required()]string password) { //TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ... // return StatusCode(200, default(string)); @@ -206,7 +207,7 @@ namespace IO.Swagger.Controllers [Route("/v2/user/{username}")] [ValidateModelState] [SwaggerOperation("UpdateUser")] - public virtual IActionResult UpdateUser([FromRoute]string username, [FromBody]User body) + public virtual IActionResult UpdateUser([FromRoute][Required]string username, [FromBody]User body) { //TODO: Uncomment the next line to return response 400 or use other options such as return this.NotFound(), return this.BadRequest(..), ... // return StatusCode(400); diff --git a/samples/server/petstore/aspnetcore/src/IO.Swagger/Filters/BasePathDocumentFilter.cs b/samples/server/petstore/aspnetcore/src/IO.Swagger/Filters/BasePathDocumentFilter.cs new file mode 100644 index 00000000000..dc3592f50e5 --- /dev/null +++ b/samples/server/petstore/aspnetcore/src/IO.Swagger/Filters/BasePathDocumentFilter.cs @@ -0,0 +1,50 @@ +using System.Linq; +using System.Text.RegularExpressions; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace IO.Swagger.Filters +{ + /// + /// BasePath Document Filter sets BasePath property of Swagger and removes it from the individual URL paths + /// + public class BasePathDocumentFilter : IDocumentFilter + { + /// + /// Constructor + /// + /// BasePath to remove from Operations + public BasePathDocumentFilter(string basePath) + { + BasePath = basePath; + } + + /// + /// Gets the BasePath of the Swagger Doc + /// + /// The BasePath of the Swagger Doc + public string BasePath { get; } + + /// + /// Apply the filter + /// + /// SwaggerDocument + /// FilterContext + public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context) + { + swaggerDoc.BasePath = 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); + } + } + } + } +} diff --git a/samples/server/petstore/aspnetcore/src/IO.Swagger/Filters/PathParameterValidationFilter.cs b/samples/server/petstore/aspnetcore/src/IO.Swagger/Filters/PathParameterValidationFilter.cs new file mode 100644 index 00000000000..798d2cef266 --- /dev/null +++ b/samples/server/petstore/aspnetcore/src/IO.Swagger/Filters/PathParameterValidationFilter.cs @@ -0,0 +1,97 @@ +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Controllers; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace IO.Swagger.Filters +{ + /// + /// Path Parameter Validation Filter + /// + public class PathParameterValidationFilter : IOperationFilter + { + /// + /// Constructor + /// + /// Operation + /// OperationFilterContext + public void Apply(Operation 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 NonBodyParameter) + { + ((NonBodyParameter)swaggerParam).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 NonBodyParameter) + { + ((NonBodyParameter)swaggerParam).MinLength = minLenght; + ((NonBodyParameter)swaggerParam).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 NonBodyParameter) + { + ((NonBodyParameter)swaggerParam).Minimum = rangeMin; + ((NonBodyParameter)swaggerParam).Maximum = rangeMax; + } + } + } + } + } + } +} + diff --git a/samples/server/petstore/aspnetcore/src/IO.Swagger/Startup.cs b/samples/server/petstore/aspnetcore/src/IO.Swagger/Startup.cs index 79024f4e6a1..0b7d570bb03 100644 --- a/samples/server/petstore/aspnetcore/src/IO.Swagger/Startup.cs +++ b/samples/server/petstore/aspnetcore/src/IO.Swagger/Startup.cs @@ -19,6 +19,7 @@ using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; using Swashbuckle.AspNetCore.Swagger; using Swashbuckle.AspNetCore.SwaggerGen; +using IO.Swagger.Filters; namespace IO.Swagger { @@ -71,6 +72,10 @@ namespace IO.Swagger c.CustomSchemaIds(type => type.FriendlyId(true)); 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(); }); }