Skip to content

Commit 7018e3f

Browse files
authored
Handle leading slash on route prefixes (#4084)
Make leading slash optional.
1 parent e78b0a1 commit 7018e3f

6 files changed

Lines changed: 126 additions & 77 deletions

File tree

src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIMiddleware.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public async Task Invoke(HttpContext httpContext)
5555
return;
5656
}
5757

58-
var match = Regex.Match(path, $"^/{Regex.Escape(_options.RoutePrefix)}/?(index.(html|js))$", RegexOptions.IgnoreCase);
58+
var match = Regex.Match(path, $"^/?{Regex.Escape(_options.RoutePrefix)}/?(index.(html|js))$", RegexOptions.IgnoreCase);
5959

6060
if (match.Success)
6161
{

test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerUIIntegrationTests.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ public static TheoryData<string, string> SwaggerUIFiles()
3737

3838
[Theory]
3939
[InlineData(typeof(Basic.Startup), "/", "index.html")]
40+
[InlineData(typeof(Basic.StartupWithAbsoluteRoutePrefix), "/abs", "abs/index.html")]
41+
[InlineData(typeof(Basic.StartupWithRelativeRoutePrefix), "/rel", "rel/index.html")]
4042
[InlineData(typeof(CustomUIConfig.Startup), "/swagger", "swagger/index.html")]
4143
[InlineData(typeof(CustomUIConfig.Startup), "/swagger/", "index.html")]
4244
public async Task RoutePrefix_RedirectsToPathRelativeIndexUrl(
@@ -55,6 +57,8 @@ public async Task RoutePrefix_RedirectsToPathRelativeIndexUrl(
5557

5658
[Theory]
5759
[InlineData(typeof(Basic.Startup), "/index.html")]
60+
[InlineData(typeof(Basic.StartupWithAbsoluteRoutePrefix), "/abs/index.html")]
61+
[InlineData(typeof(Basic.StartupWithRelativeRoutePrefix), "/rel/index.html")]
5862
[InlineData(typeof(CustomUIConfig.Startup), "/swagger/index.html")]
5963
public async Task IndexUrl_HeadRequest_ReturnsMetadata(
6064
Type startupType,
@@ -72,6 +76,8 @@ public async Task IndexUrl_HeadRequest_ReturnsMetadata(
7276

7377
[Theory]
7478
[InlineData(typeof(Basic.Startup), "/index.html", "/swagger-ui.js", "/index.css", "/swagger-ui.css")]
79+
[InlineData(typeof(Basic.StartupWithAbsoluteRoutePrefix), "/abs/index.html", "/abs/swagger-ui.js", "/abs/index.css", "/abs/swagger-ui.css")]
80+
[InlineData(typeof(Basic.StartupWithRelativeRoutePrefix), "/rel/index.html", "/rel/swagger-ui.js", "/rel/index.css", "/rel/swagger-ui.css")]
7581
[InlineData(typeof(CustomUIConfig.Startup), "/swagger/index.html", "/swagger/swagger-ui.js", "swagger/index.css", "/swagger/swagger-ui.css")]
7682
public async Task IndexUrl_ReturnsEmbeddedVersionOfTheSwaggerUI(
7783
Type startupType,

test/WebSites/Basic/Startup.cs

Lines changed: 3 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -6,85 +6,12 @@
66

77
namespace Basic;
88

9-
public class Startup
9+
public class Startup : StartupBase
1010
{
11-
public void ConfigureServices(IServiceCollection services)
12-
{
13-
services.AddControllers();
14-
15-
services.AddSwaggerGen(c =>
16-
{
17-
c.SwaggerDoc("v1",
18-
new OpenApiInfo
19-
{
20-
Title = "Test API V1",
21-
Version = "v1",
22-
Description = "A sample API for testing Swashbuckle",
23-
TermsOfService = new Uri("http://tempuri.org/terms")
24-
}
25-
);
26-
27-
c.RequestBodyFilter<AssignRequestBodyVendorExtensions>();
28-
29-
c.OperationFilter<AssignOperationVendorExtensions>();
30-
31-
c.SchemaFilter<ExamplesSchemaFilter>();
32-
33-
c.DescribeAllParametersInCamelCase();
34-
35-
c.UseOneOfForPolymorphism();
36-
c.UseAllOfForInheritance();
37-
38-
c.SelectDiscriminatorNameUsing((baseType) => "TypeName");
39-
c.SelectDiscriminatorValueUsing((subType) => subType.Name);
40-
41-
c.IncludeXmlComments(Assembly.GetExecutingAssembly());
42-
43-
c.EnableAnnotations();
44-
});
45-
}
4611

47-
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
12+
public override void Configure(IApplicationBuilder app, IWebHostEnvironment env)
4813
{
49-
if (env.IsDevelopment())
50-
{
51-
app.UseDeveloperExceptionPage();
52-
}
53-
54-
app.UseRouting();
55-
56-
app.UseEndpoints(endpoints =>
57-
{
58-
endpoints.MapControllers();
59-
60-
// Expose Swagger/OpenAPI JSON in different formats
61-
endpoints.MapSwagger("swagger/{documentName}/swagger.json");
62-
endpoints.MapSwagger("swagger/{documentName}/swaggerv2.json", c =>
63-
{
64-
c.OpenApiVersion = OpenApiSpecVersion.OpenApi2_0;
65-
});
66-
endpoints.MapSwagger("swagger/{documentName}/swaggerv3_1.json", c =>
67-
{
68-
c.OpenApiVersion = OpenApiSpecVersion.OpenApi3_1;
69-
});
70-
});
71-
72-
var supportedCultures = new[]
73-
{
74-
new CultureInfo("en-US"),
75-
new CultureInfo("fr"),
76-
new CultureInfo("sv-SE"),
77-
};
78-
79-
app.UseRequestLocalization(new RequestLocalizationOptions
80-
{
81-
DefaultRequestCulture = new RequestCulture("en-US"),
82-
// Formatting numbers, dates, etc.
83-
SupportedCultures = supportedCultures,
84-
// UI strings that we have localized.
85-
SupportedUICultures = supportedCultures
86-
});
87-
14+
base.Configure(app, env);
8815
app.UseSwaggerUI(c =>
8916
{
9017
c.RoutePrefix = ""; // serve the UI at root

test/WebSites/Basic/StartupBase.cs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using System.Globalization;
2+
using System.Reflection;
3+
using Basic.Swagger;
4+
using Microsoft.AspNetCore.Localization;
5+
using Microsoft.OpenApi;
6+
7+
namespace Basic;
8+
9+
public class StartupBase
10+
{
11+
public void ConfigureServices(IServiceCollection services)
12+
{
13+
services.AddControllers();
14+
15+
services.AddSwaggerGen(c =>
16+
{
17+
c.SwaggerDoc("v1",
18+
new OpenApiInfo
19+
{
20+
Title = "Test API V1",
21+
Version = "v1",
22+
Description = "A sample API for testing Swashbuckle",
23+
TermsOfService = new Uri("http://tempuri.org/terms")
24+
}
25+
);
26+
27+
c.RequestBodyFilter<AssignRequestBodyVendorExtensions>();
28+
29+
c.OperationFilter<AssignOperationVendorExtensions>();
30+
31+
c.SchemaFilter<ExamplesSchemaFilter>();
32+
33+
c.DescribeAllParametersInCamelCase();
34+
35+
c.UseOneOfForPolymorphism();
36+
c.UseAllOfForInheritance();
37+
38+
c.SelectDiscriminatorNameUsing((baseType) => "TypeName");
39+
c.SelectDiscriminatorValueUsing((subType) => subType.Name);
40+
41+
c.IncludeXmlComments(Assembly.GetExecutingAssembly());
42+
43+
c.EnableAnnotations();
44+
});
45+
}
46+
47+
public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env)
48+
{
49+
if (env.IsDevelopment())
50+
{
51+
app.UseDeveloperExceptionPage();
52+
}
53+
54+
app.UseRouting();
55+
56+
app.UseEndpoints(endpoints =>
57+
{
58+
endpoints.MapControllers();
59+
60+
// Expose Swagger/OpenAPI JSON in different formats
61+
endpoints.MapSwagger("swagger/{documentName}/swagger.json");
62+
endpoints.MapSwagger("swagger/{documentName}/swaggerv2.json", c =>
63+
{
64+
c.OpenApiVersion = OpenApiSpecVersion.OpenApi2_0;
65+
});
66+
endpoints.MapSwagger("swagger/{documentName}/swaggerv3_1.json", c =>
67+
{
68+
c.OpenApiVersion = OpenApiSpecVersion.OpenApi3_1;
69+
});
70+
});
71+
72+
var supportedCultures = new[]
73+
{
74+
new CultureInfo("en-US"),
75+
new CultureInfo("fr"),
76+
new CultureInfo("sv-SE"),
77+
};
78+
79+
app.UseRequestLocalization(new RequestLocalizationOptions
80+
{
81+
DefaultRequestCulture = new RequestCulture("en-US"),
82+
// Formatting numbers, dates, etc.
83+
SupportedCultures = supportedCultures,
84+
// UI strings that we have localized.
85+
SupportedUICultures = supportedCultures
86+
});
87+
}
88+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Basic;
2+
3+
public class StartupWithAbsoluteRoutePrefix : StartupBase
4+
{
5+
public override void Configure(IApplicationBuilder app, IWebHostEnvironment env)
6+
{
7+
base.Configure(app, env);
8+
app.UseSwaggerUI(c =>
9+
{
10+
c.RoutePrefix = "/abs"; // serve the UI under an absolute prefix
11+
c.SwaggerEndpoint("/swagger/v1/swagger.json", "V1 Docs");
12+
});
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Basic;
2+
3+
public class StartupWithRelativeRoutePrefix : StartupBase
4+
{
5+
public override void Configure(IApplicationBuilder app, IWebHostEnvironment env)
6+
{
7+
base.Configure(app, env);
8+
app.UseSwaggerUI(c =>
9+
{
10+
c.RoutePrefix = "rel"; // serve the UI under a relative prefix
11+
c.SwaggerEndpoint("/swagger/v1/swagger.json", "V1 Docs");
12+
});
13+
}
14+
}

0 commit comments

Comments
 (0)