diff --git a/Swashbuckle.AspNetCore.slnx b/Swashbuckle.AspNetCore.slnx
index 5ba846053c..a6e0f06356 100644
--- a/Swashbuckle.AspNetCore.slnx
+++ b/Swashbuckle.AspNetCore.slnx
@@ -80,6 +80,7 @@
+
diff --git a/docs/configure-and-customize-redoc.md b/docs/configure-and-customize-redoc.md
index 82f24a8166..965a125bb6 100644
--- a/docs/configure-and-customize-redoc.md
+++ b/docs/configure-and-customize-redoc.md
@@ -1,137 +1,169 @@
-# Configuration and Customization of `Swashbuckle.AspNetCore.ReDoc`
-
-## Change Relative Path to the UI
-
-By default, the Redoc UI will be exposed at `/api-docs`. If necessary, you can alter this when enabling the Redoc middleware:
-
-
-
-
-```cs
-app.UseReDoc(options =>
-{
- options.RoutePrefix = "docs";
-});
-```
-snippet source | anchor
-
-
-
-## Change Document Title
-
-By default, the Redoc UI will have a generic document title. You can alter this when enabling the Redoc middleware:
-
-
-
-
-```cs
-app.UseReDoc(options =>
-{
- options.DocumentTitle = "My API Docs";
-});
-```
-snippet source | anchor
-
-
-
-## Apply Redoc Parameters
-
-Redoc ships with its own set of configuration parameters, all described [in the Redoc documentation][redoc-options].
-In Swashbuckle.AspNetCore, most of these are surfaced through the Redoc middleware options:
-
-
-
-
-```cs
-app.UseReDoc(options =>
-{
- options.SpecUrl("/v1/swagger.json");
- options.EnableUntrustedSpec();
- options.ScrollYOffset(10);
- options.HideHostname();
- options.HideDownloadButton();
- options.ExpandResponses("200,201");
- options.RequiredPropsFirst();
- options.NoAutoAuth();
- options.PathInMiddlePanel();
- options.HideLoading();
- options.NativeScrollbars();
- options.DisableSearch();
- options.OnlyRequiredInSamples();
- options.SortPropsAlphabetically();
-});
-```
-snippet source | anchor
-
-
-
-> [!NOTE]
-> Using `options.SpecUrl("/v1/swagger.json")` multiple times within the same `UseReDoc(...)` will not add multiple URLs.
-
-## Inject Custom CSS
-
-To tweak the look and feel, you can inject additional CSS stylesheets by adding them to your `wwwroot` folder and specifying
-the relative paths in the middleware options:
-
-
-
-
-```cs
-app.UseReDoc(options =>
-{
- options.InjectStylesheet("/redoc/custom.css");
-});
-```
-snippet source | anchor
-
-
-
-It is also possible to modify the theme by using the `AdditionalItems` property. More information can be found
-[in the Redoc documentation][redoc-options].
-
-
-
-
-```cs
-app.UseReDoc(options =>
-{
- options.ConfigObject.AdditionalItems = new Dictionary
- {
- // Configured additional options
- };
-});
-```
-snippet source | anchor
-
-
-
-## Customize index.html
-
-To customize the UI beyond the basic options listed above, you can provide your own version of the Redoc `index.html` page:
-
-
-
-
-```cs
-app.UseReDoc(options =>
-{
- options.IndexStream = () => typeof(Program).Assembly
- .GetManifestResourceStream("CustomIndex.ReDoc.index.html"); // Requires file to be added as an embedded resource
-});
-```
-snippet source | anchor
-
-
-
-```xml
-
-
-
-
-
-```
-
-> [!TIP]
-> To get started, you should base your custom `index.html` on the [default version](../src/Swashbuckle.AspNetCore.ReDoc/index.html).
-
-[redoc-options]: https://github.com/Redocly/redoc/blob/main/docs/deployment/html.md#the-redoc-object
+# Configuration and Customization of `Swashbuckle.AspNetCore.ReDoc`
+
+## Change Relative Path to the UI
+
+By default, the Redoc UI will be exposed at `/api-docs`. If necessary, you can alter this when enabling the Redoc middleware:
+
+
+
+
+```cs
+app.UseReDoc(options =>
+{
+ options.RoutePrefix = "docs";
+});
+```
+snippet source | anchor
+
+
+
+## Change Document Title
+
+By default, the Redoc UI will have a generic document title. You can alter this when enabling the Redoc middleware:
+
+
+
+
+```cs
+app.UseReDoc(options =>
+{
+ options.DocumentTitle = "My API Docs";
+});
+```
+snippet source | anchor
+
+
+
+## Apply Redoc Parameters
+
+Redoc ships with its own set of configuration parameters, all described [in the Redoc documentation][redoc-options].
+In Swashbuckle.AspNetCore, most of these are surfaced through the Redoc middleware options:
+
+
+
+
+```cs
+app.UseReDoc(options =>
+{
+ options.SpecUrl("/v1/swagger.json");
+ options.EnableUntrustedSpec();
+ options.ScrollYOffset(10);
+ options.HideHostname();
+ options.HideDownloadButton();
+ options.ExpandResponses("200,201");
+ options.RequiredPropsFirst();
+ options.NoAutoAuth();
+ options.PathInMiddlePanel();
+ options.HideLoading();
+ options.NativeScrollbars();
+ options.DisableSearch();
+ options.OnlyRequiredInSamples();
+ options.SortPropsAlphabetically();
+});
+```
+snippet source | anchor
+
+
+
+> [!NOTE]
+> Using `options.SpecUrl("/v1/swagger.json")` multiple times within the same `UseReDoc(...)` will not add multiple URLs.
+
+## Inject Custom CSS
+
+To tweak the look and feel, you can inject additional CSS stylesheets by adding them to your `wwwroot` folder and specifying
+the relative paths in the middleware options:
+
+
+
+
+```cs
+app.UseReDoc(options =>
+{
+ options.InjectStylesheet("/redoc/custom.css");
+});
+```
+snippet source | anchor
+
+
+
+It is also possible to modify the theme by using the `AdditionalItems` property. More information can be found
+[in the Redoc documentation][redoc-options].
+
+
+
+
+```cs
+app.UseReDoc(options =>
+{
+ options.ConfigObject.AdditionalItems = new Dictionary
+ {
+ // Configured additional options
+ };
+});
+```
+snippet source | anchor
+
+
+
+## Customize index.html
+
+To customize the UI beyond the basic options listed above, you can provide your own version of the Redoc `index.html` page:
+
+
+
+
+```cs
+app.UseReDoc(options =>
+{
+ options.IndexStream = () => typeof(Program).Assembly
+ .GetManifestResourceStream("CustomIndex.ReDoc.index.html"); // Requires file to be added as an embedded resource
+});
+```
+snippet source | anchor
+
+
+
+```xml
+
+
+
+
+
+```
+
+> [!TIP]
+> To get started, you should base your custom `index.html` on the [default version](../src/Swashbuckle.AspNetCore.ReDoc/index.html).
+
+[redoc-options]: https://github.com/Redocly/redoc/blob/main/docs/deployment/html.md#the-redoc-object
+
+## Use `MapReDoc` with endpoint routing
+
+`MapReDoc` is an endpoint-routing alternative to `UseReDoc`. The options API is the same, so all customization examples on this page also apply when using `MapReDoc`.
+
+
+
+
+```cs
+app.MapReDoc();
+```
+snippet source | anchor
+
+
+
+The main differences are:
+
+- **Use**ReDoc adds middleware directly to the request pipeline and returns `IApplicationBuilder`.
+- **Map**ReDoc maps ReDoc via endpoint routing and returns `IEndpointConventionBuilder`.
+
+- Because `MapReDoc` returns an endpoint convention builder, you can attach endpoint metadata/conventions (for example, authorization policies) directly to the mapped ReDoc endpoint.
+
+
+
+
+```cs
+app.MapReDoc("redoc-auth")
+ .RequireAuthorization(); // Remember to also add RequireAuthorization to MapSwagger
+```
+snippet source | anchor
+
+
diff --git a/docs/configure-and-customize-swaggerui.md b/docs/configure-and-customize-swaggerui.md
index 8dfcf90efa..f248fd0a95 100644
--- a/docs/configure-and-customize-swaggerui.md
+++ b/docs/configure-and-customize-swaggerui.md
@@ -1,246 +1,279 @@
-# Configuration and Customization of `Swashbuckle.AspNetCore.SwaggerUI`
-
-## Change Relative Path to the UI
-
-By default, the Swagger UI will be exposed at `/swagger`. If necessary, you can alter this when enabling the SwaggerUI middleware:
-
-
-
-
-```cs
-app.UseSwaggerUI(options =>
-{
- options.RoutePrefix = "api-docs";
-});
-```
-snippet source | anchor
-
-
-
-## Change Document Title
-
-By default, the Swagger UI will have a generic document title. When you have multiple OpenAPI documents open, it can be difficult to
-tell them apart. You can alter this when enabling the SwaggerUI middleware:
-
-
-
-
-```cs
-app.UseSwaggerUI(options =>
-{
- options.DocumentTitle = "My Swagger UI";
-});
-```
-snippet source | anchor
-
-
-
-## Change CSS or JS Paths
-
-By default, the Swagger UI includes default CSS and JavaScript, but if you wish to change the path or URL (for example to use a CDN)
-you can override the defaults as shown below:
-
-
-
-
-```cs
-app.UseSwaggerUI(options =>
-{
- options.StylesPath = "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.29.1/swagger-ui.min.css";
- options.ScriptBundlePath = "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.29.1/swagger-ui-bundle.min.js";
- options.ScriptPresetsPath = "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.29.1/swagger-ui-standalone-preset.min.js";
-});
-```
-snippet source | anchor
-
-
-
-## List Multiple OpenAPI Documents
-
-When enabling the middleware, you're required to specify one or more OpenAPI endpoints (fully qualified or relative to the UI page) to
-power the UI. If you provide multiple endpoints, they'll be listed in the top right corner of the page, allowing users to toggle between
-the different documents. For example, the following configuration could be used to document different versions of an API.
-
-
-
-
-```cs
-app.UseSwaggerUI(options =>
-{
- options.SwaggerEndpoint("/swagger/v1/swagger.json", "V1 Docs");
- options.SwaggerEndpoint("/swagger/v2/swagger.json", "V2 Docs");
-});
-```
-snippet source | anchor
-
-
-
-## Apply swagger-ui Parameters
-
-[swagger-ui][swagger-ui] ships with its own set of configuration parameters, all described
-[by the swagger-ui Configuration](https://github.com/swagger-api/swagger-ui/blob/HEAD/docs/usage/configuration.md#display).
-In Swashbuckle.AspNetCore, most of these are surfaced through the SwaggerUI middleware options:
-
-
-
-
-```cs
-app.UseSwaggerUI(options =>
-{
- options.DefaultModelExpandDepth(2);
- options.DefaultModelRendering(ModelRendering.Model);
- options.DefaultModelsExpandDepth(-1);
- options.DisplayOperationId();
- options.DisplayRequestDuration();
- options.DocExpansion(DocExpansion.None);
- options.EnableDeepLinking();
- options.EnableFilter();
- options.EnablePersistAuthorization();
- options.EnableTryItOutByDefault();
- options.MaxDisplayedTags(5);
- options.ShowExtensions();
- options.ShowCommonExtensions();
- options.EnableValidator();
- options.SupportedSubmitMethods(SubmitMethod.Get, SubmitMethod.Head);
- options.UseRequestInterceptor("(request) => { return request; }");
- options.UseResponseInterceptor("(response) => { return response; }");
-});
-```
-snippet source | anchor
-
-
-
-## Inject Custom JavaScript
-
-To tweak the behavior, you can inject additional JavaScript files by adding them to your `wwwroot` folder and specifying
-the relative paths in the middleware options:
-
-
-
-
-```cs
-app.UseSwaggerUI(options =>
-{
- options.InjectJavascript("/swagger-ui/custom.js");
-});
-```
-snippet source | anchor
-
-
-
-## Inject Custom CSS
-
-To tweak the look and feel, you can inject additional CSS stylesheets by adding them to your `wwwroot` folder and specifying the
-relative paths in the middleware options:
-
-
-
-
-```cs
-app.UseSwaggerUI(options =>
-{
- options.InjectStylesheet("/swagger-ui/custom.css");
-});
-```
-snippet source | anchor
-
-
-
-## Customize index.html
-
-To customize the UI beyond the basic options listed above, you can provide your own version of the swagger-ui `index.html` page:
-
-
-
-
-```cs
-app.UseSwaggerUI(options =>
-{
- options.IndexStream = () => typeof(Program).Assembly
- .GetManifestResourceStream("CustomUIIndex.Swagger.index.html"); // Requires file to be added as an embedded resource
-});
-```
-snippet source | anchor
-
-
-
-```xml
-
-
-
-
-
-```
-
-> [!TIP]
-> To get started, you should base your custom `index.html` on the [built-in version](../src/Swashbuckle.AspNetCore.SwaggerUI/index.html).
-
-## Enable OAuth2.0 Flows
-
-[swagger-ui][swagger-ui] has built-in support to participate in OAuth2.0 authorization flows. It interacts with authorization and/or token
-endpoints, as specified in the OpenAPI JSON, to obtain access tokens for subsequent API calls. See
-[Adding Security Definitions and Requirements](configure-and-customize-swaggergen.md#add-security-definitions-and-requirements) for
-an example of adding OAuth2.0 metadata to the generated Swagger.
-
-If your OpenAPI endpoint includes the appropriate security metadata, the UI interaction should be automatically enabled. However, you
-can further customize OAuth support in the UI with the following settings below. See the
-[Swagger-UI documentation](https://github.com/swagger-api/swagger-ui/blob/HEAD/docs/usage/oauth2.md) for more information.
-
-
-
-
-```cs
-app.UseSwaggerUI(options =>
-{
- options.OAuthClientId("test-id");
- options.OAuthClientSecret("test-secret");
- options.OAuthUsername("test-user");
- options.OAuthRealm("test-realm");
- options.OAuthAppName("test-app");
- options.OAuth2RedirectUrl("url");
- options.OAuthScopeSeparator(" ");
- options.OAuthScopes("scope1", "scope2");
- options.OAuthAdditionalQueryStringParams(new Dictionary { ["foo"] = "bar" });
- options.OAuthUseBasicAuthenticationWithAccessCodeGrant();
- options.OAuthUsePkce();
-});
-```
-snippet source | anchor
-
-
-
-## Use client-side request and response interceptors
-
-To use custom interceptors on requests and responses going through swagger-ui you can define them as JavaScript functions
-in the configuration:
-
-
-
-
-```cs
-app.UseSwaggerUI(options =>
-{
- options.UseRequestInterceptor("(req) => { req.headers['x-my-custom-header'] = 'MyCustomValue'; return req; }");
- options.UseResponseInterceptor("(res) => { console.log('Custom interceptor intercepted response from:', res.url); return res; }");
-});
-```
-snippet source | anchor
-
-
-
-This can be useful in a range of scenarios where you might want to append local XSRF tokens to all requests, for example:
-
-
-
-
-```cs
-app.UseSwaggerUI(options =>
-{
- options.UseRequestInterceptor("(req) => { req.headers['X-XSRF-Token'] = localStorage.getItem('xsrf-token'); return req; }");
-});
-```
-snippet source | anchor
-
-
-
-[swagger-ui]: https://github.com/swagger-api/swagger-ui
+# Configuration and Customization of `Swashbuckle.AspNetCore.SwaggerUI`
+
+## Change Relative Path to the UI
+
+By default, the Swagger UI will be exposed at `/swagger`. If necessary, you can alter this when enabling the SwaggerUI middleware:
+
+
+
+
+```cs
+app.UseSwaggerUI(options =>
+{
+ options.RoutePrefix = "api-docs";
+});
+```
+snippet source | anchor
+
+
+
+## Change Document Title
+
+By default, the Swagger UI will have a generic document title. When you have multiple OpenAPI documents open, it can be difficult to
+tell them apart. You can alter this when enabling the SwaggerUI middleware:
+
+
+
+
+```cs
+app.UseSwaggerUI(options =>
+{
+ options.DocumentTitle = "My Swagger UI";
+});
+```
+snippet source | anchor
+
+
+
+## Change CSS or JS Paths
+
+By default, the Swagger UI includes default CSS and JavaScript, but if you wish to change the path or URL (for example to use a CDN)
+you can override the defaults as shown below:
+
+
+
+
+```cs
+app.UseSwaggerUI(options =>
+{
+ options.StylesPath = "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.29.1/swagger-ui.min.css";
+ options.ScriptBundlePath = "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.29.1/swagger-ui-bundle.min.js";
+ options.ScriptPresetsPath = "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.29.1/swagger-ui-standalone-preset.min.js";
+});
+```
+snippet source | anchor
+
+
+
+## List Multiple OpenAPI Documents
+
+When enabling the middleware, you're required to specify one or more OpenAPI endpoints (fully qualified or relative to the UI page) to
+power the UI. If you provide multiple endpoints, they'll be listed in the top right corner of the page, allowing users to toggle between
+the different documents. For example, the following configuration could be used to document different versions of an API.
+
+
+
+
+```cs
+app.UseSwaggerUI(options =>
+{
+ options.SwaggerEndpoint("/swagger/v1/swagger.json", "V1 Docs");
+ options.SwaggerEndpoint("/swagger/v2/swagger.json", "V2 Docs");
+});
+```
+snippet source | anchor
+
+
+
+## Apply swagger-ui Parameters
+
+[swagger-ui][swagger-ui] ships with its own set of configuration parameters, all described
+[by the swagger-ui Configuration](https://github.com/swagger-api/swagger-ui/blob/HEAD/docs/usage/configuration.md#display).
+In Swashbuckle.AspNetCore, most of these are surfaced through the SwaggerUI middleware options:
+
+
+
+
+```cs
+app.UseSwaggerUI(options =>
+{
+ options.DefaultModelExpandDepth(2);
+ options.DefaultModelRendering(ModelRendering.Model);
+ options.DefaultModelsExpandDepth(-1);
+ options.DisplayOperationId();
+ options.DisplayRequestDuration();
+ options.DocExpansion(DocExpansion.None);
+ options.EnableDeepLinking();
+ options.EnableFilter();
+ options.EnablePersistAuthorization();
+ options.EnableTryItOutByDefault();
+ options.MaxDisplayedTags(5);
+ options.ShowExtensions();
+ options.ShowCommonExtensions();
+ options.EnableValidator();
+ options.SupportedSubmitMethods(SubmitMethod.Get, SubmitMethod.Head);
+ options.UseRequestInterceptor("(request) => { return request; }");
+ options.UseResponseInterceptor("(response) => { return response; }");
+});
+```
+snippet source | anchor
+
+
+
+## Inject Custom JavaScript
+
+To tweak the behavior, you can inject additional JavaScript files by adding them to your `wwwroot` folder and specifying
+the relative paths in the middleware options:
+
+
+
+
+```cs
+app.UseSwaggerUI(options =>
+{
+ options.InjectJavascript("/swagger-ui/custom.js");
+});
+```
+snippet source | anchor
+
+
+
+## Inject Custom CSS
+
+To tweak the look and feel, you can inject additional CSS stylesheets by adding them to your `wwwroot` folder and specifying the
+relative paths in the middleware options:
+
+
+
+
+```cs
+app.UseSwaggerUI(options =>
+{
+ options.InjectStylesheet("/swagger-ui/custom.css");
+});
+```
+snippet source | anchor
+
+
+
+## Customize index.html
+
+To customize the UI beyond the basic options listed above, you can provide your own version of the swagger-ui `index.html` page:
+
+
+
+
+```cs
+app.UseSwaggerUI(options =>
+{
+ options.IndexStream = () => typeof(Program).Assembly
+ .GetManifestResourceStream("CustomUIIndex.Swagger.index.html"); // Requires file to be added as an embedded resource
+});
+```
+snippet source | anchor
+
+
+
+```xml
+
+
+
+
+
+```
+
+> [!TIP]
+> To get started, you should base your custom `index.html` on the [built-in version](../src/Swashbuckle.AspNetCore.SwaggerUI/index.html).
+
+## Enable OAuth2.0 Flows
+
+[swagger-ui][swagger-ui] has built-in support to participate in OAuth2.0 authorization flows. It interacts with authorization and/or token
+endpoints, as specified in the OpenAPI JSON, to obtain access tokens for subsequent API calls. See
+[Adding Security Definitions and Requirements](configure-and-customize-swaggergen.md#add-security-definitions-and-requirements) for
+an example of adding OAuth2.0 metadata to the generated Swagger.
+
+If your OpenAPI endpoint includes the appropriate security metadata, the UI interaction should be automatically enabled. However, you
+can further customize OAuth support in the UI with the following settings below. See the
+[Swagger-UI documentation](https://github.com/swagger-api/swagger-ui/blob/HEAD/docs/usage/oauth2.md) for more information.
+
+
+
+
+```cs
+app.UseSwaggerUI(options =>
+{
+ options.OAuthClientId("test-id");
+ options.OAuthClientSecret("test-secret");
+ options.OAuthUsername("test-user");
+ options.OAuthRealm("test-realm");
+ options.OAuthAppName("test-app");
+ options.OAuth2RedirectUrl("url");
+ options.OAuthScopeSeparator(" ");
+ options.OAuthScopes("scope1", "scope2");
+ options.OAuthAdditionalQueryStringParams(new Dictionary { ["foo"] = "bar" });
+ options.OAuthUseBasicAuthenticationWithAccessCodeGrant();
+ options.OAuthUsePkce();
+});
+```
+snippet source | anchor
+
+
+
+## Use client-side request and response interceptors
+
+To use custom interceptors on requests and responses going through swagger-ui you can define them as JavaScript functions
+in the configuration:
+
+
+
+
+```cs
+app.UseSwaggerUI(options =>
+{
+ options.UseRequestInterceptor("(req) => { req.headers['x-my-custom-header'] = 'MyCustomValue'; return req; }");
+ options.UseResponseInterceptor("(res) => { console.log('Custom interceptor intercepted response from:', res.url); return res; }");
+});
+```
+snippet source | anchor
+
+
+
+This can be useful in a range of scenarios where you might want to append local XSRF tokens to all requests, for example:
+
+
+
+
+```cs
+app.UseSwaggerUI(options =>
+{
+ options.UseRequestInterceptor("(req) => { req.headers['X-XSRF-Token'] = localStorage.getItem('xsrf-token'); return req; }");
+});
+```
+snippet source | anchor
+
+
+
+[swagger-ui]: https://github.com/swagger-api/swagger-ui
+
+## Use `MapSwaggerUI` with endpoint routing
+
+`MapSwaggerUI` is an endpoint-routing alternative to `UseSwaggerUI`. The options API is the same, so all customization examples on this page also apply when using `MapSwaggerUI`.
+
+
+
+
+```cs
+app.MapSwagger();
+app.MapSwaggerUI("swagger", options => options.SwaggerEndpoint("/swagger/v1/swagger.json", "V1 Docs"));
+```
+snippet source | anchor
+
+
+
+The main differences are:
+
+- **Use**SwaggerUI adds middleware directly to the request pipeline and returns `IApplicationBuilder`.
+- **Map**SwaggerUI maps the Swagger UI via endpoint routing and returns `IEndpointConventionBuilder`.
+
+- Because `MapSwaggerUI` returns an endpoint convention builder, you can attach endpoint metadata/conventions (for example, authorization policies) directly to the mapped UI endpoint.
+
+
+
+
+```cs
+app.MapSwaggerUI("swagger-auth")
+ .RequireAuthorization(); // Remember to also add RequireAuthorization to MapSwagger.
+```
+snippet source | anchor
+
+
diff --git a/src/Swashbuckle.AspNetCore.ReDoc/PublicAPI/PublicAPI.Unshipped.txt b/src/Swashbuckle.AspNetCore.ReDoc/PublicAPI/PublicAPI.Unshipped.txt
index 5f282702bb..d36ebb3ba0 100644
--- a/src/Swashbuckle.AspNetCore.ReDoc/PublicAPI/PublicAPI.Unshipped.txt
+++ b/src/Swashbuckle.AspNetCore.ReDoc/PublicAPI/PublicAPI.Unshipped.txt
@@ -1 +1 @@
-
\ No newline at end of file
+static Microsoft.AspNetCore.Builder.ReDocBuilderExtensions.MapReDoc(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string routePrefix = null, System.Action setupAction = null) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder
diff --git a/src/Swashbuckle.AspNetCore.ReDoc/ReDocBuilderExtensions.cs b/src/Swashbuckle.AspNetCore.ReDoc/ReDocBuilderExtensions.cs
index cb89a7748d..7d6d894069 100644
--- a/src/Swashbuckle.AspNetCore.ReDoc/ReDocBuilderExtensions.cs
+++ b/src/Swashbuckle.AspNetCore.ReDoc/ReDocBuilderExtensions.cs
@@ -1,4 +1,5 @@
-using Microsoft.Extensions.DependencyInjection;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Swashbuckle.AspNetCore.ReDoc;
@@ -21,16 +22,58 @@ public static IApplicationBuilder UseReDoc(
this IApplicationBuilder app,
Action setupAction = null)
{
- ReDocOptions options;
- using (var scope = app.ApplicationServices.CreateScope())
- {
- options = scope.ServiceProvider.GetRequiredService>().Value;
- setupAction?.Invoke(options);
- }
+ var options = ResolveOptions(app.ApplicationServices, setupAction);
+ EnsureDefaultSpecUrl(options);
+ return app.UseReDoc(options);
+ }
+
+ ///
+ /// Maps the Redoc middleware to the specified endpoint route.
+ ///
+ /// Endpoint route builder to which the Redoc middleware will be mapped.
+ /// Optional route prefix for the Redoc endpoint. If not provided, the value is used.
+ /// Optional setup action to configure the Redoc options.
+ /// An that can be used to further configure the endpoint.
+ public static IEndpointConventionBuilder MapReDoc(
+ this IEndpointRouteBuilder endpoints,
+ string routePrefix = null,
+ Action setupAction = null)
+ {
+ var options = ResolveOptions(endpoints.ServiceProvider, setupAction);
+
+ if (routePrefix != null)
+ {
+ options.RoutePrefix = routePrefix;
+ }
+ EnsureDefaultSpecUrl(options);
+
+ var pipeline = endpoints.CreateApplicationBuilder()
+ .UseReDoc(options)
+ .Build();
+
+ return endpoints.Map(GetRoutePattern(options.RoutePrefix), pipeline);
+ }
+
+ private static ReDocOptions ResolveOptions(IServiceProvider serviceProvider, Action setupAction)
+ {
+ using var scope = serviceProvider.CreateScope();
+ var options = scope.ServiceProvider.GetRequiredService>().Value;
+ setupAction?.Invoke(options);
+ return options;
+ }
+
+ private static void EnsureDefaultSpecUrl(ReDocOptions options)
+ {
// To simplify the common case, use a default that will work with the SwaggerMiddleware defaults
options.SpecUrl ??= "../swagger/v1/swagger.json";
+ }
- return app.UseReDoc(options);
+ private static string GetRoutePattern(string routePrefix)
+ {
+ var sanitizedRoutePrefix = routePrefix?.Trim('/');
+ return string.IsNullOrEmpty(sanitizedRoutePrefix)
+ ? "{**path}"
+ : $"{sanitizedRoutePrefix}/{{**path}}";
}
}
diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/PublicAPI/PublicAPI.Unshipped.txt b/src/Swashbuckle.AspNetCore.SwaggerUI/PublicAPI/PublicAPI.Unshipped.txt
index 5f282702bb..4bc6a2889c 100644
--- a/src/Swashbuckle.AspNetCore.SwaggerUI/PublicAPI/PublicAPI.Unshipped.txt
+++ b/src/Swashbuckle.AspNetCore.SwaggerUI/PublicAPI/PublicAPI.Unshipped.txt
@@ -1 +1 @@
-
\ No newline at end of file
+static Microsoft.AspNetCore.Builder.SwaggerUIBuilderExtensions.MapSwaggerUI(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string routePrefix = null, System.Action setupAction = null) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder
diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIBuilderExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIBuilderExtensions.cs
index 8855eb65dd..044297c18a 100644
--- a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIBuilderExtensions.cs
+++ b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIBuilderExtensions.cs
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Swashbuckle.AspNetCore.SwaggerUI;
@@ -20,20 +21,66 @@ public static IApplicationBuilder UseSwaggerUI(
this IApplicationBuilder app,
Action setupAction = null)
{
- SwaggerUIOptions options;
- using (var scope = app.ApplicationServices.CreateScope())
- {
- options = scope.ServiceProvider.GetRequiredService>().Value;
- setupAction?.Invoke(options);
- }
+ var options = ResolveOptions(app.ApplicationServices, setupAction);
+ var hostingEnv = app.ApplicationServices.GetRequiredService();
- // To simplify the common case, use a default that will work with the SwaggerMiddleware defaults
- if (options.ConfigObject.Urls == null)
- {
- var hostingEnv = app.ApplicationServices.GetRequiredService();
- options.ConfigObject.Urls = [new UrlDescriptor { Name = $"{hostingEnv.ApplicationName} v1", Url = "v1/swagger.json" }];
- }
+ EnsureDefaultUrl(options, hostingEnv.ApplicationName);
return app.UseSwaggerUI(options);
}
+
+ ///
+ /// Maps the SwaggerUI middleware to the specified endpoint route.
+ ///
+ ///
+ /// This is a convenience extension method that combines the registration of the SwaggerUI middleware with endpoint
+ /// routing. It allows you to specify the route pattern for the SwaggerUI and optionally configure the .
+ ///
+ /// Endpoint route builder to which the SwaggerUI middleware will be mapped.
+ /// Optional: The route to register SwaggerUI on. Defaults to the value.
+ /// Optional setup action to configure the .
+ /// An that can be used to further configure the endpoint.
+ public static IEndpointConventionBuilder MapSwaggerUI(
+ this IEndpointRouteBuilder endpoints,
+ string routePrefix = null,
+ Action setupAction = null)
+ {
+ var options = ResolveOptions(endpoints.ServiceProvider, setupAction);
+
+ if (routePrefix != null)
+ {
+ options.RoutePrefix = routePrefix;
+ }
+
+ var hostingEnv = endpoints.ServiceProvider.GetRequiredService();
+ EnsureDefaultUrl(options, hostingEnv.ApplicationName);
+
+ var pipeline = endpoints.CreateApplicationBuilder()
+ .UseSwaggerUI(options)
+ .Build();
+
+ return endpoints.Map(GetRoutePattern(options.RoutePrefix), pipeline);
+ }
+
+ private static SwaggerUIOptions ResolveOptions(IServiceProvider serviceProvider, Action setupAction)
+ {
+ using var scope = serviceProvider.CreateScope();
+ var options = scope.ServiceProvider.GetRequiredService>().Value;
+ setupAction?.Invoke(options);
+ return options;
+ }
+
+ private static void EnsureDefaultUrl(SwaggerUIOptions options, string applicationName)
+ {
+ // To simplify the common case, use a default that will work with the SwaggerMiddleware defaults
+ options.ConfigObject.Urls ??= [new UrlDescriptor { Name = $"{applicationName} v1", Url = "v1/swagger.json" }];
+ }
+
+ private static string GetRoutePattern(string routePrefix)
+ {
+ var sanitizedRoutePrefix = routePrefix?.Trim('/');
+ return string.IsNullOrEmpty(sanitizedRoutePrefix)
+ ? "{**path}"
+ : $"{sanitizedRoutePrefix}/{{**path}}";
+ }
}
diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/ReDocIntegrationTests.cs b/test/Swashbuckle.AspNetCore.IntegrationTests/ReDocIntegrationTests.cs
index 186665fdb2..9257dc805a 100644
--- a/test/Swashbuckle.AspNetCore.IntegrationTests/ReDocIntegrationTests.cs
+++ b/test/Swashbuckle.AspNetCore.IntegrationTests/ReDocIntegrationTests.cs
@@ -3,6 +3,7 @@
using System.Net.Http.Headers;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Mvc.Testing;
using Swashbuckle.AspNetCore.ReDoc;
using ReDocApp = ReDoc;
@@ -53,6 +54,32 @@ static void AssertResource(HttpResponseMessage response)
}
}
+
+ [Theory]
+ [InlineData("/swagger/v1/swagger.json", "application/json")]
+ [InlineData("/swagger/v1/swagger.yaml", "text/yaml")]
+ [InlineData("/swagger/v1/swagger.yml", "text/yaml")]
+ [InlineData("/api-docs/index.html", "text/html")]
+ public async Task MapSwaggerAndMapReDoc_ReturnExpectedEndpoints(string path, string mediaType)
+ {
+ var client = new WebApplicationFactory().CreateClient();
+
+ var response = await client.GetAsync(path, TestContext.Current.CancellationToken);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(mediaType, response.Content.Headers.ContentType?.MediaType);
+ }
+
+ [Theory]
+ [InlineData("/redoc-auth/index.html")]
+ public async Task MapReDoc_RequireAuthorization_ReturnUnauthorized(string path)
+ {
+ var client = new WebApplicationFactory().CreateClient();
+ var response = await client.GetAsync(path, TestContext.Current.CancellationToken);
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+ }
+
+
[Fact]
public async Task RedocMiddleware_ReturnsInitializerScript()
{
diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerAndSwaggerUIIntegrationTests.cs b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerAndSwaggerUIIntegrationTests.cs
index ad3e8d9400..1300fc25ea 100644
--- a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerAndSwaggerUIIntegrationTests.cs
+++ b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerAndSwaggerUIIntegrationTests.cs
@@ -19,4 +19,28 @@ public async Task SwaggerDocWithoutSubdirectory(string path, string mediaType)
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(mediaType, response.Content.Headers.ContentType?.MediaType);
}
+
+ [Theory]
+ [InlineData("/swagger/v1/swagger.json", "application/json")]
+ [InlineData("/swagger/v1/swagger.yaml", "text/yaml")]
+ [InlineData("/swagger/v1/swagger.yml", "text/yaml")]
+ [InlineData("/swagger/index.html", "text/html")]
+ public async Task Map_Methods_ReturnExpectedEndpoints(string path, string mediaType)
+ {
+ var client = new WebApplicationFactory().CreateClient();
+
+ var response = await client.GetAsync(path, TestContext.Current.CancellationToken);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(mediaType, response.Content.Headers.ContentType?.MediaType);
+ }
+
+ [Theory]
+ [InlineData("/swagger-auth/index.html")]
+ public async Task MapSwaggerUI_And_MapReDoc_RequireAuthorization_ReturnUnauthorized(string path)
+ {
+ var client = new WebApplicationFactory().CreateClient();
+ var response = await client.GetAsync(path, TestContext.Current.CancellationToken);
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+ }
}
diff --git a/test/WebSites/WebApi.Map/Program.cs b/test/WebSites/WebApi.Map/Program.cs
new file mode 100644
index 0000000000..de2c440824
--- /dev/null
+++ b/test/WebSites/WebApi.Map/Program.cs
@@ -0,0 +1,82 @@
+using System.Text.Json.Serialization;
+using Microsoft.AspNetCore.Http.Json;
+
+// This exists just to test and verify that the MapSwagger and MapSwaggerUI works as expected.
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.Configure(options => options.SerializerOptions.Converters.Add(new JsonStringEnumConverter()));
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen(options => options.SwaggerDoc("v1", new() { Title = "WebApi", Version = "v1" }));
+
+// Authentication and Authorization are added to verify that MapSwaggerUI and MapReDoc work as expected when RequireAuthorization is used.
+builder.Services.AddAuthentication().AddBearerToken();
+builder.Services.AddAuthorization();
+
+var app = builder.Build();
+
+app.UseAuthentication();
+app.UseAuthorization();
+
+// begin-snippet: SwaggerUI-MapSwaggerUI
+app.MapSwagger();
+app.MapSwaggerUI("swagger", options => options.SwaggerEndpoint("/swagger/v1/swagger.json", "V1 Docs"));
+// end-snippet
+
+// begin-snippet: SwaggerUI-MapSwaggerUI-RequireAuthorization
+app.MapSwaggerUI("swagger-auth")
+ .RequireAuthorization(); // Remember to also add RequireAuthorization to MapSwagger.
+// end-snippet
+
+
+// Also Map redoc in the same file
+// begin-snippet: Redoc-MapReDoc
+app.MapReDoc();
+// end-snippet
+
+// begin-snippet: Redoc-MapReDoc-RequireAuthorization
+app.MapReDoc("redoc-auth")
+ .RequireAuthorization(); // Remember to also add RequireAuthorization to MapSwagger
+// end-snippet
+
+app.UseHttpsRedirection();
+
+string[] summaries =
+[
+ "Freezing",
+ "Bracing",
+ "Chilly",
+ "Cool",
+ "Mild",
+ "Warm",
+ "Balmy",
+ "Hot",
+ "Sweltering",
+ "Scorching",
+];
+
+app.MapGet("/weatherforecast", () =>
+{
+ var forecast = Enumerable.Range(1, 5).Select(index =>
+ new
+ {
+ Date = DateTime.Now.AddDays(index),
+ TemperatureC = Random.Shared.Next(-20, 55),
+ Summary = summaries[Random.Shared.Next(summaries.Length)]
+ }
+ )
+ .ToArray();
+ return forecast;
+});
+
+app.Run();
+
+namespace WebApi.Map
+{
+ ///
+ /// Expose the Program class for use with WebApplicationFactory
+ ///
+ public partial class Program
+ {
+ }
+}
diff --git a/test/WebSites/WebApi.Map/Properties/launchSettings.json b/test/WebSites/WebApi.Map/Properties/launchSettings.json
new file mode 100644
index 0000000000..c003abfed9
--- /dev/null
+++ b/test/WebSites/WebApi.Map/Properties/launchSettings.json
@@ -0,0 +1,15 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "http://localhost:5154",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/test/WebSites/WebApi.Map/WebApi.Map.csproj b/test/WebSites/WebApi.Map/WebApi.Map.csproj
new file mode 100644
index 0000000000..ccee2d6d9e
--- /dev/null
+++ b/test/WebSites/WebApi.Map/WebApi.Map.csproj
@@ -0,0 +1,17 @@
+
+
+
+ enable
+ enable
+ WebApi.Map
+ $(DefaultTargetFrameworks)
+ true
+
+
+
+
+
+
+
+
+
diff --git a/test/WebSites/WebApi.Map/WebApi.http b/test/WebSites/WebApi.Map/WebApi.http
new file mode 100644
index 0000000000..abc2f6184b
--- /dev/null
+++ b/test/WebSites/WebApi.Map/WebApi.http
@@ -0,0 +1,4 @@
+@WebApi_HostAddress = http://localhost:5205
+
+GET {{WebApi_HostAddress}}/weatherforecast/
+Accept: application/json
diff --git a/test/WebSites/WebApi.Map/appsettings.Development.json b/test/WebSites/WebApi.Map/appsettings.Development.json
new file mode 100644
index 0000000000..0c208ae918
--- /dev/null
+++ b/test/WebSites/WebApi.Map/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/test/WebSites/WebApi.Map/appsettings.json b/test/WebSites/WebApi.Map/appsettings.json
new file mode 100644
index 0000000000..10f68b8c8b
--- /dev/null
+++ b/test/WebSites/WebApi.Map/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}