From 774ec4a41d97c684985eb6fd5ef9c2f0274532ba Mon Sep 17 00:00:00 2001 From: etherealjoy <sunn.ssb@live.com> Date: Sat, 21 Sep 2019 11:00:41 +0200 Subject: [PATCH] Add initial support of Swashbuckle5 with asp.net core 3.0 rc1 --- docs/generators/aspnetcore.md | 8 +- .../2.1/Filters/BasePathFilter.5_0.mustache | 51 ++++++ ...atePathParamsValidationFilter.5_0.mustache | 98 ++++++++++++ .../2.1/Project.csproj.3_0.mustache | 24 +++ .../aspnetcore/2.1/Project.csproj.mustache | 10 -- .../aspnetcore/2.1/Startup.3_0.mustache | 150 ++++++++++++++++++ .../resources/aspnetcore/2.1/Startup.mustache | 13 +- .../aspnetcore/.openapi-generator/VERSION | 2 +- 8 files changed, 330 insertions(+), 26 deletions(-) create mode 100644 modules/openapi-generator/src/main/resources/aspnetcore/2.1/Filters/BasePathFilter.5_0.mustache create mode 100644 modules/openapi-generator/src/main/resources/aspnetcore/2.1/Filters/GeneratePathParamsValidationFilter.5_0.mustache create mode 100644 modules/openapi-generator/src/main/resources/aspnetcore/2.1/Project.csproj.3_0.mustache create mode 100644 modules/openapi-generator/src/main/resources/aspnetcore/2.1/Startup.3_0.mustache diff --git a/docs/generators/aspnetcore.md b/docs/generators/aspnetcore.md index 387832c1ab2..6ebcfcf0b8f 100644 --- a/docs/generators/aspnetcore.md +++ b/docs/generators/aspnetcore.md @@ -17,18 +17,14 @@ sidebar_label: aspnetcore |packageGuid|The GUID that will be associated with the C# project| |null| |sourceFolder|source folder for generated code| |src| |compatibilityVersion|ASP.Net Core CompatibilityVersion| |Version_2_2| -|aspnetCoreVersion|ASP.NET Core version: 3.0 (preview4 only), 2.2, 2.1, 2.0 (deprecated)| |2.2| -|swashbuckleVersion|Swashbucke version: 3.0.0, 4.0.0| |3.0.0| +|aspnetCoreVersion|ASP.NET Core version: 3.0, 2.2, 2.1, 2.0 (deprecated)| |2.2| +|swashbuckleVersion|Swashbuckle version: 3.0.0, 4.0.0, 5.0.0| |3.0.0| |sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true| |useDateTimeOffset|Use DateTimeOffset to model date-time properties| |false| |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| -|useFrameworkReference|Use frameworkReference for ASP.NET Core 3.0+ and PackageReference ASP.NET Core 2.2 or earlier.| |false| -|useNewtonsoft|Uses the Newtonsoft JSON library.| |true| -|newtonsoftVersion|Version for Microsoft.AspNetCore.Mvc.NewtonsoftJson for ASP.NET Core 3.0+| |3.0.0-preview5-19227-01| -|useDefaultRouting|Use default routing for the ASP.NET Core version. For 3.0 turn off default because it is not yet supported.| |true| |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| diff --git a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Filters/BasePathFilter.5_0.mustache b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Filters/BasePathFilter.5_0.mustache new file mode 100644 index 00000000000..fa9c099863a --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Filters/BasePathFilter.5_0.mustache @@ -0,0 +1,51 @@ +using System.Linq; +using System.Text.RegularExpressions; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace {{packageName}}.Filters +{ + /// <summary> + /// BasePath Document Filter sets BasePath property of Swagger and removes it from the individual URL paths + /// </summary> + public class BasePathFilter : IDocumentFilter + { + /// <summary> + /// Constructor + /// </summary> + /// <param name="basePath">BasePath to remove from Operations</param> + public BasePathFilter(string basePath) + { + BasePath = basePath; + } + + /// <summary> + /// Gets the BasePath of the Swagger Doc + /// </summary> + /// <returns>The BasePath of the Swagger Doc</returns> + public string BasePath { get; } + + /// <summary> + /// Apply the filter + /// </summary> + /// <param name="swaggerDoc">SwaggerDocument</param> + /// <param name="context">FilterContext</param> + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + //swaggerDoc.BasePath = BasePath; + + var pathsToModify = swaggerDoc.Paths.Where(p => p.Key.StartsWith(BasePath)).ToList(); + + foreach (var path in pathsToModify) + { + if (path.Key.StartsWith(BasePath)) + { + string newKey = Regex.Replace(path.Key, $"^{BasePath}", string.Empty); + swaggerDoc.Paths.Remove(path.Key); + swaggerDoc.Paths.Add(newKey, path.Value); + } + } + } + } +} diff --git a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Filters/GeneratePathParamsValidationFilter.5_0.mustache b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Filters/GeneratePathParamsValidationFilter.5_0.mustache new file mode 100644 index 00000000000..d63969d4788 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Filters/GeneratePathParamsValidationFilter.5_0.mustache @@ -0,0 +1,98 @@ +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace {{packageName}}.Filters +{ + /// <summary> + /// Path Parameter Validation Rules Filter + /// </summary> + public class GeneratePathParamsValidationFilter : IOperationFilter + { + /// <summary> + /// Constructor + /// </summary> + /// <param name="operation">Operation</param> + /// <param name="context">OperationFilterContext</param> + 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 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/openapi-generator/src/main/resources/aspnetcore/2.1/Project.csproj.3_0.mustache b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Project.csproj.3_0.mustache new file mode 100644 index 00000000000..334d3993214 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Project.csproj.3_0.mustache @@ -0,0 +1,24 @@ +<Project Sdk="{{projectSdk}}"> + <PropertyGroup> + <Description>{{packageName}}</Description> + <Copyright>{{packageName}}</Copyright> + <TargetFramework>netcoreapp{{aspnetCoreVersion}}</TargetFramework> + <GenerateDocumentationFile>true</GenerateDocumentationFile> + <PreserveCompilationContext>true</PreserveCompilationContext> +{{#isLibrary}} + <OutputType>Library</OutputType> +{{/isLibrary}} + <AssemblyName>{{packageName}}</AssemblyName> + <PackageId>{{packageName}}</PackageId> + </PropertyGroup> + <ItemGroup> + <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0-rc1.19457.4" /> +{{#useSwashbuckle}} + <PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0-rc2"/> + <PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="5.0.0-rc2" /> +{{/useSwashbuckle}} + </ItemGroup> + <ItemGroup> + <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.3" /> + </ItemGroup> +</Project> 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 a7b6b35bd78..610c822af0b 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 @@ -12,17 +12,7 @@ <PackageId>{{packageName}}</PackageId> </PropertyGroup> <ItemGroup> -{{#useFrameworkReference}} -{{#isLibrary}} - <FrameworkReference Include="Microsoft.AspNetCore.App" /> -{{/isLibrary}} -{{/useFrameworkReference}} -{{^useFrameworkReference}} <PackageReference Include="Microsoft.AspNetCore.App" /> -{{/useFrameworkReference}} -{{#useNewtonsoft}} - <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="{{newtonsoftVersion}}" /> -{{/useNewtonsoft}} {{#useSwashbuckle}} <PackageReference Include="Swashbuckle.AspNetCore" Version="{{swashbuckleVersion}}"/> <PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="{{swashbuckleVersion}}" /> diff --git a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Startup.3_0.mustache b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Startup.3_0.mustache new file mode 100644 index 00000000000..59dd846d205 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Startup.3_0.mustache @@ -0,0 +1,150 @@ +{{>partial_header}} +using System; +using System.IO; +using System.Reflection; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization;{{#useSwashbuckle}} +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; +using Microsoft.OpenApi.Models; +using {{packageName}}.Filters;{{/useSwashbuckle}} +using {{packageName}}.Authentication; +using Microsoft.AspNetCore.Authorization; + +namespace {{packageName}} +{ + /// <summary> + /// Startup + /// </summary> + public class Startup + { + /// <summary> + /// Constructor + /// </summary> + /// <param name="configuration"></param> + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + /// <summary> + /// The application configuration. + /// </summary> + public IConfiguration Configuration { get; } + + /// <summary> + /// This method gets called by the runtime. Use this method to add services to the container. + /// </summary> + /// <param name="services"></param> + public void ConfigureServices(IServiceCollection services) + { +{{#authMethods}} +{{#isApiKey}} +{{#-first}} + services.AddTransient<IAuthorizationHandler, ApiKeyRequirementHandler>(); + services.AddAuthorization(authConfig => + { +{{/-first}} + authConfig.AddPolicy("{{name}}", + policyBuilder => policyBuilder + .AddRequirements(new ApiKeyRequirement(new[] { "my-secret-key" },"{{name}}"))); +{{#-last}} + }); +{{/-last}} +{{/isApiKey}} +{{/authMethods}} + + // Add framework services. + services + .AddControllers() + .SetCompatibilityVersion(CompatibilityVersion.Version_3_0) + .AddNewtonsoftJson(opts => + { + opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + opts.SerializerSettings.Converters.Add(new StringEnumConverter + { + NamingStrategy = new CamelCaseNamingStrategy() + }); + } + ); + + {{#useSwashbuckle}} + + 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 3.0)", + Contact = new OpenApiContact() + { + Name = "{{#infoName}}{{{infoName}}}{{/infoName}}{{^infoName}}OpenAPI-Generator Contributors{{/infoName}}", + Url = new Uri("{{#infoUrl}}{{{infoUrl}}}{{/infoUrl}}{{^infoUrl}}https://github.com/openapitools/openapi-generator{{/infoUrl}}"), + Email = "{{#infoEmail}}{{{infoEmail}}}{{/infoEmail}}" + }, + TermsOfService = "{{#termsOfService}}{{{termsOfService}}}{{/termsOfService}}" + }); + c.CustomSchemaIds(type => type.FriendlyId(true)); + c.DescribeAllEnumsAsStrings(); + c.IncludeXmlComments($"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}{Assembly.GetEntryAssembly().GetName().Name}.xml"); + {{#basePathWithoutHost}} + // Sets the basePath property in the Swagger document generated + c.DocumentFilter<BasePathFilter>("{{{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<GeneratePathParamsValidationFilter>(); + }); + {{/useSwashbuckle}} + } + + /// <summary> + /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + /// </summary> + /// <param name="app"></param> + /// <param name="env"></param> + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + app.UseHttpsRedirection(); + app + .UseDefaultFiles() + .UseStaticFiles(){{#useSwashbuckle}} + .UseSwagger(c => + { + c.RouteTemplate = "swagger/{documentName}/openapi.json"; + }) + .UseSwaggerUI(c => + { + //TODO: Either use the SwaggerGen generated Swagger contract (generated from C# classes) + c.SwaggerEndpoint("/swagger/{{#version}}{{{version}}}{{/version}}{{^version}}v1{{/version}}/openapi.json", "{{#appName}}{{{appName}}}{{/appName}}{{^appName}}{{packageName}}{{/appName}}"); + + //TODO: Or alternatively use the original Swagger contract that's included in the static files + // c.SwaggerEndpoint("/openapi-original.json", "{{#appName}}{{{appName}}}{{/appName}}{{^appName}}{{packageName}}{{/appName}} Original"); + }){{/useSwashbuckle}}; + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseHsts(); + } + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + + }); + } + } +} 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 744a282fee1..5883d72bcae 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 @@ -60,16 +60,16 @@ namespace {{packageName}} // Add framework services. services - .AddMvc({{^useDefaultRouting}}opts => opts.EnableEndpointRouting = false{{/useDefaultRouting}}) + .AddMvc() {{#compatibilityVersion}} .SetCompatibilityVersion(CompatibilityVersion.{{compatibilityVersion}}) {{/compatibilityVersion}} - .{{#useNewtonsoft}}AddNewtonsoftJson{{/useNewtonsoft}}{{^useNewtonsoft}}AddJsonOptions{{/useNewtonsoft}}(opts => + .AddJsonOptions(opts => { opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); opts.SerializerSettings.Converters.Add(new StringEnumConverter { - {{#useNewtonsoft}}NamingStrategy = new CamelCaseNamingStrategy(){{/useNewtonsoft}}{{^useNewtonsoft}}CamelCaseText = true{{/useNewtonsoft}} + CamelCaseText = true }); }); {{#useSwashbuckle}} @@ -127,12 +127,7 @@ namespace {{packageName}} //TODO: Or alternatively use the original Swagger contract that's included in the static files // c.SwaggerEndpoint("/openapi-original.json", "{{#appName}}{{{appName}}}{{/appName}}{{^appName}}{{packageName}}{{/appName}} Original"); - }){{/useSwashbuckle}};{{^useDefaultRouting}} - app.UseRouting(); - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - });{{/useDefaultRouting}} + }){{/useSwashbuckle}}; if (env.IsDevelopment()) { diff --git a/samples/server/petstore/aspnetcore/.openapi-generator/VERSION b/samples/server/petstore/aspnetcore/.openapi-generator/VERSION index 2f81801b794..0e97bd19efb 100644 --- a/samples/server/petstore/aspnetcore/.openapi-generator/VERSION +++ b/samples/server/petstore/aspnetcore/.openapi-generator/VERSION @@ -1 +1 @@ -4.1.1-SNAPSHOT \ No newline at end of file +4.1.3-SNAPSHOT \ No newline at end of file -- GitLab