Search code examples
c#swagger-uiswashbuckle

How change swagger Route endpoint url and save api Versionning


I have Startup file where i configure SwaggerUI and Enpoints Url

namespace AccountingService
{
    public class Startup
    {
        //public Startup(IConfiguration configuration, IHostEnvironment env, ILogger<Startup> logger) { }
 
       //public void ConfigureServices(IServiceCollection services){ }
 
        public void Configure(IApplicationBuilder app, IServiceProvider serviceProvider, IApiVersionDescriptionProvider provider)
        {
            if (HostEnvironment.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseIpRateLimiting();
            app.ConfigureCustomMiddleware(_appSettings);
            UserHelper.InitDependencies(serviceProvider.GetRequiredService<IUserDataService>());
            
            app.UseRouting();
            app.UseCors("MyPolicy");

            app.UseHttpsRedirection();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseTimezone();
            app.UseErrorService(serviceProvider);
            app.UseSession();

            app.UseUserService();
 
            if (_appSettings.Swagger.Enabled)
            {
                app.UseSwagger();
                app.UseSwaggerUI(options =>
                {
                    foreach (var description in provider.ApiVersionDescriptions)
                    {
                        options.SwaggerEndpoint($"swagger/{description.GroupName}/swagger.json", description.GroupName.ToLowerInvariant());
                        options.RoutePrefix = string.Empty;
                    }
                });
            }
            
            //app.UseNamedSwagger(_appSettings.ServiceName, _appSettings.ServiceTitle, _appSettings.Version);

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

Here how its working in swagger url generic in this case localhost:[port]/swagger/{groupname}/swagger.json

I need here change swagger url to for example [name-service]/swagger/v1/swagger.json Its need to correct routing on nginx our server on docker compose

When I try to change SwaggerEndpoint any other url value like

_appSettings.ServiceName
if (_appSettings.Swagger.Enabled)
            {
                app.UseSwagger();
                app.UseSwaggerUI(options =>
                {
                    foreach (var description in provider.ApiVersionDescriptions)
                    {
                        options.SwaggerEndpoint($"{_appSettings.ServiceName}/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
                        options.RoutePrefix = string.Empty;
                    }
                });
            }

Its brokes at all and nothing to do with that.

Same problem when I try change only route prefix result

app.RoutePrefix = $"{_appSettings.ServiceName}/swagger";

I dont know how correctly form question and problem case in this.

But when I use other method UseNamedSwagger for generate swagger UI

app.UseNamedSwagger(_appSettings.ServiceName, _appSettings.ServiceTitle, _appSettings.Version);

instead of

app.UseSwaggerUI(options =>
                {
                    foreach (var description in provider.ApiVersionDescriptions)
                    {
                        options.SwaggerEndpoint($"{_appSettings.ServiceName}/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
                        options.RoutePrefix = string.Empty;
                    }
                });

It work correctly and swagger url change correct, but here my versionning disappear v1,v2 but here my versionning disappear v1,v2

Here for addition how I API controller building

    [Authorize]
    [ApiController]
    [ApiVersion("1.0")]
    [Route("api/v{version:apiVersion}/[controller]")]
    public class ActsController : ControllerBase

What I need change or add to correct show swagger url "edo-service/swagger/v1/swagger" and not lost my versionning definitions (v1,v2)

ConfigureServices method if needed

 public void ConfigureServices(IServiceCollection services)
        {
            _logger.LogTrace("Startup::ConfigureServices");

            //Enable Cors
            services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
            {
                builder.AllowAnyOrigin()
                    .AllowAnyMethod()
                    .AllowAnyHeader();
            }));

            // configure strongly typed settings objects
            _appSettings = Configuration.GetSection("AppSettings").Get<AppSettings>();
            services.Configure<AppSettings>(Configuration.GetSection(nameof(AppSettings)));
            // configure jwt authentication
            var key = Encoding.ASCII.GetBytes(_appSettings.Secret);

            // configure swagger settings
            var swaggerSettings = Configuration.GetSection("SwaggerAdminSettings").Get<SwaggerSettingsModel>();

            services.Configure<ErrorServiceApiConfiguration>(
                Configuration.GetSection(nameof(ErrorServiceApiConfiguration)));

            services.AddAuthentication(..);

            // requires using Microsoft.Extensions.Options
            services.Configure<FormOptions>(o =>
            {
                o.ValueLengthLimit = int.MaxValue;
                o.MultipartBodyLengthLimit = int.MaxValue;
                o.MemoryBufferThreshold = int.MaxValue;
            });

            //Add IMapper profile configuration to DI
            var mapperConfig = new MapperConfiguration(mc => mc.AddProfile(new AutoMapperProfile()));
            services.AddSingleton(mapperConfig.CreateMapper());

           // many other services....

            // -- API Rate Limit Configuration --
            services.AddMemoryCache();
            services.AddInMemoryRateLimiting();
            services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
            services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimiting"));

            //Add Services here
            //==== CommonServiceTools services ====
            services.AddLogger();
            services.ConfigureCrud();
            services.AddExternalApi();
            services.AddWarehouseApi();
            services.AddInvoiceManager();
            // many other services....

            services.AddDistributedMemoryCache();
            services.AddSession();

            services.Configure<MinioSettings>(Configuration.GetSection(nameof(MinioSettings)));
            services.Configure<WarehouseApiSettings>(Configuration.GetSection(nameof(WarehouseApiSettings)));

            services.AddControllers(opt =>
            { opt.UseCentralRoutePrefix(new RouteAttribute(_appSettings.ServiceName)); })
                .AddNewtonsoftJson(options => options.UseMemberCasing())
                .AddJsonOptions(options =>
                    options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())).SetCompatibilityVersion(CompatibilityVersion.Latest);

            //API versioning
            services.AddApiVersioning(
                o =>
                {
                    //o.Conventions.Controller<UserController>().HasApiVersion(1, 0);
                    o.ReportApiVersions = true;
                    o.AssumeDefaultVersionWhenUnspecified = true;
                    o.DefaultApiVersion = new ApiVersion(1, 0);
                    o.ApiVersionReader = new UrlSegmentApiVersionReader();
                }
            );

            // note: the specified format code will format the version as "'v'major[.minor][-status]"
            services.AddVersionedApiExplorer(
                options =>
                {
                    options.GroupNameFormat = "'v'VVV";

                    // note: this option is only necessary when versioning by url segment. the SubstitutionFormat
                    // can also be used to control the format of the API version in route accounting-service
                    options.SubstituteApiVersionInUrl = true;
                });

            services.AddWkhtmltopdf();


            if (_appSettings.Swagger.Enabled)
            {
                // -- Swagger configuration -- 
                services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigurationSwaggerOptions>();
                // Register the Swagger generator, defining 1 or more Swagger documents
                services.AddSwaggerGen(c =>
                {
                    c.OperationFilter<SwaggerDefaultValues>();
                    c.UseAllOfToExtendReferenceSchemas();

                    //c.SwaggerDoc(_appSettings.Version, 
                    //    new OpenApiInfo { Title = _appSettings.ServiceTitle, Version = _appSettings.Version });
                    /*Authorization*/
                    var jwtSecurityScheme = new OpenApiSecurityScheme
                    {
                        Scheme = "bearer",
                        BearerFormat = "JWT",
                        Name = "JWT Authentication",
                        In = ParameterLocation.Header,
                        Type = SecuritySchemeType.Http,
                        Description = "Put **_ONLY_** your JWT Bearer token on textbox below!",

                        Reference = new OpenApiReference
                        {
                            Id = JwtBearerDefaults.AuthenticationScheme,
                            Type = ReferenceType.SecurityScheme
                        }
                    };

                    c.AddSecurityDefinition(jwtSecurityScheme.Reference.Id, jwtSecurityScheme);

                    c.AddSecurityRequirement(new OpenApiSecurityRequirement
                    {
                        { jwtSecurityScheme, Array.Empty<string>() }
                    });

                    //1-Get all the assemblies of the project to add the related XML Comments
                    Assembly currentAssembly = Assembly.GetExecutingAssembly();
                    AssemblyName[] referencedAssemblies = currentAssembly.GetReferencedAssemblies();
                    IEnumerable<AssemblyName> allAssemblies = null;

                    if (referencedAssemblies != null && referencedAssemblies.Any())
                        allAssemblies = referencedAssemblies.Union(new AssemblyName[] { currentAssembly.GetName() });
                    else
                        allAssemblies = new AssemblyName[] { currentAssembly.GetName() };

                    IEnumerable<string> xmlDocs = allAssemblies
                        .Select(a => Path.Combine(Path.GetDirectoryName(currentAssembly.Location), $"{a.Name}.xml"))
                        .Where(f => File.Exists(f));

                    //2-Add the path to the XML comments for the assemblies having enabled the doc generation
                    if (xmlDocs != null && xmlDocs.Any())
                    {
                        foreach (var item in xmlDocs)
                        {
                            c.IncludeXmlComments(item, includeControllerXmlComments: true);
                        }
                    }
                });
            }

            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

            _logger.LogDebug("Startup::Constructor::Settings loaded");
        }

Solution

  • Solved, just comment above

    //c.SwaggerDoc(_appSettings.Version,
    //    new OpenApiInfo { Title = _appSettings.ServiceTitle, Version = _appSettings.Version });
    

    And do this thing

     if (_appSettings.Swagger.Enabled)
                {
                    app.UseSwagger(delegate (SwaggerOptions c)
                    {
                        c.RouteTemplate = $"/{_appSettings.ServiceName}/swagger/{{documentName}}/swagger.json";
                    }).UseSwaggerUI(options =>
                    {
                        foreach (var description in provider.ApiVersionDescriptions)
                        {
                            options.SwaggerEndpoint(
                                $"/{_appSettings.ServiceName}/swagger/{description.GroupName}/swagger.json",
                                $"{_appSettings.ServiceTitle} {description.GroupName}");
                            options.RoutePrefix = $"{_appSettings.ServiceName}/swagger";
                        }
                    });
                }