Customize your API documentation in ASP.NET Core applications!
+### Beautiful, modern themes for Swagger/OpenAPI documentation in ASP.NET Core
+
+Make your API documentation look great with themes that fit your style.
+
+**[Get Started](https://github.com/teociaps/SwaggerUI.Themes/wiki/Getting-Started)** • **[View Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Predefined-Themes)** • **[Full Documentation](https://github.com/teociaps/SwaggerUI.Themes/wiki)**
+
+---
+
+| Package | Purpose | NuGet |
+|---------|---------|-------|
+| **AspNetCore.SwaggerUI.Themes** | For [Swashbuckle.AspNetCore][swashbuckle-link] | [![swashbuckle-nuget]][swashbuckle-nuget-link] |
+| **NSwag.AspNetCore.Themes** | For [NSwag.AspNetCore][nswag-link] | [![nswag-nuget]][nswag-nuget-link] |
+
+---
+
+
+
+> [!WARNING]
+> **Version 3.0.0 Breaking Changes**
+>
+> Upgrading from v2.0.0? Please review the **[Migration Guide](https://github.com/teociaps/SwaggerUI.Themes/wiki/Migration-v3)** for important API changes.
+
+
+## 🚀 Quick Start
+
+```bash
+# Install package
+dotnet add package AspNetCore.SwaggerUI.Themes
+# or
+dotnet add package NSwag.AspNetCore.Themes
+```
+
+```csharp
+// Apply a theme - that's it!
+app.UseSwaggerUI(Theme.Dark); // Swashbuckle
+// or
+app.UseSwaggerUi(Theme.Dark); // NSwag
+```
+
+## ✨ Features
+
+- **[Built-in Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Predefined-Themes)** - Choose from predefined themes ready to use
+
+- **[Custom Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Custom-Themes)** - Build your own themes with full control, or create standalone themes with zero dependencies
+
+- **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Enhance your documentation with new capabilities
+
+**Discover more in the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki)!**
+
+## 💡 Basic Usage Examples
+
+### Swashbuckle
+
+```csharp
+using AspNetCore.Swagger.Themes;
+
+...
+
+// Simple
+app.UseSwaggerUI(Theme.Dark);
+
+// Or with advanced options
+app.UseSwaggerUI(Theme.Dark, c =>
+{
+ c.EnableAllAdvancedOptions();
+});
+
+...
+```
-
+### NSwag
+```csharp
+using AspNetCore.Swagger.Themes;
-
+...
+// Simple
+app.UseSwaggerUi(Theme.Dark);
-| Package | Purpose |
-| :------ | :------ |
-| [![Swashbuckle Nuget Version]](https://www.nuget.org/packages/AspNetCore.SwaggerUI.Themes/) | Customize the style for [Swashbuckle.AspNetCore.SwaggerUI] |
-| [![NSwag Nuget Version]](https://www.nuget.org/packages/NSwag.AspNetCore.Themes/) | Customize the style for [NSwag.AspNetCore] |
+// Or with advanced options
+app.UseSwaggerUi(Theme.Dark, c =>
+{
+ c.EnableAllAdvancedOptions();
+});
+...
+```
-## Features
+### Custom Theme
-- **[Predefined Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Predefined-Themes)**: choose from a variety of themes to customize the Swagger documentation interface. Options include a default style that preserves the classic Swagger UI look, along with fresh, modern styles.
+```csharp
+public class MyTheme : Theme
+{
+ protected MyTheme() : base("my-theme.css") { }
+ public static MyTheme Custom => new();
+}
-- **[Custom Styles](https://github.com/teociaps/SwaggerUI.Themes/wiki/Custom-Styles)**: design your own Swagger UI style by either extending the classic or modern base styles or creating a completely new look.
+// Usage
+app.UseSwaggerUI(MyTheme.Custom); // Swashbuckle
-- **[Advanced Options](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)**: access expanded features with both classic and modern styles for an optimized API documentation experience.
+app.UseSwaggerUi(MyTheme.Custom); // NSwag
+```
+**Learn more in the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki)!**
-## Contributing
+## 🤝 Contributing
-If you have any suggestions, bug reports, or contributions, feel free to open an [issue](https://github.com/teociaps/SwaggerUI.Themes/issues) or submit a [pull request](https://github.com/teociaps/SwaggerUI.Themes/pulls).
+Contributions are welcome! See the [Contributing Guide](CONTRIBUTING.md) for details.
+## 📜 License
-## Release
+MIT Licensed - see [LICENSE](LICENSE) for details.
-See the [release notes](https://github.com/teociaps/SwaggerUI.Themes/releases) for details.
+---
+
+**Made with ❤️ by [@teociaps](https://github.com/teociaps)**
-[Swashbuckle Nuget Version]: https://img.shields.io/nuget/v/AspNetCore.SwaggerUI.Themes?logo=nuget&label=AspNetCore.SwaggerUI.Themes&color=blue
-[NSwag Nuget Version]: https://img.shields.io/nuget/v/NSwag.AspNetCore.Themes?logo=nuget&label=NSwag.AspNetCore.Themes&color=blue
+
-[Swashbuckle.AspNetCore.SwaggerUI]: https://github.com/domaindrivendev/Swashbuckle.AspNetCore
-[NSwag.AspNetCore]: https://github.com/RicoSuter/NSwag?tab=readme-ov-file#aspnet-and-aspnet-core
+
+[swashbuckle-nuget]: https://img.shields.io/nuget/v/AspNetCore.SwaggerUI.Themes?logo=nuget&label=Version&color=blue
+[swashbuckle-nuget-link]: https://www.nuget.org/packages/AspNetCore.SwaggerUI.Themes/
+[nswag-nuget]: https://img.shields.io/nuget/v/NSwag.AspNetCore.Themes?logo=nuget&label=Version&color=blue
+[nswag-nuget-link]: https://www.nuget.org/packages/NSwag.AspNetCore.Themes/
+[swashbuckle-link]: https://github.com/domaindrivendev/Swashbuckle.AspNetCore
+[nswag-link]: https://github.com/RicoSuter/NSwag
\ No newline at end of file
diff --git a/build/NuGet.props b/build/NuGet.props
index 95f861f..dd5d805 100644
--- a/build/NuGet.props
+++ b/build/NuGet.props
@@ -20,7 +20,7 @@
-
+
diff --git a/package-readme.md b/package-readme.md
deleted file mode 100644
index ecf20c3..0000000
--- a/package-readme.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# SwaggerUI.Themes
-
-Change style to your API documentation in ASP.NET Core applications.
-
-
-## Features
-- __New Themes__: Choose from a variety of themes to customize the Swagger documentation interface. Options include a default style that preserves the classic Swagger UI look, along with fresh, modern styles.
-- __Advanced Options__: Access expanded features with both classic and modern styles for an optimized API documentation experience.
-- __Custom Styles__: Design your own Swagger UI style by either extending the classic or modern base styles or creating a completely new look.
-- __Easy Integration__: and add style parameters to the existing Swagger UI setup for a seamless upgrade.
-
-
-[Getting started](https://github.com/teociaps/SwaggerUI.Themes/wiki/Getting-Started)!
\ No newline at end of file
diff --git a/src/AspNetCore.SwaggerUI.Themes/package-readme.md b/src/AspNetCore.SwaggerUI.Themes/package-readme.md
new file mode 100644
index 0000000..917be5f
--- /dev/null
+++ b/src/AspNetCore.SwaggerUI.Themes/package-readme.md
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+# AspNetCore.SwaggerUI.Themes
+
+### Beautiful, modern themes for Swashbuckle Swagger UI in ASP.NET Core
+
+Make your API documentation look great with themes that fit your style.
+
+**[Get Started](https://github.com/teociaps/SwaggerUI.Themes/wiki/Getting-Started)** • **[View Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Predefined-Themes)** • **[Documentation](https://github.com/teociaps/SwaggerUI.Themes/wiki)**
+
+
+
+> ⚠️ **Version 3.0 Breaking Changes**
+> Upgrading from v2.0.0? Please review the **[Migration Guide](https://github.com/teociaps/SwaggerUI.Themes/wiki/Migration-v3)** for important API changes.
+
+## Installation
+
+```bash
+dotnet add package AspNetCore.SwaggerUI.Themes
+```
+
+## Quick Start
+
+```csharp
+app.UseSwaggerUI(Theme.Dark);
+```
+
+> **Note**: The `UseSwaggerUI()` method is provided by Swashbuckle.AspNetCore. This package adds convenient overloads to apply themes seamlessly.
+
+## Features
+
+- **[Built-in Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Predefined-Themes)** - Predefined themes ready to use
+
+- **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Unlock new capabilities to enhance your Swagger UI
+
+- **[Custom Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Custom-Themes)** - Create your own themes with full control or build standalone themes
+
+## Usage
+
+```csharp
+using AspNetCore.Swagger.Themes;
+
+...
+
+// Use a built-in theme
+app.UseSwaggerUI(Theme.Dark);
+
+// Or with advanced features
+app.UseSwaggerUI(Theme.Dark, c =>
+{
+ c.EnableAllAdvancedOptions();
+});
+
+// Or use your custom theme from assembly
+app.UseSwaggerUI(Assembly.GetExecutingAssembly(), "my-theme.css");
+
+...
+```
+
+---
+
+#### Discover all the features and customization options in the [documentation](https://github.com/teociaps/SwaggerUI.Themes/wiki)!
\ No newline at end of file
diff --git a/src/AspNetCore.SwaggerUI.Themes/readme.txt b/src/AspNetCore.SwaggerUI.Themes/readme.txt
index 94239be..90c9275 100644
--- a/src/AspNetCore.SwaggerUI.Themes/readme.txt
+++ b/src/AspNetCore.SwaggerUI.Themes/readme.txt
@@ -2,11 +2,20 @@
## RELEASE NOTES ##
#-------------------#
-v2.1.0
+v3.0.0
-Changes:
-- .NET 10 support!
-- .NET 6 & .NET 7 support dropped.
+BREAKING CHANGES:
+- API redesign: Style → Theme (all classes renamed)
+- Classic themes removed (modern-only going forward)
+- See migration guide: https://github.com/teociaps/SwaggerUI.Themes/wiki/Migration-v3
+Other Changes:
+- .NET 10 support; .NET 6 & 7 discontinued
+- Unified theme system with modern defaults
+- Standalone theme support (zero dependencies)
+- Minification control via useMinified parameter
+- Advanced features available on all themes
+- 50% smaller package footprint
+- Enhanced performance and clarity
-More info: https://github.com/teociaps/SwaggerUI.Themes?tab=readme-ov-file#swaggeruithemes
\ No newline at end of file
+For details: https://github.com/teociaps/SwaggerUI.Themes?tab=readme-ov-file#swaggeruithemes
\ No newline at end of file
diff --git a/src/NSwag.AspNetCore.Themes/package-readme.md b/src/NSwag.AspNetCore.Themes/package-readme.md
new file mode 100644
index 0000000..761e63e
--- /dev/null
+++ b/src/NSwag.AspNetCore.Themes/package-readme.md
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+# NSwag.AspNetCore.Themes
+
+### Beautiful, modern themes for NSwag Swagger UI in ASP.NET Core
+
+Make your API documentation look great with themes that fit your style.
+
+**[Get Started](https://github.com/teociaps/SwaggerUI.Themes/wiki/Getting-Started)** • **[View Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Predefined-Themes)** • **[Documentation](https://github.com/teociaps/SwaggerUI.Themes/wiki)**
+
+
+
+> ⚠️ **Version 3.0 Breaking Changes**
+> Upgrading from v2.0.0? Please review the **[Migration Guide](https://github.com/teociaps/SwaggerUI.Themes/wiki/Migration-v3)** for important API changes.
+
+## Installation
+
+```bash
+dotnet add package NSwag.AspNetCore.Themes
+```
+
+## Quick Start
+
+```csharp
+app.UseSwaggerUi(Theme.Dark);
+```
+
+> **Note**: The `UseSwaggerUi()` method is provided by NSwag.AspNetCore. This package adds convenient overloads to apply themes seamlessly.
+
+## Features
+
+- **[Built-in Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Predefined-Themes)** - Predefined themes ready to use
+
+- **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Unlock new capabilities to enhance your Swagger UI
+
+- **[Custom Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Custom-Themes)** - Create your own themes with full control or build standalone themes
+
+## Usage
+
+```csharp
+using AspNetCore.Swagger.Themes;
+
+...
+
+// Use a built-in theme
+app.UseSwaggerUi(Theme.Dark);
+
+// Or with advanced features
+app.UseSwaggerUi(Theme.Dark, c =>
+{
+ c.EnableAllAdvancedOptions();
+});
+
+// Or use your custom theme from assembly
+app.UseSwaggerUi(Assembly.GetExecutingAssembly(), "my-theme.css");
+
+...
+```
+
+---
+
+#### Discover all the features and customization options in the [documentation](https://github.com/teociaps/SwaggerUI.Themes/wiki)!
\ No newline at end of file
diff --git a/src/NSwag.AspNetCore.Themes/readme.txt b/src/NSwag.AspNetCore.Themes/readme.txt
index 94239be..90c9275 100644
--- a/src/NSwag.AspNetCore.Themes/readme.txt
+++ b/src/NSwag.AspNetCore.Themes/readme.txt
@@ -2,11 +2,20 @@
## RELEASE NOTES ##
#-------------------#
-v2.1.0
+v3.0.0
-Changes:
-- .NET 10 support!
-- .NET 6 & .NET 7 support dropped.
+BREAKING CHANGES:
+- API redesign: Style → Theme (all classes renamed)
+- Classic themes removed (modern-only going forward)
+- See migration guide: https://github.com/teociaps/SwaggerUI.Themes/wiki/Migration-v3
+Other Changes:
+- .NET 10 support; .NET 6 & 7 discontinued
+- Unified theme system with modern defaults
+- Standalone theme support (zero dependencies)
+- Minification control via useMinified parameter
+- Advanced features available on all themes
+- 50% smaller package footprint
+- Enhanced performance and clarity
-More info: https://github.com/teociaps/SwaggerUI.Themes?tab=readme-ov-file#swaggeruithemes
\ No newline at end of file
+For details: https://github.com/teociaps/SwaggerUI.Themes?tab=readme-ov-file#swaggeruithemes
\ No newline at end of file
From 60845c3c5fd8cea1cb4d855dccc5de2c1cb9a324 Mon Sep 17 00:00:00 2001
From: Teuz
Date: Tue, 18 Nov 2025 20:04:09 +0100
Subject: [PATCH 04/18] Refactor package references NSwag.AspNetCore and
Swashbuckle.AspNetCore in project files
---
.../Sample.AspNetCore.SwaggerUI.NSwag.csproj | 4 ----
.../Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj | 4 ----
.../AspNetCore.SwaggerUI.Themes.csproj | 2 +-
src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj | 2 +-
4 files changed, 2 insertions(+), 10 deletions(-)
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Sample.AspNetCore.SwaggerUI.NSwag.csproj b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Sample.AspNetCore.SwaggerUI.NSwag.csproj
index c9f060e..50d5c02 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Sample.AspNetCore.SwaggerUI.NSwag.csproj
+++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Sample.AspNetCore.SwaggerUI.NSwag.csproj
@@ -8,10 +8,6 @@
false
-
-
-
-
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj
index 93bc22c..c690065 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj
@@ -8,10 +8,6 @@
false
-
-
-
-
diff --git a/src/AspNetCore.SwaggerUI.Themes/AspNetCore.SwaggerUI.Themes.csproj b/src/AspNetCore.SwaggerUI.Themes/AspNetCore.SwaggerUI.Themes.csproj
index 0fce925..312b5c4 100644
--- a/src/AspNetCore.SwaggerUI.Themes/AspNetCore.SwaggerUI.Themes.csproj
+++ b/src/AspNetCore.SwaggerUI.Themes/AspNetCore.SwaggerUI.Themes.csproj
@@ -24,7 +24,7 @@
-
+
diff --git a/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj b/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj
index df352a9..d2ff579 100644
--- a/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj
+++ b/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj
@@ -24,7 +24,7 @@
-
+
From 55a913c2a040655a32c97a17f96e79ff67f10bfc Mon Sep 17 00:00:00 2001
From: Teuz
Date: Sat, 22 Nov 2025 01:06:10 +0100
Subject: [PATCH 05/18] Introduce dynamic theme switcher for Swagger UI
Introduced a theme switcher feature enabling users to dynamically switch between predefined and custom themes in Swagger UI. Added support for predefined themes and custom themes.
Enhanced `Program.cs` with examples of using themes and advanced options. Updated `FileProvider.cs` to expose theme metadata as a JSON endpoint for the theme switcher. Refactored `BaseTheme.cs` for better theme name handling.
Updated CSS and JavaScript files to support the theme switcher, including dropdown styles and runtime switching logic. Added new CSS files for predefined and custom themes. Improved caching, resource management, and advanced UI features in `AdvancedOptions.cs` and `FileProvider.cs`.
Enhanced testing coverage with `ThemeSwitcherTests`, `FileProviderMiddlewareTests`, and updates to `ThemeProviderTests`. Improved documentation, comments, and ensured backward compatibility with existing configurations.
---
SwaggerUI.Themes.sln | 4 +-
build/Common.Core.props | 5 +-
.../CompanyThemes/CompanyThemes.cs | 19 +
.../CompanyThemes/corporate-blue.css | 124 +++++
.../CompanyThemes/startup-purple.css | 140 +++++
.../CompanyThemes/tech-green.css | 129 +++++
.../Program.cs | 186 ++++++-
.../Sample.AspNetCore.SwaggerUI.NSwag.csproj | 9 +-
.../CustomMinifiedStyle.cs} | 11 +-
.../SwaggerThemes/Custom/CustomTheme.cs | 12 +
.../SwaggerThemes/{ => Custom}/custom.css | 0
.../{ => Custom}/minifiedCustom.min.css | 0
.../SwaggerThemes/SeasonalThemes.cs | 23 +
.../SwaggerThemes/holiday-red.css | 51 ++
.../SwaggerThemes/ocean-blue.css | 45 ++
.../CompanyThemes/CompanyThemes.cs | 19 +
.../CompanyThemes/corporate-blue.css | 124 +++++
.../CompanyThemes/startup-purple.css | 140 +++++
.../CompanyThemes/tech-green.css | 129 +++++
.../Program.cs | 184 ++++++-
...le.AspNetCore.SwaggerUI.Swashbuckle.csproj | 9 +-
.../CustomMinifiedStyle.cs} | 11 +-
.../SwaggerThemes/Custom/CustomTheme.cs | 12 +
.../SwaggerThemes/{ => Custom}/custom.css | 1 +
.../{ => Custom}/minifiedCustom.min.css | 0
.../SwaggerThemes/SeasonalThemes.cs | 23 +
.../SwaggerThemes/holiday-red.css | 51 ++
.../SwaggerThemes/ocean-blue.css | 45 ++
.../Swagger/Themes/AdvancedOptions.cs | 14 +-
.../AspNetCore/Swagger/Themes/BaseTheme.cs | 17 +-
.../AspNetCore/Swagger/Themes/FileProvider.cs | 119 ++++-
.../AspNetCore/Swagger/Themes/MimeTypes.cs | 11 +
.../AspNetCore/Swagger/Themes/Scripts/ui.js | 173 +++++++
.../Swagger/Themes/Scripts/ui.min.js | 4 +-
.../Swagger/Themes/Styles/common.css | 17 +-
.../Swagger/Themes/Styles/common.min.css | 2 +-
.../Swagger/Themes/Styles/dark.min.css | 2 +-
.../Swagger/Themes/Styles/deepsea.min.css | 2 +-
.../Swagger/Themes/Styles/desert.min.css | 2 +-
.../Swagger/Themes/Styles/forest.min.css | 2 +-
.../Swagger/Themes/Styles/futuristic.min.css | 2 +-
.../Swagger/Themes/Styles/light.min.css | 2 +-
.../AspNetCore/Swagger/Themes/Theme.cs | 10 -
.../Swagger/Themes/ThemeBuilderHelpers.cs | 70 +++
.../Swagger/Themes/ThemeSwitcher.cs | 325 ++++++++++++
.../Swagger/Themes/ThemeSwitcherOptions.cs | 224 ++++++++
.../Builder/SwaggerUIBuilderExtensions.cs | 154 +++---
.../Builder/SwaggerUIOptionsExtensions.cs | 150 +++---
.../Builder/NSwagBuilderExtensions.cs | 97 ++--
.../Builder/SwaggerUiSettingsExtensions.cs | 146 +++---
.../FileProviderMiddlewareTests.cs | 117 +++++
.../ThemeProviderTests.cs | 80 +--
.../ThemeSwitcherTests.cs | 488 ++++++++++++++++++
53 files changed, 3354 insertions(+), 382 deletions(-)
create mode 100644 samples/Sample.AspNetCore.SwaggerUI.NSwag/CompanyThemes/CompanyThemes.cs
create mode 100644 samples/Sample.AspNetCore.SwaggerUI.NSwag/CompanyThemes/corporate-blue.css
create mode 100644 samples/Sample.AspNetCore.SwaggerUI.NSwag/CompanyThemes/startup-purple.css
create mode 100644 samples/Sample.AspNetCore.SwaggerUI.NSwag/CompanyThemes/tech-green.css
rename samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/{CustomTheme.cs => Custom/CustomMinifiedStyle.cs} (58%)
create mode 100644 samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/CustomTheme.cs
rename samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/{ => Custom}/custom.css (100%)
rename samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/{ => Custom}/minifiedCustom.min.css (100%)
create mode 100644 samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/SeasonalThemes.cs
create mode 100644 samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/holiday-red.css
create mode 100644 samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/ocean-blue.css
create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/CompanyThemes/CompanyThemes.cs
create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/CompanyThemes/corporate-blue.css
create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/CompanyThemes/startup-purple.css
create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/CompanyThemes/tech-green.css
rename samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/{CustomTheme.cs => Custom/CustomMinifiedStyle.cs} (58%)
create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/CustomTheme.cs
rename samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/{ => Custom}/custom.css (86%)
rename samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/{ => Custom}/minifiedCustom.min.css (100%)
create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/SeasonalThemes.cs
create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/holiday-red.css
create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/ocean-blue.css
create mode 100644 src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeBuilderHelpers.cs
create mode 100644 src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeSwitcher.cs
create mode 100644 src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeSwitcherOptions.cs
create mode 100644 tests/AspNetCore.SwaggerUI.Themes.Tests/FileProviderMiddlewareTests.cs
create mode 100644 tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeSwitcherTests.cs
diff --git a/SwaggerUI.Themes.sln b/SwaggerUI.Themes.sln
index a64a09c..fc8d08a 100644
--- a/SwaggerUI.Themes.sln
+++ b/SwaggerUI.Themes.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.9.34414.90
+# Visual Studio Version 18
+VisualStudioVersion = 18.0.11205.157 d18.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore.SwaggerUI.Themes", "src\AspNetCore.SwaggerUI.Themes\AspNetCore.SwaggerUI.Themes.csproj", "{5FC2CEDA-5475-4096-BDEC-1C9BFC933FB8}"
EndProject
diff --git a/build/Common.Core.props b/build/Common.Core.props
index e58adf7..a199fed 100644
--- a/build/Common.Core.props
+++ b/build/Common.Core.props
@@ -1,10 +1,11 @@
-
net8.0;net9.0;net10.014.0enable
-
+
+ true
+
\ No newline at end of file
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/CompanyThemes/CompanyThemes.cs b/samples/Sample.AspNetCore.SwaggerUI.NSwag/CompanyThemes/CompanyThemes.cs
new file mode 100644
index 0000000..2f894d2
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/CompanyThemes/CompanyThemes.cs
@@ -0,0 +1,19 @@
+using AspNetCore.Swagger.Themes;
+
+namespace CompanyThemes;
+
+///
+/// Company brand themes for Swagger UI.
+///
+public class CompanyThemes : Theme
+{
+ protected CompanyThemes(string fileName) : base(fileName)
+ {
+ }
+
+ public static CompanyThemes CorporateBlue => new("corporate-blue.css");
+
+ public static CompanyThemes TechGreen => new("tech-green.css");
+
+ public static CompanyThemes StartupPurple => new("startup-purple.css");
+}
\ No newline at end of file
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/CompanyThemes/corporate-blue.css b/samples/Sample.AspNetCore.SwaggerUI.NSwag/CompanyThemes/corporate-blue.css
new file mode 100644
index 0000000..175845c
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/CompanyThemes/corporate-blue.css
@@ -0,0 +1,124 @@
+/* Corporate Blue Theme - Professional Business */
+
+:root {
+ --primary-color: #0066cc;
+ --primary-dark: #004999;
+ --primary-light: #3385d6;
+ --secondary-color: #5c7080;
+ --background-dark: #1a2332;
+ --background-light: #2c3e50;
+ --text-primary: #ecf0f1;
+ --text-secondary: #bdc3c7;
+ --border-color: #34495e;
+ --success-color: #27ae60;
+ --error-color: #e74c3c;
+}
+
+.swagger-ui {
+ background-color: var(--background-dark);
+ color: var(--text-primary);
+}
+
+.swagger-ui .topbar {
+ background-color: var(--primary-color);
+ border-bottom: 3px solid var(--primary-dark);
+}
+
+.swagger-ui .topbar .download-url-wrapper input[type=text] {
+ background-color: var(--background-light);
+ border-color: var(--border-color);
+ color: var(--text-primary);
+}
+
+.swagger-ui .info .title {
+ color: var(--primary-light);
+}
+
+.swagger-ui .info .base-url {
+ color: var(--text-secondary);
+}
+
+.swagger-ui .scheme-container {
+ background-color: var(--background-light);
+ border-color: var(--border-color);
+}
+
+.swagger-ui .opblock {
+ border-color: var(--border-color);
+ background-color: var(--background-light);
+}
+
+.swagger-ui .opblock.opblock-get {
+ background-color: rgba(0, 102, 204, 0.1);
+ border-color: var(--primary-color);
+}
+
+.swagger-ui .opblock.opblock-get .opblock-summary {
+ border-color: var(--primary-color);
+}
+
+.swagger-ui .opblock.opblock-post {
+ background-color: rgba(39, 174, 96, 0.1);
+ border-color: var(--success-color);
+}
+
+.swagger-ui .opblock.opblock-post .opblock-summary {
+ border-color: var(--success-color);
+}
+
+.swagger-ui .opblock.opblock-put {
+ background-color: rgba(243, 156, 18, 0.1);
+ border-color: #f39c12;
+}
+
+.swagger-ui .opblock.opblock-delete {
+ background-color: rgba(231, 76, 60, 0.1);
+ border-color: var(--error-color);
+}
+
+.swagger-ui .opblock-summary-method {
+ background-color: var(--primary-color);
+ color: white;
+ font-weight: 600;
+}
+
+.swagger-ui .opblock-summary-path {
+ color: var(--text-primary);
+}
+
+.swagger-ui .btn {
+ background-color: var(--primary-color);
+ border-color: var(--primary-dark);
+ color: white;
+}
+
+.swagger-ui .btn:hover {
+ background-color: var(--primary-dark);
+}
+
+.swagger-ui table thead tr th {
+ color: var(--text-primary);
+ border-color: var(--border-color);
+}
+
+.swagger-ui table tbody tr td {
+ color: var(--text-secondary);
+ border-color: var(--border-color);
+}
+
+.swagger-ui .response-col_status {
+ color: var(--success-color);
+}
+
+.swagger-ui .model-box {
+ background-color: var(--background-light);
+ border-color: var(--border-color);
+}
+
+.swagger-ui .model {
+ color: var(--text-primary);
+}
+
+.swagger-ui section.models {
+ border-color: var(--border-color);
+}
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/CompanyThemes/startup-purple.css b/samples/Sample.AspNetCore.SwaggerUI.NSwag/CompanyThemes/startup-purple.css
new file mode 100644
index 0000000..c6a3bdb
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/CompanyThemes/startup-purple.css
@@ -0,0 +1,140 @@
+/* Startup Purple Theme - Innovative Startup */
+
+:root {
+ --primary-color: #9c27b0;
+ --primary-dark: #6a1b9a;
+ --primary-light: #ce93d8;
+ --secondary-color: #7b1fa2;
+ --background-dark: #1a0a1f;
+ --background-light: #2d1b3d;
+ --text-primary: #f3e5f5;
+ --text-secondary: #ce93d8;
+ --border-color: #4a148c;
+ --success-color: #00e676;
+ --error-color: #ff1744;
+}
+
+.swagger-ui {
+ background-color: var(--background-dark);
+ color: var(--text-primary);
+}
+
+.swagger-ui .topbar {
+ background: linear-gradient(135deg, #6a1b9a 0%, #9c27b0 50%, #ce93d8 100%);
+ border-bottom: 3px solid var(--primary-light);
+}
+
+.swagger-ui .topbar .download-url-wrapper input[type=text] {
+ background-color: rgba(255, 255, 255, 0.1);
+ border: 2px solid var(--primary-light);
+ color: var(--text-primary);
+ border-radius: 20px;
+}
+
+.swagger-ui .info .title {
+ background: linear-gradient(135deg, var(--primary-color), var(--primary-light));
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+ font-weight: 800;
+}
+
+.swagger-ui .info .base-url {
+ color: var(--text-secondary);
+}
+
+.swagger-ui .scheme-container {
+ background-color: var(--background-light);
+ border: 2px solid var(--border-color);
+ border-radius: 8px;
+}
+
+.swagger-ui .opblock {
+ border-color: var(--border-color);
+ background-color: var(--background-light);
+ border-radius: 8px;
+ box-shadow: 0 4px 6px rgba(156, 39, 176, 0.2);
+}
+
+.swagger-ui .opblock.opblock-get {
+ background: linear-gradient(135deg, rgba(156, 39, 176, 0.1) 0%, rgba(206, 147, 216, 0.1) 100%);
+ border-left: 5px solid var(--primary-color);
+}
+
+.swagger-ui .opblock.opblock-post {
+ background: linear-gradient(135deg, rgba(0, 230, 118, 0.1) 0%, rgba(0, 230, 118, 0.05) 100%);
+ border-left: 5px solid var(--success-color);
+}
+
+.swagger-ui .opblock.opblock-put {
+ background: linear-gradient(135deg, rgba(255, 152, 0, 0.1) 0%, rgba(255, 152, 0, 0.05) 100%);
+ border-left: 5px solid #ff9800;
+}
+
+.swagger-ui .opblock.opblock-delete {
+ background: linear-gradient(135deg, rgba(255, 23, 68, 0.1) 0%, rgba(255, 23, 68, 0.05) 100%);
+ border-left: 5px solid var(--error-color);
+}
+
+.swagger-ui .opblock-summary-method {
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
+ color: white;
+ font-weight: 700;
+ text-transform: uppercase;
+ border-radius: 4px;
+}
+
+.swagger-ui .opblock-summary-path {
+ color: var(--text-primary);
+ font-weight: 600;
+}
+
+.swagger-ui .btn {
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
+ border: none;
+ color: white;
+ font-weight: 600;
+ border-radius: 20px;
+ padding: 8px 20px;
+ transition: all 0.3s ease;
+}
+
+.swagger-ui .btn:hover {
+ background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-color) 100%);
+ box-shadow: 0 6px 12px rgba(156, 39, 176, 0.4);
+ transform: translateY(-2px);
+}
+
+.swagger-ui table thead tr th {
+ color: var(--text-primary);
+ border-color: var(--border-color);
+ background: linear-gradient(135deg, rgba(156, 39, 176, 0.2) 0%, rgba(106, 27, 154, 0.2) 100%);
+}
+
+.swagger-ui table tbody tr td {
+ color: var(--text-secondary);
+ border-color: var(--border-color);
+}
+
+.swagger-ui .response-col_status {
+ color: var(--success-color);
+ font-weight: 700;
+}
+
+.swagger-ui .model-box {
+ background-color: var(--background-light);
+ border: 2px solid var(--border-color);
+ border-radius: 8px;
+}
+
+.swagger-ui .model {
+ color: var(--text-primary);
+}
+
+.swagger-ui section.models {
+ border-color: var(--border-color);
+}
+
+.swagger-ui .loading-container .loading:after {
+ border-color: var(--primary-color) transparent transparent;
+}
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/CompanyThemes/tech-green.css b/samples/Sample.AspNetCore.SwaggerUI.NSwag/CompanyThemes/tech-green.css
new file mode 100644
index 0000000..1d260b5
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/CompanyThemes/tech-green.css
@@ -0,0 +1,129 @@
+/* Tech Green Theme - Modern Technology */
+
+:root {
+ --primary-color: #00c853;
+ --primary-dark: #009624;
+ --primary-light: #5efc82;
+ --secondary-color: #607d8b;
+ --background-dark: #0d1b0e;
+ --background-light: #1a3a1f;
+ --text-primary: #e8f5e9;
+ --text-secondary: #a5d6a7;
+ --border-color: #2e7d32;
+ --success-color: #66bb6a;
+ --error-color: #ef5350;
+}
+
+.swagger-ui {
+ background-color: var(--background-dark);
+ color: var(--text-primary);
+}
+
+.swagger-ui .topbar {
+ background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-color) 100%);
+ border-bottom: 2px solid var(--primary-light);
+}
+
+.swagger-ui .topbar .download-url-wrapper input[type=text] {
+ background-color: rgba(255, 255, 255, 0.1);
+ border-color: var(--primary-light);
+ color: var(--text-primary);
+}
+
+.swagger-ui .info .title {
+ color: var(--primary-light);
+ text-shadow: 0 0 10px rgba(0, 200, 83, 0.5);
+}
+
+.swagger-ui .info .base-url {
+ color: var(--text-secondary);
+}
+
+.swagger-ui .scheme-container {
+ background-color: var(--background-light);
+ border-color: var(--border-color);
+}
+
+.swagger-ui .opblock {
+ border-color: var(--border-color);
+ background-color: var(--background-light);
+ box-shadow: 0 2px 4px rgba(0, 200, 83, 0.1);
+}
+
+.swagger-ui .opblock.opblock-get {
+ background-color: rgba(0, 200, 83, 0.1);
+ border-left: 4px solid var(--primary-color);
+}
+
+.swagger-ui .opblock.opblock-post {
+ background-color: rgba(102, 187, 106, 0.1);
+ border-left: 4px solid var(--success-color);
+}
+
+.swagger-ui .opblock.opblock-put {
+ background-color: rgba(255, 193, 7, 0.1);
+ border-left: 4px solid #ffc107;
+}
+
+.swagger-ui .opblock.opblock-delete {
+ background-color: rgba(239, 83, 80, 0.1);
+ border-left: 4px solid var(--error-color);
+}
+
+.swagger-ui .opblock-summary-method {
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
+ color: #000;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+.swagger-ui .opblock-summary-path {
+ color: var(--text-primary);
+ font-family: 'Courier New', monospace;
+}
+
+.swagger-ui .btn {
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
+ border: none;
+ color: #000;
+ font-weight: 600;
+}
+
+.swagger-ui .btn:hover {
+ background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-color) 100%);
+ box-shadow: 0 4px 8px rgba(0, 200, 83, 0.3);
+}
+
+.swagger-ui table thead tr th {
+ color: var(--text-primary);
+ border-color: var(--border-color);
+ background-color: rgba(0, 200, 83, 0.1);
+}
+
+.swagger-ui table tbody tr td {
+ color: var(--text-secondary);
+ border-color: var(--border-color);
+}
+
+.swagger-ui .response-col_status {
+ color: var(--primary-light);
+ font-weight: 600;
+}
+
+.swagger-ui .model-box {
+ background-color: var(--background-light);
+ border: 1px solid var(--border-color);
+ border-radius: 4px;
+}
+
+.swagger-ui .model {
+ color: var(--text-primary);
+}
+
+.swagger-ui section.models {
+ border-color: var(--border-color);
+}
+
+.swagger-ui .loading-container .loading:after {
+ border-color: var(--primary-color) transparent transparent;
+}
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs
index ab0e6aa..33623ab 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs
+++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs
@@ -1,9 +1,10 @@
+using AspNetCore.Swagger.Themes;
using Sample.AspNetCore.SwaggerUI.NSwag;
-using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
+builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(OpenApiDocGenConfigurer.Configure);
var app = builder.Build();
@@ -12,21 +13,182 @@
{
app.UseOpenApi();
- //string inlineStyle = "body { background-color: #000; }";
- //app.UseSwaggerUi(inlineStyle, c => c.DocumentTitle = "Sample Title");
+ // ========================================
+ // 🎨 BASIC USAGE EXAMPLES
+ // ========================================
- //app.UseSwaggerUi(CustomMinifiedStyle.CustomMin, c =>
- //app.UseSwaggerUi(CustomTheme.Custom, c =>
+ // 1. Simple predefined theme (no theme switcher)
+ //app.UseSwaggerUi(Theme.Dark, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Dark Theme";
+ //});
+
+ // 2. Inline CSS theme
+ //app.UseSwaggerUi("body { background-color: #1a1a2e; color: #eee; }", c =>
+ //{
+ // c.DocumentTitle = "Sample API - Inline Custom Style";
+ //});
+
+ // 3. Custom theme from embedded resource
//app.UseSwaggerUi(Assembly.GetExecutingAssembly(), "custom.css", c =>
- app.UseSwaggerUi(Assembly.GetExecutingAssembly(), "standalone.custom.css", c => // Fully independent - no common.css or ui.js
+ //{
+ // c.DocumentTitle = "Sample API - Custom Theme from Assembly";
+ // c.EnableAllAdvancedOptions(); // Enables theme switcher + all UI features
+ //});
+
+ // 4. Standalone theme (no dependencies on common.css or ui.js)
+ //app.UseSwaggerUi(Assembly.GetExecutingAssembly(), "standalone.custom.css", c =>
+ //{
+ // c.DocumentTitle = "Sample API - Standalone Theme";
+ // // Note: Standalone themes don't support advanced options
+ //});
+
+ // ========================================
+ // 🔀 THEME SWITCHER EXAMPLES
+ // ========================================
+
+ // 5. All themes (predefined + custom auto-discovery) - DEFAULT
//app.UseSwaggerUi(Theme.Dark, c =>
+ //{
+ // c.DocumentTitle = "Sample API - All Themes";
+ // c.EnableThemeSwitcher(); // Shows all 6 predefined + discovered custom themes
+ //});
+
+ // 6. Predefined themes only
+ //app.UseSwaggerUi(Theme.Forest, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Predefined Only";
+ // c.EnableThemeSwitcher(ThemeSwitcherOptions.PredefinedOnly());
+ //});
+
+ // 7. Custom themes only (auto-discovery)
+ //app.UseSwaggerUi(CustomTheme.Custom, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Custom Themes Only";
+ // c.EnableThemeSwitcher(ThemeSwitcherOptions.CustomOnly());
+ //});
+
+ // 8. Specific themes selection (mixed predefined + custom)
+ //app.UseSwaggerUi(CompanyThemes.CorporateBlue, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Business Themes";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithThemes(
+ // Theme.Dark,
+ // Theme.Light,
+ // CompanyThemes.CorporateBlue,
+ // CompanyThemes.TechGreen,
+ // CompanyThemes.StartupPurple
+ // ));
+ //});
+
+ // 9. Exclude specific themes
+ //app.UseSwaggerUi(Theme.DeepSea, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Excluding Themes";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .ExcludeThemes(Theme.Futuristic, Theme.Desert));
+ //});
+
+ // 10. Custom display format
+ //app.UseSwaggerUi(Theme.Desert, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Custom Display Format";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithDisplayFormat("🎨 {name}"));
+ //});
+
+ // 11. Seasonal/Holiday themes
+ //app.UseSwaggerUi(SeasonalThemes.HolidayRed, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Holiday Special 🎄";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithThemes(
+ // SeasonalThemes.HolidayRed,
+ // SeasonalThemes.OceanBlue,
+ // Theme.Dark,
+ // Theme.Light
+ // ));
+ //});
+
+ // 12. Professional company branding
+ //app.UseSwaggerUi(CompanyThemes.TechGreen, c =>
+ //{
+ // c.DocumentTitle = "Company API Documentation";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithThemes(
+ // CompanyThemes.CorporateBlue,
+ // CompanyThemes.TechGreen,
+ // CompanyThemes.StartupPurple
+ // )
+ // .WithDisplayFormat("Company Theme: {name}"));
+ //});
+
+ // ========================================
+ // ⚙️ ADVANCED UI FEATURES (without theme switcher)
+ // ========================================
+
+ // 13. Enable all advanced UI features
+ //app.UseSwaggerUi(Theme.Dark, c =>
+ //{
+ // c.DocumentTitle = "Sample API - All Features";
+ // c.EnableAllAdvancedOptions(); // Pinnable topbar, back-to-top, sticky ops, expand/collapse, theme switcher
+ //});
+
+ // 14. Individual advanced features
+ //app.UseSwaggerUi(Theme.Light, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Selected Features";
+ // c.EnablePinnableTopbar();
+ // c.ShowBackToTopButton();
+ // c.EnableStickyOperations();
+ // c.EnableExpandOrCollapseAllOperations();
+ // // Note: Not enabling theme switcher here
+ //});
+
+ // 15. Advanced features with custom theme
+ //app.UseSwaggerUi(CustomTheme.Custom, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Custom with Features";
+ // c.EnablePinnableTopbar();
+ // c.ShowBackToTopButton();
+ // c.EnableThemeSwitcher();
+ //});
+
+ // ========================================
+ // 🎯 ADVANCED THEME SWITCHER OPTIONS
+ // ========================================
+
+ // 16. ExplicitOnly mode (no auto-discovery)
+ //app.UseSwaggerUi(CompanyThemes.CorporateBlue, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Explicit Themes";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithThemes(
+ // Theme.Dark,
+ // Theme.Light,
+ // CompanyThemes.CorporateBlue
+ // )
+ // .WithCustomThemes(CustomThemeMode.ExplicitOnly));
+ //});
+
+ // 17. Complex filtering
+ //app.UseSwaggerUi(Theme.Forest, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Complex Filtering";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithAllPredefinedThemes(true) // Include all 6 predefined
+ // .ExcludeThemes(Theme.Futuristic) // But exclude Futuristic
+ // .WithCustomThemes(CustomThemeMode.AutoDiscover)); // Auto-discover custom
+ //});
+
+ // 18. Minimal configuration (just 2 themes)
+ app.UseSwaggerUi(Theme.Dark, c =>
{
- c.DocumentTitle = "Sample Title";
- c.EnableAllAdvancedOptions();
- //c.EnablePinnableTopbar();
- //c.ShowBackToTopButton();
- //c.EnableStickyOperations();
- //c.EnableExpandOrCollapseAllOperations();
+ c.DocumentTitle = "Sample API - Minimal";
+ c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ .WithThemes(Theme.Dark, Theme.Light)
+ .WithCustomThemes(CustomThemeMode.None));
});
}
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Sample.AspNetCore.SwaggerUI.NSwag.csproj b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Sample.AspNetCore.SwaggerUI.NSwag.csproj
index 50d5c02..035ed8f 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Sample.AspNetCore.SwaggerUI.NSwag.csproj
+++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Sample.AspNetCore.SwaggerUI.NSwag.csproj
@@ -21,9 +21,14 @@
-
-
+
+
+
+
+
+
+
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/CustomTheme.cs b/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/CustomMinifiedStyle.cs
similarity index 58%
rename from samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/CustomTheme.cs
rename to samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/CustomMinifiedStyle.cs
index 8c992ab..c8cd835 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/CustomTheme.cs
+++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/CustomMinifiedStyle.cs
@@ -1,15 +1,6 @@
using AspNetCore.Swagger.Themes;
-namespace SwaggerThemes;
-
-public class CustomTheme : Theme
-{
- protected CustomTheme(string fileName) : base(fileName)
- {
- }
-
- public static CustomTheme Custom => new("custom.css");
-}
+namespace SwaggerThemes.Custom;
public class CustomMinifiedStyle : Theme
{
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/CustomTheme.cs b/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/CustomTheme.cs
new file mode 100644
index 0000000..9d89d77
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/CustomTheme.cs
@@ -0,0 +1,12 @@
+using AspNetCore.Swagger.Themes;
+
+namespace SwaggerThemes.Custom;
+
+public class CustomTheme : Theme
+{
+ protected CustomTheme(string fileName) : base(fileName)
+ {
+ }
+
+ public static CustomTheme Custom => new("custom.css");
+}
\ No newline at end of file
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/custom.css b/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/custom.css
similarity index 100%
rename from samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/custom.css
rename to samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/custom.css
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/minifiedCustom.min.css b/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/minifiedCustom.min.css
similarity index 100%
rename from samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/minifiedCustom.min.css
rename to samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/minifiedCustom.min.css
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/SeasonalThemes.cs b/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/SeasonalThemes.cs
new file mode 100644
index 0000000..94b75ff
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/SeasonalThemes.cs
@@ -0,0 +1,23 @@
+using AspNetCore.Swagger.Themes;
+
+namespace SwaggerThemes;
+
+///
+/// Seasonal themes for special occasions.
+///
+public class SeasonalThemes : Theme
+{
+ protected SeasonalThemes(string fileName) : base(fileName)
+ {
+ }
+
+ ///
+ /// Festive theme for holidays.
+ ///
+ public static SeasonalThemes HolidayRed => new("holiday-red.css");
+
+ ///
+ /// Summer vacation theme.
+ ///
+ public static SeasonalThemes OceanBlue => new("ocean-blue.css");
+}
\ No newline at end of file
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/holiday-red.css b/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/holiday-red.css
new file mode 100644
index 0000000..32837dc
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/holiday-red.css
@@ -0,0 +1,51 @@
+/* Holiday Red Theme - Festive */
+
+:root {
+ --primary-color: #d32f2f;
+ --primary-dark: #b71c1c;
+ --primary-light: #ff6659;
+ --secondary-color: #c62828;
+ --background-dark: #1a0505;
+ --background-light: #3d1010;
+ --text-primary: #ffebee;
+ --text-secondary: #ffcdd2;
+ --border-color: #8b0000;
+ --success-color: #4caf50;
+ --accent-gold: #ffd700;
+}
+
+.swagger-ui {
+ background-color: var(--background-dark);
+ color: var(--text-primary);
+}
+
+.swagger-ui .topbar {
+ background: linear-gradient(135deg, #b71c1c 0%, #d32f2f 50%, #ff6659 100%);
+ border-bottom: 3px solid var(--accent-gold);
+}
+
+.swagger-ui .info .title {
+ color: var(--accent-gold);
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
+}
+
+.swagger-ui .opblock.opblock-get {
+ background-color: rgba(211, 47, 47, 0.1);
+ border-color: var(--primary-color);
+}
+
+.swagger-ui .opblock-summary-method {
+ background-color: var(--primary-color);
+ color: white;
+}
+
+.swagger-ui .btn {
+ background-color: var(--primary-color);
+ border-color: var(--primary-dark);
+ color: white;
+}
+
+.swagger-ui .btn:hover {
+ background-color: var(--primary-dark);
+ box-shadow: 0 0 10px var(--accent-gold);
+}
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/ocean-blue.css b/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/ocean-blue.css
new file mode 100644
index 0000000..e072b52
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/ocean-blue.css
@@ -0,0 +1,45 @@
+/* Ocean Blue Theme - Summer Vacation */
+
+:root {
+ --primary-color: #0277bd;
+ --primary-dark: #01579b;
+ --primary-light: #58a5f0;
+ --secondary-color: #00acc1;
+ --background-dark: #051923;
+ --background-light: #003459;
+ --text-primary: #e1f5fe;
+ --text-secondary: #81d4fa;
+ --border-color: #006064;
+ --success-color: #00bfa5;
+ --wave-color: #4fc3f7;
+}
+
+.swagger-ui {
+ background: linear-gradient(180deg, var(--background-dark) 0%, var(--background-light) 100%);
+ color: var(--text-primary);
+}
+
+.swagger-ui .topbar {
+ background: linear-gradient(135deg, #01579b 0%, #0277bd 50%, #4fc3f7 100%);
+ border-bottom: 2px solid var(--wave-color);
+}
+
+.swagger-ui .info .title {
+ color: var(--wave-color);
+}
+
+.swagger-ui .opblock.opblock-get {
+ background-color: rgba(2, 119, 189, 0.1);
+ border-color: var(--primary-color);
+}
+
+.swagger-ui .opblock-summary-method {
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
+ color: white;
+}
+
+.swagger-ui .btn {
+ background-color: var(--secondary-color);
+ border-color: var(--primary-color);
+ color: white;
+}
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/CompanyThemes/CompanyThemes.cs b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/CompanyThemes/CompanyThemes.cs
new file mode 100644
index 0000000..2f894d2
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/CompanyThemes/CompanyThemes.cs
@@ -0,0 +1,19 @@
+using AspNetCore.Swagger.Themes;
+
+namespace CompanyThemes;
+
+///
+/// Company brand themes for Swagger UI.
+///
+public class CompanyThemes : Theme
+{
+ protected CompanyThemes(string fileName) : base(fileName)
+ {
+ }
+
+ public static CompanyThemes CorporateBlue => new("corporate-blue.css");
+
+ public static CompanyThemes TechGreen => new("tech-green.css");
+
+ public static CompanyThemes StartupPurple => new("startup-purple.css");
+}
\ No newline at end of file
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/CompanyThemes/corporate-blue.css b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/CompanyThemes/corporate-blue.css
new file mode 100644
index 0000000..175845c
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/CompanyThemes/corporate-blue.css
@@ -0,0 +1,124 @@
+/* Corporate Blue Theme - Professional Business */
+
+:root {
+ --primary-color: #0066cc;
+ --primary-dark: #004999;
+ --primary-light: #3385d6;
+ --secondary-color: #5c7080;
+ --background-dark: #1a2332;
+ --background-light: #2c3e50;
+ --text-primary: #ecf0f1;
+ --text-secondary: #bdc3c7;
+ --border-color: #34495e;
+ --success-color: #27ae60;
+ --error-color: #e74c3c;
+}
+
+.swagger-ui {
+ background-color: var(--background-dark);
+ color: var(--text-primary);
+}
+
+.swagger-ui .topbar {
+ background-color: var(--primary-color);
+ border-bottom: 3px solid var(--primary-dark);
+}
+
+.swagger-ui .topbar .download-url-wrapper input[type=text] {
+ background-color: var(--background-light);
+ border-color: var(--border-color);
+ color: var(--text-primary);
+}
+
+.swagger-ui .info .title {
+ color: var(--primary-light);
+}
+
+.swagger-ui .info .base-url {
+ color: var(--text-secondary);
+}
+
+.swagger-ui .scheme-container {
+ background-color: var(--background-light);
+ border-color: var(--border-color);
+}
+
+.swagger-ui .opblock {
+ border-color: var(--border-color);
+ background-color: var(--background-light);
+}
+
+.swagger-ui .opblock.opblock-get {
+ background-color: rgba(0, 102, 204, 0.1);
+ border-color: var(--primary-color);
+}
+
+.swagger-ui .opblock.opblock-get .opblock-summary {
+ border-color: var(--primary-color);
+}
+
+.swagger-ui .opblock.opblock-post {
+ background-color: rgba(39, 174, 96, 0.1);
+ border-color: var(--success-color);
+}
+
+.swagger-ui .opblock.opblock-post .opblock-summary {
+ border-color: var(--success-color);
+}
+
+.swagger-ui .opblock.opblock-put {
+ background-color: rgba(243, 156, 18, 0.1);
+ border-color: #f39c12;
+}
+
+.swagger-ui .opblock.opblock-delete {
+ background-color: rgba(231, 76, 60, 0.1);
+ border-color: var(--error-color);
+}
+
+.swagger-ui .opblock-summary-method {
+ background-color: var(--primary-color);
+ color: white;
+ font-weight: 600;
+}
+
+.swagger-ui .opblock-summary-path {
+ color: var(--text-primary);
+}
+
+.swagger-ui .btn {
+ background-color: var(--primary-color);
+ border-color: var(--primary-dark);
+ color: white;
+}
+
+.swagger-ui .btn:hover {
+ background-color: var(--primary-dark);
+}
+
+.swagger-ui table thead tr th {
+ color: var(--text-primary);
+ border-color: var(--border-color);
+}
+
+.swagger-ui table tbody tr td {
+ color: var(--text-secondary);
+ border-color: var(--border-color);
+}
+
+.swagger-ui .response-col_status {
+ color: var(--success-color);
+}
+
+.swagger-ui .model-box {
+ background-color: var(--background-light);
+ border-color: var(--border-color);
+}
+
+.swagger-ui .model {
+ color: var(--text-primary);
+}
+
+.swagger-ui section.models {
+ border-color: var(--border-color);
+}
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/CompanyThemes/startup-purple.css b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/CompanyThemes/startup-purple.css
new file mode 100644
index 0000000..c6a3bdb
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/CompanyThemes/startup-purple.css
@@ -0,0 +1,140 @@
+/* Startup Purple Theme - Innovative Startup */
+
+:root {
+ --primary-color: #9c27b0;
+ --primary-dark: #6a1b9a;
+ --primary-light: #ce93d8;
+ --secondary-color: #7b1fa2;
+ --background-dark: #1a0a1f;
+ --background-light: #2d1b3d;
+ --text-primary: #f3e5f5;
+ --text-secondary: #ce93d8;
+ --border-color: #4a148c;
+ --success-color: #00e676;
+ --error-color: #ff1744;
+}
+
+.swagger-ui {
+ background-color: var(--background-dark);
+ color: var(--text-primary);
+}
+
+.swagger-ui .topbar {
+ background: linear-gradient(135deg, #6a1b9a 0%, #9c27b0 50%, #ce93d8 100%);
+ border-bottom: 3px solid var(--primary-light);
+}
+
+.swagger-ui .topbar .download-url-wrapper input[type=text] {
+ background-color: rgba(255, 255, 255, 0.1);
+ border: 2px solid var(--primary-light);
+ color: var(--text-primary);
+ border-radius: 20px;
+}
+
+.swagger-ui .info .title {
+ background: linear-gradient(135deg, var(--primary-color), var(--primary-light));
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+ font-weight: 800;
+}
+
+.swagger-ui .info .base-url {
+ color: var(--text-secondary);
+}
+
+.swagger-ui .scheme-container {
+ background-color: var(--background-light);
+ border: 2px solid var(--border-color);
+ border-radius: 8px;
+}
+
+.swagger-ui .opblock {
+ border-color: var(--border-color);
+ background-color: var(--background-light);
+ border-radius: 8px;
+ box-shadow: 0 4px 6px rgba(156, 39, 176, 0.2);
+}
+
+.swagger-ui .opblock.opblock-get {
+ background: linear-gradient(135deg, rgba(156, 39, 176, 0.1) 0%, rgba(206, 147, 216, 0.1) 100%);
+ border-left: 5px solid var(--primary-color);
+}
+
+.swagger-ui .opblock.opblock-post {
+ background: linear-gradient(135deg, rgba(0, 230, 118, 0.1) 0%, rgba(0, 230, 118, 0.05) 100%);
+ border-left: 5px solid var(--success-color);
+}
+
+.swagger-ui .opblock.opblock-put {
+ background: linear-gradient(135deg, rgba(255, 152, 0, 0.1) 0%, rgba(255, 152, 0, 0.05) 100%);
+ border-left: 5px solid #ff9800;
+}
+
+.swagger-ui .opblock.opblock-delete {
+ background: linear-gradient(135deg, rgba(255, 23, 68, 0.1) 0%, rgba(255, 23, 68, 0.05) 100%);
+ border-left: 5px solid var(--error-color);
+}
+
+.swagger-ui .opblock-summary-method {
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
+ color: white;
+ font-weight: 700;
+ text-transform: uppercase;
+ border-radius: 4px;
+}
+
+.swagger-ui .opblock-summary-path {
+ color: var(--text-primary);
+ font-weight: 600;
+}
+
+.swagger-ui .btn {
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
+ border: none;
+ color: white;
+ font-weight: 600;
+ border-radius: 20px;
+ padding: 8px 20px;
+ transition: all 0.3s ease;
+}
+
+.swagger-ui .btn:hover {
+ background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-color) 100%);
+ box-shadow: 0 6px 12px rgba(156, 39, 176, 0.4);
+ transform: translateY(-2px);
+}
+
+.swagger-ui table thead tr th {
+ color: var(--text-primary);
+ border-color: var(--border-color);
+ background: linear-gradient(135deg, rgba(156, 39, 176, 0.2) 0%, rgba(106, 27, 154, 0.2) 100%);
+}
+
+.swagger-ui table tbody tr td {
+ color: var(--text-secondary);
+ border-color: var(--border-color);
+}
+
+.swagger-ui .response-col_status {
+ color: var(--success-color);
+ font-weight: 700;
+}
+
+.swagger-ui .model-box {
+ background-color: var(--background-light);
+ border: 2px solid var(--border-color);
+ border-radius: 8px;
+}
+
+.swagger-ui .model {
+ color: var(--text-primary);
+}
+
+.swagger-ui section.models {
+ border-color: var(--border-color);
+}
+
+.swagger-ui .loading-container .loading:after {
+ border-color: var(--primary-color) transparent transparent;
+}
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/CompanyThemes/tech-green.css b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/CompanyThemes/tech-green.css
new file mode 100644
index 0000000..1d260b5
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/CompanyThemes/tech-green.css
@@ -0,0 +1,129 @@
+/* Tech Green Theme - Modern Technology */
+
+:root {
+ --primary-color: #00c853;
+ --primary-dark: #009624;
+ --primary-light: #5efc82;
+ --secondary-color: #607d8b;
+ --background-dark: #0d1b0e;
+ --background-light: #1a3a1f;
+ --text-primary: #e8f5e9;
+ --text-secondary: #a5d6a7;
+ --border-color: #2e7d32;
+ --success-color: #66bb6a;
+ --error-color: #ef5350;
+}
+
+.swagger-ui {
+ background-color: var(--background-dark);
+ color: var(--text-primary);
+}
+
+.swagger-ui .topbar {
+ background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-color) 100%);
+ border-bottom: 2px solid var(--primary-light);
+}
+
+.swagger-ui .topbar .download-url-wrapper input[type=text] {
+ background-color: rgba(255, 255, 255, 0.1);
+ border-color: var(--primary-light);
+ color: var(--text-primary);
+}
+
+.swagger-ui .info .title {
+ color: var(--primary-light);
+ text-shadow: 0 0 10px rgba(0, 200, 83, 0.5);
+}
+
+.swagger-ui .info .base-url {
+ color: var(--text-secondary);
+}
+
+.swagger-ui .scheme-container {
+ background-color: var(--background-light);
+ border-color: var(--border-color);
+}
+
+.swagger-ui .opblock {
+ border-color: var(--border-color);
+ background-color: var(--background-light);
+ box-shadow: 0 2px 4px rgba(0, 200, 83, 0.1);
+}
+
+.swagger-ui .opblock.opblock-get {
+ background-color: rgba(0, 200, 83, 0.1);
+ border-left: 4px solid var(--primary-color);
+}
+
+.swagger-ui .opblock.opblock-post {
+ background-color: rgba(102, 187, 106, 0.1);
+ border-left: 4px solid var(--success-color);
+}
+
+.swagger-ui .opblock.opblock-put {
+ background-color: rgba(255, 193, 7, 0.1);
+ border-left: 4px solid #ffc107;
+}
+
+.swagger-ui .opblock.opblock-delete {
+ background-color: rgba(239, 83, 80, 0.1);
+ border-left: 4px solid var(--error-color);
+}
+
+.swagger-ui .opblock-summary-method {
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
+ color: #000;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+.swagger-ui .opblock-summary-path {
+ color: var(--text-primary);
+ font-family: 'Courier New', monospace;
+}
+
+.swagger-ui .btn {
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
+ border: none;
+ color: #000;
+ font-weight: 600;
+}
+
+.swagger-ui .btn:hover {
+ background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-color) 100%);
+ box-shadow: 0 4px 8px rgba(0, 200, 83, 0.3);
+}
+
+.swagger-ui table thead tr th {
+ color: var(--text-primary);
+ border-color: var(--border-color);
+ background-color: rgba(0, 200, 83, 0.1);
+}
+
+.swagger-ui table tbody tr td {
+ color: var(--text-secondary);
+ border-color: var(--border-color);
+}
+
+.swagger-ui .response-col_status {
+ color: var(--primary-light);
+ font-weight: 600;
+}
+
+.swagger-ui .model-box {
+ background-color: var(--background-light);
+ border: 1px solid var(--border-color);
+ border-radius: 4px;
+}
+
+.swagger-ui .model {
+ color: var(--text-primary);
+}
+
+.swagger-ui section.models {
+ border-color: var(--border-color);
+}
+
+.swagger-ui .loading-container .loading:after {
+ border-color: var(--primary-color) transparent transparent;
+}
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs
index 2370653..9010f7e 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs
@@ -1,5 +1,5 @@
+using AspNetCore.Swagger.Themes;
using Sample.AspNetCore.SwaggerUI.Swashbuckle;
-using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
@@ -13,22 +13,184 @@
{
app.UseSwagger();
- //string inlineStyle = "body { background-color: #000; }";
- //app.UseSwaggerUI(inlineStyle, c => c.DocumentTitle = "Sample Title");
+ // ========================================
+ // 🎨 BASIC USAGE EXAMPLES
+ // ========================================
- //app.UseSwaggerUI(CustomMinifiedStyle.CustomMin, c =>
- //app.UseSwaggerUI(CustomTheme.Custom, c =>
+ // 1. Simple predefined theme (no theme switcher)
+ //app.UseSwaggerUI(Theme.Dark, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Dark Theme";
+ //});
+
+ // 2. Inline CSS theme
+ //app.UseSwaggerUI("body { background-color: #1a1a2e; color: #eee; }", c =>
+ //{
+ // c.DocumentTitle = "Sample API - Inline Custom Style";
+ //});
+
+ // 3. Custom theme from embedded resource
//app.UseSwaggerUI(Assembly.GetExecutingAssembly(), "custom.css", c =>
- app.UseSwaggerUI(Assembly.GetExecutingAssembly(), "standalone.custom.css", c => // Fully independent - no common.css or ui.js
+ //{
+ // c.DocumentTitle = "Sample API - Custom Theme from Assembly";
+ // c.EnableAllAdvancedOptions(); // Enables theme switcher + all UI features
+ //});
+
+ // 4. Standalone theme (no dependencies on common.css or ui.js)
+ //app.UseSwaggerUI(Assembly.GetExecutingAssembly(), "standalone.custom.css", c =>
+ //{
+ // c.DocumentTitle = "Sample API - Standalone Theme";
+ // // Note: Standalone themes don't support advanced options
+ //});
+
+ // ========================================
+ // 🔀 THEME SWITCHER EXAMPLES
+ // ========================================
+
+ // 5. All themes (predefined + custom auto-discovery) - DEFAULT
+ //app.UseSwaggerUI(Theme.Dark, c =>
+ //{
+ // c.DocumentTitle = "Sample API - All Themes";
+ // c.EnableThemeSwitcher(); // Shows all 6 predefined + discovered custom themes
+ //});
+
+ // 6. Predefined themes only
+ //app.UseSwaggerUI(Theme.Forest, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Predefined Only";
+ // c.EnableThemeSwitcher(ThemeSwitcherOptions.PredefinedOnly());
+ //});
+
+ // 7. Custom themes only (auto-discovery)
+ //app.UseSwaggerUI(CustomTheme.Custom, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Custom Themes Only";
+ // c.EnableThemeSwitcher(ThemeSwitcherOptions.CustomOnly());
+ //});
+
+ // 8. Specific themes selection (mixed predefined + custom)
+ //app.UseSwaggerUI(CompanyThemes.CorporateBlue, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Business Themes";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithThemes(
+ // Theme.Dark,
+ // Theme.Light,
+ // CompanyThemes.CorporateBlue,
+ // CompanyThemes.TechGreen,
+ // CompanyThemes.StartupPurple
+ // ));
+ //});
+
+ // 9. Exclude specific themes
+ //app.UseSwaggerUI(Theme.DeepSea, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Excluding Themes";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .ExcludeThemes(Theme.Futuristic, Theme.Desert));
+ //});
+
+ // 10. Custom display format
+ //app.UseSwaggerUI(Theme.Desert, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Custom Display Format";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithDisplayFormat("🎨 {name}"));
+ //});
+
+ // 11. Seasonal/Holiday themes
+ //app.UseSwaggerUI(SeasonalThemes.HolidayRed, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Holiday Special 🎄";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithThemes(
+ // SeasonalThemes.HolidayRed,
+ // SeasonalThemes.OceanBlue,
+ // Theme.Dark,
+ // Theme.Light
+ // ));
+ //});
+
+ // 12. Professional company branding
+ //app.UseSwaggerUI(CompanyThemes.TechGreen, c =>
+ //{
+ // c.DocumentTitle = "Company API Documentation";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithThemes(
+ // CompanyThemes.CorporateBlue,
+ // CompanyThemes.TechGreen,
+ // CompanyThemes.StartupPurple
+ // )
+ // .WithDisplayFormat("Company Theme: {name}"));
+ //});
+
+ // ========================================
+ // ⚙️ ADVANCED UI FEATURES (without theme switcher)
+ // ========================================
+
+ // 13. Enable all advanced UI features
//app.UseSwaggerUI(Theme.Dark, c =>
+ //{
+ // c.DocumentTitle = "Sample API - All Features";
+ // c.EnableAllAdvancedOptions(); // Pinnable topbar, back-to-top, sticky ops, expand/collapse, theme switcher
+ //});
+
+ // 14. Individual advanced features
+ //app.UseSwaggerUI(Theme.Light, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Selected Features";
+ // c.EnablePinnableTopbar();
+ // c.ShowBackToTopButton();
+ // c.EnableStickyOperations();
+ // c.EnableExpandOrCollapseAllOperations();
+ // // Note: Not enabling theme switcher here
+ //});
+
+ // 15. Advanced features with custom theme
+ //app.UseSwaggerUI(CustomTheme.Custom, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Custom with Features";
+ // c.EnablePinnableTopbar();
+ // c.ShowBackToTopButton();
+ // c.EnableThemeSwitcher();
+ //});
+
+ // ========================================
+ // 🎯 ADVANCED THEME SWITCHER OPTIONS
+ // ========================================
+
+ // 16. ExplicitOnly mode (no auto-discovery)
+ //app.UseSwaggerUI(CompanyThemes.CorporateBlue, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Explicit Themes";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithThemes(
+ // Theme.Dark,
+ // Theme.Light,
+ // CompanyThemes.CorporateBlue
+ // )
+ // .WithCustomThemes(CustomThemeMode.ExplicitOnly));
+ //});
+
+ // 17. Complex filtering
+ app.UseSwaggerUI(Theme.Forest, c =>
{
- c.DocumentTitle = "Sample Title";
+ c.DocumentTitle = "Sample API - Complex Filtering";
c.EnableAllAdvancedOptions();
- //c.EnablePinnableTopbar();
- //c.ShowBackToTopButton();
- //c.EnableStickyOperations();
- //c.EnableExpandOrCollapseAllOperations();
+ c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ .WithAllPredefinedThemes(true) // Include all 6 predefined
+ .ExcludeThemes(Theme.Futuristic) // But exclude Futuristic
+ .WithCustomThemes(CustomThemeMode.AutoDiscover)); // Auto-discover custom
});
+
+ // 18. Minimal configuration (just 2 themes)
+ //app.UseSwaggerUI(Theme.Dark, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Minimal";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithThemes(Theme.Dark, Theme.Light)
+ // .WithCustomThemes(CustomThemeMode.None));
+ //});
}
app.UseHttpsRedirection();
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj
index c690065..b051984 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj
@@ -21,9 +21,14 @@
-
-
+
+
+
+
+
+
+
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/CustomTheme.cs b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/CustomMinifiedStyle.cs
similarity index 58%
rename from samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/CustomTheme.cs
rename to samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/CustomMinifiedStyle.cs
index 8c992ab..c8cd835 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/CustomTheme.cs
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/CustomMinifiedStyle.cs
@@ -1,15 +1,6 @@
using AspNetCore.Swagger.Themes;
-namespace SwaggerThemes;
-
-public class CustomTheme : Theme
-{
- protected CustomTheme(string fileName) : base(fileName)
- {
- }
-
- public static CustomTheme Custom => new("custom.css");
-}
+namespace SwaggerThemes.Custom;
public class CustomMinifiedStyle : Theme
{
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/CustomTheme.cs b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/CustomTheme.cs
new file mode 100644
index 0000000..9d89d77
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/CustomTheme.cs
@@ -0,0 +1,12 @@
+using AspNetCore.Swagger.Themes;
+
+namespace SwaggerThemes.Custom;
+
+public class CustomTheme : Theme
+{
+ protected CustomTheme(string fileName) : base(fileName)
+ {
+ }
+
+ public static CustomTheme Custom => new("custom.css");
+}
\ No newline at end of file
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/custom.css b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/custom.css
similarity index 86%
rename from samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/custom.css
rename to samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/custom.css
index 64bbe07..04fd75a 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/custom.css
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/custom.css
@@ -5,4 +5,5 @@
*/
body {
background-color: #4cff00;
+ color: brown;
}
\ No newline at end of file
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/minifiedCustom.min.css b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/minifiedCustom.min.css
similarity index 100%
rename from samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/minifiedCustom.min.css
rename to samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/minifiedCustom.min.css
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/SeasonalThemes.cs b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/SeasonalThemes.cs
new file mode 100644
index 0000000..94b75ff
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/SeasonalThemes.cs
@@ -0,0 +1,23 @@
+using AspNetCore.Swagger.Themes;
+
+namespace SwaggerThemes;
+
+///
+/// Seasonal themes for special occasions.
+///
+public class SeasonalThemes : Theme
+{
+ protected SeasonalThemes(string fileName) : base(fileName)
+ {
+ }
+
+ ///
+ /// Festive theme for holidays.
+ ///
+ public static SeasonalThemes HolidayRed => new("holiday-red.css");
+
+ ///
+ /// Summer vacation theme.
+ ///
+ public static SeasonalThemes OceanBlue => new("ocean-blue.css");
+}
\ No newline at end of file
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/holiday-red.css b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/holiday-red.css
new file mode 100644
index 0000000..32837dc
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/holiday-red.css
@@ -0,0 +1,51 @@
+/* Holiday Red Theme - Festive */
+
+:root {
+ --primary-color: #d32f2f;
+ --primary-dark: #b71c1c;
+ --primary-light: #ff6659;
+ --secondary-color: #c62828;
+ --background-dark: #1a0505;
+ --background-light: #3d1010;
+ --text-primary: #ffebee;
+ --text-secondary: #ffcdd2;
+ --border-color: #8b0000;
+ --success-color: #4caf50;
+ --accent-gold: #ffd700;
+}
+
+.swagger-ui {
+ background-color: var(--background-dark);
+ color: var(--text-primary);
+}
+
+.swagger-ui .topbar {
+ background: linear-gradient(135deg, #b71c1c 0%, #d32f2f 50%, #ff6659 100%);
+ border-bottom: 3px solid var(--accent-gold);
+}
+
+.swagger-ui .info .title {
+ color: var(--accent-gold);
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
+}
+
+.swagger-ui .opblock.opblock-get {
+ background-color: rgba(211, 47, 47, 0.1);
+ border-color: var(--primary-color);
+}
+
+.swagger-ui .opblock-summary-method {
+ background-color: var(--primary-color);
+ color: white;
+}
+
+.swagger-ui .btn {
+ background-color: var(--primary-color);
+ border-color: var(--primary-dark);
+ color: white;
+}
+
+.swagger-ui .btn:hover {
+ background-color: var(--primary-dark);
+ box-shadow: 0 0 10px var(--accent-gold);
+}
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/ocean-blue.css b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/ocean-blue.css
new file mode 100644
index 0000000..e072b52
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/ocean-blue.css
@@ -0,0 +1,45 @@
+/* Ocean Blue Theme - Summer Vacation */
+
+:root {
+ --primary-color: #0277bd;
+ --primary-dark: #01579b;
+ --primary-light: #58a5f0;
+ --secondary-color: #00acc1;
+ --background-dark: #051923;
+ --background-light: #003459;
+ --text-primary: #e1f5fe;
+ --text-secondary: #81d4fa;
+ --border-color: #006064;
+ --success-color: #00bfa5;
+ --wave-color: #4fc3f7;
+}
+
+.swagger-ui {
+ background: linear-gradient(180deg, var(--background-dark) 0%, var(--background-light) 100%);
+ color: var(--text-primary);
+}
+
+.swagger-ui .topbar {
+ background: linear-gradient(135deg, #01579b 0%, #0277bd 50%, #4fc3f7 100%);
+ border-bottom: 2px solid var(--wave-color);
+}
+
+.swagger-ui .info .title {
+ color: var(--wave-color);
+}
+
+.swagger-ui .opblock.opblock-get {
+ background-color: rgba(2, 119, 189, 0.1);
+ border-color: var(--primary-color);
+}
+
+.swagger-ui .opblock-summary-method {
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
+ color: white;
+}
+
+.swagger-ui .btn {
+ background-color: var(--secondary-color);
+ border-color: var(--primary-color);
+ color: white;
+}
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/AdvancedOptions.cs b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/AdvancedOptions.cs
index e91b207..e7bf728 100644
--- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/AdvancedOptions.cs
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/AdvancedOptions.cs
@@ -9,6 +9,7 @@ internal static class AdvancedOptions
internal const string BackToTop = nameof(BackToTop);
internal const string StickyOperations = nameof(StickyOperations);
internal const string ExpandOrCollapseAllOperations = nameof(ExpandOrCollapseAllOperations);
+ internal const string ThemeSwitcher = nameof(ThemeSwitcher);
// CSS placeholders
internal const string StickyOperationsCssPH = "#STICKY_OPERATIONS";
@@ -19,11 +20,13 @@ internal static class AdvancedOptions
internal const string PinnableTopbarJsPH = "{$PINNABLE_TOPBAR}";
internal const string BackToTopJsPH = "{$BACK_TO_TOP}";
internal const string ExpandOrCollapseAllOperationsJsPH = "{$EXPAND_COLLAPSE_ALL_OPERATIONS}";
+ internal const string ThemeSwitcherJsPH = "{$THEME_SWITCHER}";
private static readonly string[][] s_jsDependentFeatures = [
[PinnableTopbar, PinnableTopbarJsPH],
[BackToTop, BackToTopJsPH],
- [ExpandOrCollapseAllOperations, ExpandOrCollapseAllOperationsJsPH]
+ [ExpandOrCollapseAllOperations, ExpandOrCollapseAllOperationsJsPH],
+ [ThemeSwitcher, ThemeSwitcherJsPH]
];
internal static void EnablePinnableTopbar(this IDictionary options)
@@ -46,6 +49,11 @@ internal static void EnableExpandOrCollapseAllOperations(this IDictionary options)
+ {
+ options.TryAdd(ThemeSwitcher, true);
+ }
+
internal static bool AnyJsFeatureEnabled(IDictionary options)
{
foreach (var feature in s_jsDependentFeatures)
@@ -68,8 +76,6 @@ internal static string Apply(string content, IDictionary advance
};
}
- #region Private
-
private static string ApplyCssFeatures(string cssContent, IDictionary cssAdvancedOptions)
{
if (cssAdvancedOptions.TryGetValue(StickyOperations, out var isEnabled) && isEnabled is true)
@@ -93,6 +99,4 @@ private static string ApplyJsFeatures(string jsContent, IDictionary
/// The theme name.
- protected virtual string GetThemeName()
+ protected internal virtual string GetThemeName()
{
- var nameWithoutExtension = FileName
- .Replace(".min.css", "", StringComparison.OrdinalIgnoreCase)
- .Replace(".css", "", StringComparison.OrdinalIgnoreCase);
+ var fileName = FileName;
- return char.ToUpper(nameWithoutExtension[0]) + nameWithoutExtension[1..];
+ // Remove extension
+ if (fileName.EndsWith(".min.css", StringComparison.OrdinalIgnoreCase))
+ fileName = fileName[..^8];
+ else if (fileName.EndsWith(".css", StringComparison.OrdinalIgnoreCase))
+ fileName = fileName[..^4];
+
+ if (string.IsNullOrEmpty(fileName))
+ return string.Empty;
+
+ return char.ToUpper(fileName[0]) + fileName[1..];
}
private static void CheckFileNameExtension(string fileName)
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs
index 8ede20f..31b0f25 100644
--- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs
@@ -1,9 +1,14 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
+using System.Collections.Frozen;
using System.Reflection;
+using System.Text.Json;
namespace AspNetCore.Swagger.Themes;
+///
+/// Handles file I/O and HTTP endpoint registration for themes.
+///
internal static class FileProvider
{
private const string _Prefix = "AspNetCore.Swagger.Themes";
@@ -13,9 +18,29 @@ internal static class FileProvider
internal const string StylesPath = "/styles/";
internal const string ScriptsPath = "/scripts/";
-
+ internal const string ThemeMetadataPath = "/themes/metadata.json";
internal const string JsFilename = "ui.min.js";
+ // Track registered endpoints to prevent duplicates
+ private static readonly HashSet s_registeredEndpoints = new(StringComparer.OrdinalIgnoreCase);
+
+ private static FrozenSet s_frozenEndpoints;
+
+ // Predefined theme names (compile-time constant)
+ private static readonly FrozenSet s_predefinedThemeNames = new[]
+ {
+ nameof(Theme.Dark), nameof(Theme.Light), nameof(Theme.Forest), nameof(Theme.DeepSea), nameof(Theme.Desert), nameof(Theme.Futuristic)
+ }.ToFrozenSet(StringComparer.OrdinalIgnoreCase);
+
+ private static readonly JsonSerializerOptions s_cachedJsonOptions = new()
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ WriteIndented = false
+ };
+
+ ///
+ /// Gets resource text from embedded resources.
+ ///
internal static string GetResourceText(string fileName, Type themeType = null)
{
var assembly = themeType?.Assembly ?? Assembly.GetExecutingAssembly();
@@ -29,38 +54,44 @@ internal static string GetResourceText(string fileName, Type themeType = null)
return reader.ReadToEnd();
}
+ ///
+ /// Gets resource text from assembly with common style detection.
+ ///
internal static string GetResourceText(string fileName, Assembly assembly, out string commonStyle, out bool loadJs)
{
if (!IsCssFile(fileName))
- throw new InvalidOperationException($"{fileName} is not a valid name for CSS files. It must end with '.css' or '.min.css'.");
+ throw new InvalidOperationException($"{fileName} is not a valid CSS file. Must end with '.css' or '.min.css'.");
var resourceNamespaces = assembly.GetManifestResourceNames()
- .Where(n => n.EndsWith(_CustomStylesNamespace + fileName, StringComparison.OrdinalIgnoreCase))
+ .Where(n => n.EndsWith(_CustomStylesNamespace + fileName, StringComparison.OrdinalIgnoreCase)) // TODO: add other namespaces?
.ToArray();
if (resourceNamespaces.Length != 1)
- throw new InvalidOperationException($"Can't find {fileName} or it appears more than one time in assembly {assembly.GetName().Name}.");
+ throw new InvalidOperationException($"Can't find {fileName} or it appears more than once in {assembly.GetName().Name}.");
var resourceName = resourceNamespaces[0];
-
- // Retrieve the common theme and determine if JS needs to be loaded
commonStyle = RetrieveCommonStyleFromCustom(resourceName, out loadJs);
using var stream = assembly.GetManifestResourceStream(resourceName)
- ?? throw new FileNotFoundException($"Can't find {fileName} resource in assembly {assembly.GetName().Name}.");
+ ?? throw new FileNotFoundException($"Can't find {fileName} in {assembly.GetName().Name}.");
using var reader = new StreamReader(stream);
return reader.ReadToEnd();
}
+ ///
+ /// Registers a GET endpoint for serving static content.
+ ///
internal static void AddGetEndpoint(IApplicationBuilder app, string path, string content, string contentType = MimeTypes.Text.Css)
{
+ if (!s_registeredEndpoints.Add(path))
+ return;
+
if (app is WebApplication webApp)
{
webApp.MapGet(path, (HttpContext context) =>
{
- SetHeaders(context);
-
+ SetCacheHeaders(context);
return Results.Content(content, contentType);
})
.ExcludeFromDescription();
@@ -71,8 +102,7 @@ internal static void AddGetEndpoint(IApplicationBuilder app, string path, string
{
if (context.Request.Path.Equals(path, StringComparison.OrdinalIgnoreCase))
{
- SetHeaders(context);
-
+ SetCacheHeaders(context);
context.Response.ContentType = contentType;
await context.Response.WriteAsync(content);
}
@@ -83,14 +113,67 @@ internal static void AddGetEndpoint(IApplicationBuilder app, string path, string
});
}
- static void SetHeaders(HttpContext context)
+ static void SetCacheHeaders(HttpContext context)
{
context.Response.Headers.CacheControl = "max-age=3600";
context.Response.Headers.Expires = DateTime.UtcNow.AddHours(1).ToString("R");
}
}
- #region Private
+ ///
+ /// Exposes theme metadata as JSON endpoint.
+ ///
+ internal static void ExposeThemeMetadata(
+ IApplicationBuilder app,
+ IEnumerable themes,
+ string currentThemeName,
+ string displayFormat)
+ {
+ if (s_registeredEndpoints.Contains(ThemeMetadataPath))
+ return;
+
+ var themeList = themes.Select(rt => new
+ {
+ name = rt.Name,
+ displayName = rt.Name,
+ cssPath = rt.CssPath,
+ isStandalone = rt.IsStandalone
+ }).ToList();
+
+ // Ensure current theme is first
+ var current = themeList.FirstOrDefault(t => t.name.Equals(currentThemeName, StringComparison.OrdinalIgnoreCase));
+ if (current is not null)
+ {
+ themeList.Remove(current);
+ themeList.Insert(0, current);
+ }
+
+ var response = new
+ {
+ themes = themeList,
+ config = new
+ {
+ displayFormat,
+ currentTheme = currentThemeName
+ }
+ };
+
+ var json = JsonSerializer.Serialize(response, s_cachedJsonOptions);
+
+ AddGetEndpoint(app, ThemeMetadataPath, json, MimeTypes.Application.Json);
+ }
+
+ ///
+ /// Freezes collections after startup for better read performance.
+ ///
+ internal static void FreezeCollections() =>
+ s_frozenEndpoints ??= s_registeredEndpoints.ToFrozenSet(StringComparer.OrdinalIgnoreCase);
+
+ ///
+ /// Checks if a theme name is predefined.
+ ///
+ internal static bool IsPredefinedTheme(string themeName) =>
+ s_predefinedThemeNames.Contains(themeName);
private static bool IsCssFile(string fileName) =>
fileName.EndsWith(".css", StringComparison.OrdinalIgnoreCase) ||
@@ -99,7 +182,7 @@ private static bool IsCssFile(string fileName) =>
private static string DetermineResourceNamespace(string fileName, Type themeType)
{
if (IsCssFile(fileName) && themeType is not null && themeType.BaseType != typeof(BaseTheme))
- return themeType.Namespace;
+ return themeType.Namespace!;
return IsCssFile(fileName) ? _StylesNamespace : _ScriptsNamespace;
}
@@ -111,18 +194,12 @@ private static string RetrieveCommonStyleFromCustom(string resourceName, out boo
if (!resourceName.Contains(_CustomStylesNamespace))
return string.Empty;
- // Extract filename from resource name
- int index = resourceName.IndexOf(_CustomStylesNamespace);
- string fileName = resourceName[(index + _CustomStylesNamespace.Length)..];
+ var fileName = resourceName[(resourceName.IndexOf(_CustomStylesNamespace) + _CustomStylesNamespace.Length)..];
- // If filename contains "standalone", don't load anything (fully independent)
if (fileName.Contains("standalone", StringComparison.OrdinalIgnoreCase))
return string.Empty;
- // Otherwise, load both common theme and JS
loadJs = true;
return GetResourceText("common.min.css");
}
-
- #endregion Private
}
\ No newline at end of file
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/MimeTypes.cs b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/MimeTypes.cs
index 1c9b2f0..c5c1b33 100644
--- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/MimeTypes.cs
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/MimeTypes.cs
@@ -22,4 +22,15 @@ internal static class Text
///
internal const string Javascript = _Prefix + "javascript";
}
+
+ ///
+ /// MIME type constants for application/* types.
+ ///
+ internal static class Application
+ {
+ ///
+ /// application/json
+ ///
+ internal const string Json = "application/json";
+ }
}
\ No newline at end of file
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js
index 583b763..24a6672 100644
--- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.js
@@ -18,6 +18,8 @@ window.onpageshow = function () {
setUpScrollToTopButton({$BACK_TO_TOP});
setUpExpandAndCollapseOperationsButtons({$EXPAND_COLLAPSE_ALL_OPERATIONS});
+
+ setUpThemeSwitcher({$THEME_SWITCHER});
}
}, 100);
}
@@ -148,4 +150,175 @@ function setUpExpandAndCollapseOperationsButtons(enabled) {
});
}
});
+}
+
+function setUpThemeSwitcher(enabled) {
+ if (enabled === false)
+ return;
+
+ const STORAGE_KEY = 'swaggerui-theme-preference';
+ const METADATA_ENDPOINT = '/themes/metadata.json';
+
+ let themesMetadata = null;
+ let currentTheme = null;
+
+ // Load theme metadata and initialize
+ fetch(METADATA_ENDPOINT)
+ .then(response => {
+ if (!response.ok) {
+ console.warn('[ThemeSwitcher] Failed to load theme metadata');
+ return null;
+ }
+ return response.json();
+ })
+ .then(data => {
+ if (!data || !data.themes || data.themes.length < 2) {
+ console.warn('[ThemeSwitcher] Not enough themes available for switcher');
+ return;
+ }
+
+ themesMetadata = data;
+ currentTheme = detectCurrentTheme(data.themes);
+ restoreSavedTheme(data.themes);
+ injectThemeSwitcherUI(data);
+ })
+ .catch(error => {
+ console.error('[ThemeSwitcher] Error loading themes:', error);
+ });
+
+ function detectCurrentTheme(themes) {
+ // Check for data-theme attribute first (most reliable)
+ const activeLink = document.querySelector('link[rel="stylesheet"]:not([disabled])[data-theme]');
+ if (activeLink && activeLink.dataset.theme) {
+ return activeLink.dataset.theme;
+ }
+
+ // Fallback: check href patterns
+ const styleElements = document.querySelectorAll('link[rel="stylesheet"]:not([disabled])');
+
+ for (const element of styleElements) {
+ const theme = themes.find(t => {
+ if (element.href) {
+ return element.href.includes(t.cssPath) ||
+ element.href.endsWith(t.cssPath) ||
+ element.href.includes(t.name.toLowerCase());
+ }
+ return false;
+ });
+
+ if (theme) {
+ return theme.name;
+ }
+ }
+
+ // Fallback to first theme
+ return themes.length > 0 ? themes[0].name : null;
+ }
+
+ function restoreSavedTheme(themes) {
+ const saved = localStorage.getItem(STORAGE_KEY);
+
+ if (saved && themes.some(t => t.name === saved)) {
+ if (saved !== currentTheme) {
+ switchTheme(saved, false);
+ }
+ }
+ }
+
+ function injectThemeSwitcherUI(data) {
+ const topbarWrapper = document.querySelector('.topbar-wrapper');
+ if (!topbarWrapper) {
+ console.warn('[ThemeSwitcher] Topbar not found');
+ return;
+ }
+
+ const select = document.createElement('select');
+ select.id = 'theme-switcher-select';
+ select.setAttribute('aria-label', 'Select theme');
+ select.title = 'Switch theme';
+
+ data.themes.forEach(theme => {
+ const option = document.createElement('option');
+ option.value = theme.name;
+ option.textContent = formatThemeName(theme.name, data.config?.displayFormat || '{name}');
+
+ if (theme.name === currentTheme) {
+ option.selected = true;
+ }
+
+ select.appendChild(option);
+ });
+
+ select.addEventListener('change', (e) => {
+ switchTheme(e.target.value, true);
+ });
+
+ // Insert before pin button if it exists
+ const pinButton = document.getElementById('pin-topbar-btn');
+ if (pinButton) {
+ topbarWrapper.insertBefore(select, pinButton);
+ } else {
+ topbarWrapper.appendChild(select);
+ }
+ }
+
+ function switchTheme(themeName, saveToStorage) {
+ const theme = themesMetadata?.themes.find(t => t.name === themeName);
+ if (!theme) {
+ console.warn(`[ThemeSwitcher] Theme not found: ${themeName}`);
+ return;
+ }
+
+ // Find all theme stylesheets
+ const allStyleLinks = document.querySelectorAll('link[rel="stylesheet"]');
+ const allThemePaths = themesMetadata.themes.map(t => t.cssPath);
+
+ let themeActivated = false;
+
+ allStyleLinks.forEach(link => {
+ // Check if this is a theme stylesheet (has data-theme or matches a known theme path)
+ const isThemeStylesheet = link.dataset.theme ||
+ allThemePaths.some(path => link.href && link.href.endsWith(path));
+
+ if (isThemeStylesheet) {
+ // Use exact path matching to avoid substring issues
+ const isTargetTheme = (link.dataset.theme === themeName) ||
+ (link.href && link.href.endsWith(theme.cssPath));
+
+ if (isTargetTheme) {
+ link.disabled = false;
+ link.dataset.theme = themeName;
+ themeActivated = true;
+ } else {
+ link.disabled = true;
+ }
+ }
+ });
+
+ if (!themeActivated) {
+ console.warn(`[ThemeSwitcher] Could not activate theme: ${themeName}`);
+ }
+
+ currentTheme = themeName;
+
+ if (saveToStorage) {
+ localStorage.setItem(STORAGE_KEY, themeName);
+ }
+
+ const dropdown = document.getElementById('theme-switcher-select');
+ if (dropdown && dropdown.value !== themeName) {
+ dropdown.value = themeName;
+ }
+ }
+
+ function formatThemeName(name, format) {
+ const formatted = name
+ .replace(/([A-Z])/g, ' $1')
+ .trim()
+ .split(/[\s_-]+/)
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
+ .join(' ');
+
+ return format.replace('{name}', formatted);
+ }
}
\ No newline at end of file
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js
index 7e8d8ad..1c3411a 100644
--- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/ui.min.js
@@ -1,5 +1,5 @@
-/*Swagger UI*/const rootElement=document.documentElement;function setUpPinnableTopbar(e){if(!1===e)return;let t=document.querySelector(".topbar-wrapper"),n=document.createElement("button");n.setAttribute("id","pin-topbar-btn"),n.addEventListener("click",()=>pinOrUnpinTopbar(n)),t.appendChild(n),pinOrUnpinTopbar(n)}function pinOrUnpinTopbar(e){e.parentNode.parentNode.parentNode.classList.contains("pinned")?(e.parentNode.parentNode.parentNode.classList.remove("pinned"),setUnpinnedIconTo(e),e.setAttribute("title","Pin topbar")):(e.parentNode.parentNode.parentNode.classList.add("pinned"),setPinnedIconTo(e),e.setAttribute("title","Unpin topbar")),e?.blur()}function setPinnedIconTo(e){e.innerHTML=`
+/*Swagger UI https://github.com/teociaps/SwaggerUI.Themes */const rootElement=document.documentElement;function setUpPinnableTopbar(e){if(!1===e)return;let t=document.querySelector(".topbar-wrapper"),n=document.createElement("button");n.setAttribute("id","pin-topbar-btn"),n.addEventListener("click",()=>pinOrUnpinTopbar(n)),t.appendChild(n),pinOrUnpinTopbar(n)}function pinOrUnpinTopbar(e){e.parentNode.parentNode.parentNode.classList.contains("pinned")?(e.parentNode.parentNode.parentNode.classList.remove("pinned"),setUnpinnedIconTo(e),e.setAttribute("title","Pin topbar")):(e.parentNode.parentNode.parentNode.classList.add("pinned"),setPinnedIconTo(e),e.setAttribute("title","Unpin topbar")),e?.blur()}function setPinnedIconTo(e){e.innerHTML=``}function setUnpinnedIconTo(e){e.innerHTML=`
- `}function setUpScrollToTopButton(e){if(!1===e)return;let t=document.createElement("div");t.classList.add("scroll-to-top-wrapper");let n=document.createElement("button");n.setAttribute("id","scroll-to-top-btn"),n.setAttribute("title","Back to top"),n.addEventListener("click",()=>{scrollToTop(),n?.blur()}),t.appendChild(n);let $=document.getElementById("swagger-ui");$.appendChild(t);let o=()=>{window.scrollY>=200?n.classList.add("showBtn"):n.classList.remove("showBtn")};window.addEventListener("scroll",o),window.addEventListener("resize",o)}function scrollToTop(){rootElement.scrollTo({top:0,behavior:"smooth"})}function setUpExpandAndCollapseOperationsButtons(e){if(!1===e)return;let t=document.querySelectorAll(".opblock-tag-section");t.forEach(e=>{let t=e.querySelector("h3"),n=t.querySelector("button.expand-operation");if(n){let $=document.createElement("button");$.setAttribute("title","Expand/Collapse all the operations"),$.classList.add("expand-collapse-all-btn"),$.innerHTML="Expand/Collapse All",t.insertBefore($,n),$.addEventListener("click",t=>{t.preventDefault(),t.stopPropagation();let n=e.querySelectorAll(".opblock .opblock-control-arrow"),$=Array.from(n).every(e=>"true"===e.getAttribute("aria-expanded"));$?n.forEach(e=>{"true"===e.getAttribute("aria-expanded")&&e.click()}):n.forEach(e=>{"false"===e.getAttribute("aria-expanded")&&e.click()})})}})}window.onpageshow=function(){let e=setInterval(function(){null!=document.getElementById("swagger-ui")&&(clearInterval(e),console.log("Hello Swagger UI!"),setUpPinnableTopbar({$PINNABLE_TOPBAR}),setUpScrollToTopButton({$BACK_TO_TOP}),setUpExpandAndCollapseOperationsButtons({$EXPAND_COLLAPSE_ALL_OPERATIONS}))},100)};
\ No newline at end of file
+ `}function setUpScrollToTopButton(e){if(!1===e)return;let t=document.createElement("div");t.classList.add("scroll-to-top-wrapper");let n=document.createElement("button");n.setAttribute("id","scroll-to-top-btn"),n.setAttribute("title","Back to top"),n.addEventListener("click",()=>{scrollToTop(),n?.blur()}),t.appendChild(n);let l=document.getElementById("swagger-ui");l.appendChild(t);let r=()=>{window.scrollY>=200?n.classList.add("showBtn"):n.classList.remove("showBtn")};window.addEventListener("scroll",r),window.addEventListener("resize",r)}function scrollToTop(){rootElement.scrollTo({top:0,behavior:"smooth"})}function setUpExpandAndCollapseOperationsButtons(e){if(!1===e)return;let t=document.querySelectorAll(".opblock-tag-section");t.forEach(e=>{let t=e.querySelector("h3"),n=t.querySelector("button.expand-operation");if(n){let l=document.createElement("button");l.setAttribute("title","Expand/Collapse all the operations"),l.classList.add("expand-collapse-all-btn"),l.innerHTML="Expand/Collapse All",t.insertBefore(l,n),l.addEventListener("click",t=>{t.preventDefault(),t.stopPropagation();let n=e.querySelectorAll(".opblock .opblock-control-arrow"),l=Array.from(n).every(e=>"true"===e.getAttribute("aria-expanded"));l?n.forEach(e=>{"true"===e.getAttribute("aria-expanded")&&e.click()}):n.forEach(e=>{"false"===e.getAttribute("aria-expanded")&&e.click()})})}})}function setUpThemeSwitcher(e){if(!1===e)return;let t="swaggerui-theme-preference",n=null,l=null;function r(e,r){let a=n?.themes.find(t=>t.name===e);if(!a){console.warn(`[ThemeSwitcher] Theme not found: ${e}`);return}let o=document.querySelectorAll('link[rel="stylesheet"]'),i=n.themes.map(e=>e.cssPath),s=!1;o.forEach(t=>{let n=t.dataset.theme||i.some(e=>t.href&&t.href.endsWith(e));if(n){let l=t.dataset.theme===e||t.href&&t.href.endsWith(a.cssPath);l?(t.disabled=!1,t.dataset.theme=e,s=!0):t.disabled=!0}}),s||console.warn(`[ThemeSwitcher] Could not activate theme: ${e}`),l=e,r&&localStorage.setItem(t,e);let $=document.getElementById("theme-switcher-select");$&&$.value!==e&&($.value=e)}fetch("/themes/metadata.json").then(e=>e.ok?e.json():(console.warn("[ThemeSwitcher] Failed to load theme metadata"),null)).then(e=>{if(!e||!e.themes||e.themes.length<2){console.warn("[ThemeSwitcher] Not enough themes available for switcher");return}n=e,l=function e(t){let n=document.querySelector('link[rel="stylesheet"]:not([disabled])[data-theme]');if(n&&n.dataset.theme)return n.dataset.theme;let l=document.querySelectorAll('link[rel="stylesheet"]:not([disabled])');for(let r of l){let a=t.find(e=>!!r.href&&(r.href.includes(e.cssPath)||r.href.endsWith(e.cssPath)||r.href.includes(e.name.toLowerCase())));if(a)return a.name}return t.length>0?t[0].name:null}(e.themes),function e(n){let a=localStorage.getItem(t);a&&n.some(e=>e.name===a)&&a!==l&&r(a,!1)}(e.themes),function e(t){let n=document.querySelector(".topbar-wrapper");if(!n){console.warn("[ThemeSwitcher] Topbar not found");return}let a=document.createElement("select");a.id="theme-switcher-select",a.setAttribute("aria-label","Select theme"),a.title="Switch theme",t.themes.forEach(e=>{let n=document.createElement("option");n.value=e.name,n.textContent=function e(t,n){let l=t.replace(/([A-Z])/g," $1").trim().split(/[\s_-]+/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(" ");return n.replace("{name}",l)}(e.name,t.config?.displayFormat||"{name}"),e.name===l&&(n.selected=!0),a.appendChild(n)}),a.addEventListener("change",e=>{r(e.target.value,!0)});let o=document.getElementById("pin-topbar-btn");o?n.insertBefore(a,o):n.appendChild(a)}(e)}).catch(e=>{console.error("[ThemeSwitcher] Error loading themes:",e)})}window.onpageshow=function(){let e=setInterval(function(){null!=document.getElementById("swagger-ui")&&(clearInterval(e),console.log("Hello Swagger UI!"),setUpPinnableTopbar({$PINNABLE_TOPBAR}),setUpScrollToTopButton({$BACK_TO_TOP}),setUpExpandAndCollapseOperationsButtons({$EXPAND_COLLAPSE_ALL_OPERATIONS}),setUpThemeSwitcher({$THEME_SWITCHER}))},100)};
\ No newline at end of file
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css
index 9a6edd5..fbd08b2 100644
--- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.css
@@ -126,7 +126,7 @@ body {
cursor: pointer;
background: transparent;
border: none;
- margin: .8rem;
+ margin: .2rem;
padding: .2rem;
transition: all .2s;
rotate: z 359deg;
@@ -251,6 +251,13 @@ body {
color: var(--topbar-download-url-button-color, #fff);
}
+#theme-switcher-select {
+ background: var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);
+ border: 2px solid var(--topbar-select-border-color, #62a03f);
+ min-width: 120px;
+ box-shadow: none;
+}
+
/* Info-box (title, description, contact info...) */
.swagger-ui .info li,
.swagger-ui .info p,
@@ -396,7 +403,7 @@ body {
}
.swagger-ui:has(> .topbar.pinned) .opblock-tag-section.is-open .opblock-tag {
- top: 4.9rem;
+ top: 4.2rem;
}
.swagger-ui .opblock {
@@ -739,12 +746,18 @@ body {
border: 2px solid var(--select-border-color, #41444e);
box-shadow: 0 1px 2px 0 var(--select-shadow-color, rgba(0,0,0,.25));
color: var(--swagger-main-color, #3b4151);
+ cursor: pointer;
}
.swagger-ui select[multiple] {
background: var(--select-background-color, #f7f7f7);
}
+ .swagger-ui select:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+
.swagger-ui label {
color: var(--swagger-main-color, #3b4151);
}
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css
index ce76ec5..9c800ca 100644
--- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/common.min.css
@@ -1 +1 @@
-/*Common Theme*/@charset "UTF-8";*,:focus{font-family:system-ui,sans-serif}.swagger-ui,.swagger-ui .checkbox p,.swagger-ui .dialog-ux .modal-ux-content h4,.swagger-ui .dialog-ux .modal-ux-content p,.swagger-ui .dialog-ux .modal-ux-header h3,.swagger-ui .errors-wrapper .errors h4,.swagger-ui .errors-wrapper hgroup h4,.swagger-ui .info .base-url,.swagger-ui .info .title,.swagger-ui .info h1,.swagger-ui .info h2,.swagger-ui .info h3,.swagger-ui .info h4,.swagger-ui .info h5,.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info table,.swagger-ui .loading-container .loading:after,.swagger-ui .model,.swagger-ui .opblock .opblock-section-header h4,.swagger-ui .opblock .opblock-section-header>label,.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-external-docs-wrapper,.swagger-ui .opblock-external-docs-wrapper h4,.swagger-ui .opblock-external-docs-wrapper p,.swagger-ui .opblock-tag,.swagger-ui .opblock-tag small,.swagger-ui .opblock-title_normal,.swagger-ui .opblock-title_normal h4,.swagger-ui .opblock-title_normal p,.swagger-ui .parameter__name,.swagger-ui .parameter__type,.swagger-ui .response-col_links,.swagger-ui .response-col_status,.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5,.swagger-ui .scheme-container .schemes>label,.swagger-ui .scopes h2,.swagger-ui .servers>label,.swagger-ui .tab li,.swagger-ui label,.swagger-ui section h3,.swagger-ui table.headers td{color:var(--swagger-main-color,#3b4151)}::-webkit-scrollbar{width:16px}::-webkit-scrollbar-thumb{height:56px;border-radius:8px;border:4px solid transparent;background-clip:content-box;background-color:var(--scrollbar-thumb-color,#a5a5a5);transition:background-color .3s}::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover-color,#727272)}body::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-color,#a5a5a5)}:focus{outline:0!important}:focus-visible{outline:auto!important;outline-offset:1px}body{background-color:var(--body-background-color,#fafafa);padding-bottom:1rem}*,::after,::before{box-sizing:border-box}.swagger-ui{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.15;margin:0}.swagger-ui .loading-container .loading:before{border:2px solid var(--loading-container-border-color,rgba(85,85,85,.1));border-top-color:var(--loading-container-border-top-color,rgba(0,0,0,.6))}.swagger-ui .errors-wrapper{background:var(--errors-wrapper-background-color,rgba(249,62,62,.1));border:2px solid var(--errors-wrapper-border-color,#f93e3e)}.swagger-ui .errors-wrapper .errors small{color:var(--errors-wrapper-errors-color,#606060)}.swagger-ui .topbar{background-color:transparent;z-index:1000;padding:0}.swagger-ui .topbar.pinned{position:sticky;top:10px}.swagger-ui .topbar.pinned::before{content:'';position:absolute;inset:-10px 0 0;pointer-events:none;user-select:none;backdrop-filter:blur(20px);-webkit-mask-image:linear-gradient(to bottom,black 40%,transparent);mask-image:linear-gradient(to bottom,black 40%,transparent);background:linear-gradient(90deg,rgba(0,0,0,0) 5%,rgba(8,8,8,.4) 50%,rgba(0,0,0,0) 95%)}.swagger-ui .topbar .topbar-wrapper{background-color:var(--topbar-background-color,#000);border:1px solid var(--topbar-border-color,gray);border-top:0;border-radius:0 0 .5rem .5rem;padding:.5rem;box-shadow:0 5px 15px 0 var(--topbar-pinned-shadow-color,rgba(0,0,0,.5))}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn svg{fill:var(--topbar-pin-icon-color,white)}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn{cursor:pointer;background:0 0;border:none;margin:.8rem;padding:.2rem;transition:.2s;rotate:z 359deg}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:hover{scale:1.05;animation:1.2s infinite pintobbar}#scroll-to-top-btn:active,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:active{scale:0.95}.swagger-ui .topbar.pinned .topbar-wrapper{background-color:var(--topbar-pinned-background-color,rgba(0,0,0,.7));border:1px solid var(--topbar-border-color,gray);border-radius:.5rem;backdrop-filter:blur(20px)}.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn{translate:0 5px;rotate:z 0deg}.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:hover{animation:1.2s infinite unpintobbar}@keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-moz-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-webkit-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@keyframes unpintobbar{10%{translate:0 1px}70%{rotate:z 50deg;translate:-3px 1px}}@-moz-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}@-webkit-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}.swagger-ui .topbar .download-url-wrapper .select-label select,.swagger-ui .topbar .download-url-wrapper input[type=text]{border:2px solid var(--topbar-select-border-color,#62a03f)}.swagger-ui .topbar .download-url-wrapper .select-label{color:var(--topbar-select-label-color,#f0f0f0)}.swagger-ui .topbar .download-url-wrapper .download-url-button{background:var(--topbar-download-url-button-background-color,#62a03f);color:var(--topbar-download-url-button-color,#fff)}.swagger-ui .info a,.swagger-ui .scopes h2 a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .info a:hover{color:var(--swagger-info-link-hover,#1f69c0)}.swagger-ui .info .title small{background:var(--api-version-background-color,#7d8492)}.swagger-ui .info .title small.version-stamp{background-color:var(--api-version-stamp-background-color,#89bf04)}.swagger-ui .info .title small pre{color:var(--api-version-color,#fff)}.swagger-ui .scheme-container{background:0 0;box-shadow:none;margin:0;padding:0}.swagger-ui .auth-wrapper{display:flex;flex:1;justify-content:flex-end;border:1px solid var(--auth-wrapper-border-color,#d5d5d5);padding:0;border-radius:5px;box-shadow:0 3px 5px -5px var(--auth-wrapper-box-shadow-color,#000);background:var(--auth-wrapper-background-color,#fff)}.swagger-ui .auth-container{border-bottom:1px solid var(--auth-container-border-bottom-color,#ebebeb);color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container .errors{background-color:var(--auth-container-background-color,#fee);color:var(--auth-container-errors-color,red)}.swagger-ui .btn.authorize{background-color:var(--btn-authorize-background-color,transparent);border-color:var(--btn-authorize-border-color,#49cc90);color:var(--btn-authorize-font-color,#49cc90)}.swagger-ui .btn.authorize svg{fill:var(--btn-authorize-svg-fill-color,#49cc90)}.swagger-ui .opblock-tag-section{display:flex;flex-direction:column;border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;margin-block:1rem;overflow:clip}.swagger-ui .opblock-tag-section .expand-collapse-all-btn{display:none;user-select:none}.swagger-ui .opblock-tag-section.is-open .expand-collapse-all-btn{background:var(--opblock-tag-section-expandcollapse-background-color,rgb(220 220 220 / 45%));color:var(--swagger-main-color,#3b4151);border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;padding:5px 10px;font-size:medium;margin-inline:10px;display:block}.swagger-ui .opblock-tag{align-items:center;cursor:pointer;display:flex;padding:10px 20px 10px 10px;color:var(--swagger-main-color,#3b4151);font-size:24px;border-bottom:0 dashed var(--opblock-tag-border-bottom-color,#e1e1e1);margin:0;transition:.2s}.swagger-ui .opblock-tag:focus-within,.swagger-ui .opblock-tag:hover{background:var(--opblock-tag-background-color-hover,rgba(0,0,0,.02))}.swagger-ui .opblock-tag+div .operation-tag-content{margin-inline:5px}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag{position:sticky;top:0;z-index:4;margin-bottom:1rem;border-bottom:1px dashed var(--opblock-tag-border-bottom-color,#e1e1e1);background:var(--body-background-color,#fafafa)}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:focus-within,#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:hover{background:var(--opblock-tag-pinned-background-color-hover,#f1f1f1)}.swagger-ui:has(> .topbar.pinned) .opblock-tag-section.is-open .opblock-tag{top:4.9rem}.swagger-ui .opblock{border:1px solid var(--opblock-border-color,#000);box-shadow:0 0 3px var(--opblock-shadow-color,rgba(0,0,0,.19))}.swagger-ui .opblock .tab-header .tab-item.active h4 span:after{background:var(--opblock-tabheader-underline-color,gray)}.swagger-ui .opblock .opblock-summary button svg{fill:var(--opblock-summary-svg-icons-color,#000)}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid var(--opblock-summary-border-bottom-color,#000)}.swagger-ui .opblock.opblock-post,.swagger-ui .opblock.opblock-post .opblock-summary{border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock .opblock-section-header{background:var(--opblock-section-header-background-color,hsla(0,0%,100%,.8));box-shadow:0 1px 2px var(--opblock-section-header-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-summary-method{background:var(--opblock-summary-method-background-color,#000);color:var(--opblock-summary-method-color,#fff);text-shadow:0 1px 0 var(--opblock-summary-method-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{color:var(--swagger-main-color,#3b4151);word-break:normal}.swagger-ui .opblock.opblock-post{background:var(--opblock-post-background-color,rgba(73,204,144,.1))}.swagger-ui .opblock.opblock-post .opblock-summary-method{background:var(--opblock-post-method-color,#49cc90)}.swagger-ui .opblock.opblock-put,.swagger-ui .opblock.opblock-put .opblock-summary{border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span:after{background:var(--opblock-post-method-color,#49cc90);border-radius:1rem}.swagger-ui .opblock.opblock-put{background:var(--opblock-put-background-color,rgba(252,161,48,.1))}.swagger-ui .opblock.opblock-put .opblock-summary-method,.swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span:after{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-delete,.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete{background:var(--opblock-delete-background-color,rgba(249,62,62,.1))}.swagger-ui .opblock.opblock-delete .opblock-summary-method,.swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span:after{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-get,.swagger-ui .opblock.opblock-get .opblock-summary{border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get{background:var(--opblock-get-background-color,rgba(97,175,254,.1))}.swagger-ui .opblock.opblock-get .opblock-summary-method,.swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span:after{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-patch,.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch{background:var(--opblock-patch-background-color,rgba(80,227,194,.1))}.swagger-ui .opblock.opblock-patch .opblock-summary-method,.swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span:after{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-head,.swagger-ui .opblock.opblock-head .opblock-summary{border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head{background:var(--opblock-head-background-color,rgba(144,18,254,.1))}.swagger-ui .opblock.opblock-head .opblock-summary-method,.swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span:after{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-options,.swagger-ui .opblock.opblock-options .opblock-summary{border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options{background:var(--opblock-options-background-color,rgba(13,90,167,.1))}.swagger-ui .opblock.opblock-options .opblock-summary-method,.swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span:after{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-deprecated,.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated{background:var(--opblock-deprecated-background-color,hsla(0,0%,92%,.1))}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method,.swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span:after{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .tab li:first-of-type:after{background:var(--swagger-tabs-divider-color,rgba(0,0,0,.2))}.swagger-ui .response-col_links .response-undocumented,.swagger-ui .response-col_status .response-undocumented{color:var(--response-undocumented-color,#909090)}.swagger-ui .response-control-media-type--accept-controller select{border-color:var(--response-control-media-type-color,green)}.swagger-ui .response-control-media-type__accept-message{color:var(--response-control-media-type-color,green)}.swagger-ui .opblock-body pre.microlight{background:var(--opblock-pre-microlight-background-color,#333)!important;color:var(--opblock-pre-microlight-color,#fff)!important}.swagger-ui .download-contents{background:var(--download-contents-background-color,#7d8293);color:var(--download-contents-color,#fff)}.swagger-ui .copy-to-clipboard{background:var(--copy-to-clipboard-background-color,#7d8293)}.swagger-ui .copy-to-clipboard button{background:var(--copy-to-clipboard-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .filter .operation-filter-input{border:2px solid var(--filter-operation-filter-input-border-color,#d8dde7)}.swagger-ui .download-url-wrapper .failed,.swagger-ui .filter .failed{color:var(--filter-download-failed-color,red)}.swagger-ui .download-url-wrapper .loading,.swagger-ui .filter .loading{color:var(--filter-download-loading-color,#aaa)}.swagger-ui table.headers .header-example{color:var(--table-headers-example-color,#999)}.swagger-ui table thead tr td,.swagger-ui table thead tr th{border-bottom:1px solid var(--table-thead-border-bottom-color,rgba(59,65,81,.2));color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name.required span{color:var(--parameter-name-required-symbol-color,red)}.swagger-ui .parameter__name.required::after{color:var(--parameter-name-required-color,rgba(255,0,0,.6))}.swagger-ui .parameter__extension,.swagger-ui .parameter__in,.swagger-ui .response__extension{color:var(--parameter-in-extension-color,gray)}.swagger-ui .parameter__deprecated{color:var(--parameter-deprecated-color,red)}.swagger-ui .btn,.swagger-ui select,.swagger-ui textarea{color:var(--swagger-main-color,#3b4151)}.swagger-ui .btn{background:var(--button-background-color,transparent);border:2px solid var(--button-border-color,gray);box-shadow:0 1px 2px var(--button-shadow-color,rgba(0,0,0,.1))}.swagger-ui .btn:hover{box-shadow:0 0 5px var(--button-shadow-hover-color,rgba(0,0,0,.3))}.swagger-ui .btn.cancel{background-color:var(--button-cancel-background-color,transparent);border-color:var(--button-cancel-border-color,#ff6060);color:var(--button-cancel-color,#ff6060)}.swagger-ui .btn.execute{background-color:var(--button-execute-background-color,#4990e2);border-color:var(--button-execute-border-color,#4990e2);color:var(--button-execute-color,#fff)}.swagger-ui button.invalid{background:var(--button-invalid-background-color,#feebeb);border-color:var(--button-invalid-border-color,#f93e3e)}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{fill:var(--expand-operation-svg-arrow-color,#707070)}.swagger-ui .expand-methods:hover svg,.swagger-ui .expand-operation:hover svg{fill:var(--expand-operation-svg-arrow-hover-color,#404040)}.swagger-ui select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat);border:2px solid var(--select-border-color,#41444e);box-shadow:0 1px 2px 0 var(--select-shadow-color,rgba(0,0,0,.25))}.swagger-ui select[multiple]{background:var(--select-background-color,#f7f7f7)}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text],.swagger-ui textarea{background:var(--input-background-color,#fff);border:1px solid var(--input-border-color,#d9d9d9);color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email].invalid,.swagger-ui input[type=file].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid,.swagger-ui select.invalid,.swagger-ui textarea.invalid{background:var(--input-invalid-background-color,#feebeb);border-color:var(--input-invalid-border-color,#f93e3e);color:var(--input-invalid-color,#3b4151)}.swagger-ui input[disabled],.swagger-ui select[disabled]{background-color:var(--input-disabled-background-color,#fafafa);color:var(--input-disabled-color,#888)}.swagger-ui select[disabled]{border-color:var(--select-disabled-border-color,#888)}.swagger-ui textarea[disabled]{background-color:var(--textarea-disabled-background-color,#41444e);color:var(--textarea-disabled-color,#fff)}.swagger-ui textarea{background:var(--textarea-background-color,hsla(0,0%,100%,.8))}.swagger-ui textarea:focus{border:2px solid var(--textarea-border-focus-color,#61affe)}.swagger-ui textarea.curl{background:var(--textarea-curl-background-color,#41444e);color:var(--textarea-curl-color,#fff)}.swagger-ui .checkbox{color:var(--checkbox-color,#303030)}.swagger-ui .checkbox input[type=checkbox]+label>.item{background:var(--checkbox-label-item-background-color,#e8e8e8);box-shadow:0 0 0 2px var(--checkbox-label-item-shadow-color,#e8e8e8)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:var(--checkbox-label-item-icon, #e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .dialog-ux .backdrop-ux{background:var(--dialog-backdrop-color,rgba(0,0,0,.8))}.swagger-ui .dialog-ux .modal-ux{background:var(--dialog-background-color,#fff);border:1px solid var(--dialog-border-color,#ebebeb);box-shadow:0 10px 30px 0 var(--dialog-shadow-color,rgba(0,0,0,.2))}.swagger-ui .dialog-ux .modal-ux-header{border-bottom:1px solid var(--dialog-border-color,#ebebeb)}.swagger-ui .dialog-ux .modal-ux-header button svg{fill:var(--dialog-close-button-icon-color,black)}.swagger-ui .model .deprecated span,.swagger-ui .model .deprecated td{color:var(--model-deprecated-color,#a0a0a0)!important}.swagger-ui .model-toggle{cursor:pointer;-webkit-transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}.swagger-ui .model-toggle.collapsed{-webkit-transform:rotate(0);transform:rotate(0)}.swagger-ui .model-toggle::after{background:var(--model-arrow-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .model-hint{background:var(--model-hint-background-color,rgba(0,0,0,.7));color:var(--model-hint-color,#ebebeb)}.swagger-ui .model .property{color:var(--model-property-color,#999)}.swagger-ui .model .property.primitive{color:var(--model-property-primitive-color,#6b6b6b)}.swagger-ui table.model tr.property-row .star{color:var(--model-property-required-symbol-color,red)}.swagger-ui .model .external-docs,.swagger-ui table.model tr.description{color:var(--model-property-description,#666)}.swagger-ui table.model tr.extension{color:var(--model-property-extension,#777)}.swagger-ui section.models{border:1px solid var(--model-section-border-color,rgba(59,65,81,.3));border-radius:.5rem;padding:0;margin:0}.swagger-ui section.models.is-open h4{border-bottom:1px solid var(--model-section-border-color,rgba(59,65,81,.3))}.swagger-ui section.models h4{color:var(--model-section-header-color,#606060);padding:0}.swagger-ui section.models h4:hover{background:var(--model-section-header-hover-background-color,rgba(0,0,0,.02))}.swagger-ui section.models h4 svg{fill:var(--expand-model-svg-arrow-color,#707070)}.swagger-ui section.models h4 button.models-control{color:var(--model-section-header-color,#606060);padding:20px}.swagger-ui section.models h5{color:var(--model-section-little-header-color,#707070)}.swagger-ui section.models .model-container{background:var(--model-container-background-color,rgba(0,0,0,.05))}.swagger-ui section.models .model-container:hover{background:var(--model-container-hover-background-color,rgba(0,0,0,.07))}.swagger-ui .model-box{background:var(--model-box-background-color,rgba(0,0,0,.1))}.swagger-ui .model-title{color:var(--model-title-color,#505050)}.swagger-ui .model-deprecated-warning{color:var(--model-deprecated-warning-color,#f93e3e)}.swagger-ui .prop-type{color:var(--model-prop-type-color,#55a)}.swagger-ui .prop-format{color:var(--model-prop-format-color,#606060)}.swagger-ui .markdown pre,.swagger-ui .renderedMarkdown pre{color:var(--rendered-markdown-pre-color,#000)}.swagger-ui .markdown code,.swagger-ui .renderedMarkdown code{background:var(--rendered-markdown-code-background-color,rgba(0,0,0,.05));color:var(--rendered-markdown-code-color,#9012fe)}.scroll-to-top-wrapper{position:fixed;bottom:0;left:0;right:0;text-align:end;box-sizing:border-box;margin:0 auto;max-width:1460px;padding:0 20px;width:100%;pointer-events:none}#scroll-to-top-btn{background-color:var(--scroll-to-top-button-background-color,#000000d1);border:1px solid var(--scroll-to-top-button-border-color,#d1c9c9);border-radius:50%;color:#fff;cursor:pointer;width:2.5rem;aspect-ratio:1;backdrop-filter:blur(20px);margin:10px;position:relative;transition:background-color .5s,opacity .6s .2s,translate .8s,scale .2s;opacity:0;translate:0 100px;box-shadow:0 0 10px 0 var(--scroll-to-top-button-shadow-color,rgba(0,0,0,.4))}#scroll-to-top-btn.showBtn{opacity:1;translate:0 0;pointer-events:auto}#scroll-to-top-btn::before{position:absolute;content:'';top:10px;left:12px;right:12px;background-color:var(--scroll-to-top-button-icon-color,#fff);border-radius:1rem;height:2px}#scroll-to-top-btn::after{position:absolute;content:'';top:15px;left:50%;border:solid var(--scroll-to-top-button-icon-color,#fff);border-width:0 2px 2px 0;padding:3px;transform:rotate(-135deg);translate:-50% 4px}#scroll-to-top-btn:focus-within,#scroll-to-top-btn:hover{background-color:var(--scroll-to-top-button-hover-background-color,#0d0d0dba)}#scroll-to-top-btn:focus-within::after,#scroll-to-top-btn:hover::after{animation:1.2s ease-in infinite bounceUp}@keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-moz-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-webkit-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@media only screen and (max-width:680px){.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{word-break:break-all}}
\ No newline at end of file
+/*Common Theme https://github.com/teociaps/SwaggerUI.Themes */@charset "UTF-8";*,:focus{font-family:system-ui,sans-serif}.swagger-ui,.swagger-ui .checkbox p,.swagger-ui .dialog-ux .modal-ux-content h4,.swagger-ui .dialog-ux .modal-ux-content p,.swagger-ui .dialog-ux .modal-ux-header h3,.swagger-ui .errors-wrapper .errors h4,.swagger-ui .errors-wrapper hgroup h4,.swagger-ui .info .base-url,.swagger-ui .info .title,.swagger-ui .info h1,.swagger-ui .info h2,.swagger-ui .info h3,.swagger-ui .info h4,.swagger-ui .info h5,.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info table,.swagger-ui .loading-container .loading:after,.swagger-ui .model,.swagger-ui .opblock .opblock-section-header h4,.swagger-ui .opblock .opblock-section-header>label,.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-external-docs-wrapper,.swagger-ui .opblock-external-docs-wrapper h4,.swagger-ui .opblock-external-docs-wrapper p,.swagger-ui .opblock-tag,.swagger-ui .opblock-tag small,.swagger-ui .opblock-title_normal,.swagger-ui .opblock-title_normal h4,.swagger-ui .opblock-title_normal p,.swagger-ui .parameter__name,.swagger-ui .parameter__type,.swagger-ui .response-col_links,.swagger-ui .response-col_status,.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5,.swagger-ui .scheme-container .schemes>label,.swagger-ui .scopes h2,.swagger-ui .servers>label,.swagger-ui .tab li,.swagger-ui label,.swagger-ui section h3,.swagger-ui table.headers td{color:var(--swagger-main-color,#3b4151)}::-webkit-scrollbar{width:16px}::-webkit-scrollbar-thumb{height:56px;border-radius:8px;border:4px solid transparent;background-clip:content-box;background-color:var(--scrollbar-thumb-color,#a5a5a5);transition:background-color .3s}::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover-color,#727272)}body::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-color,#a5a5a5)}#theme-switcher-select,.swagger-ui select{background:var(--select-background-icon, #f7f7f7 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat)}:focus{outline:0!important}:focus-visible{outline:auto!important;outline-offset:1px}body{background-color:var(--body-background-color,#fafafa);padding-bottom:1rem}*,::after,::before{box-sizing:border-box}.swagger-ui{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.15;margin:0}.swagger-ui .loading-container .loading:before{border:2px solid var(--loading-container-border-color,rgba(85,85,85,.1));border-top-color:var(--loading-container-border-top-color,rgba(0,0,0,.6))}.swagger-ui .errors-wrapper{background:var(--errors-wrapper-background-color,rgba(249,62,62,.1));border:2px solid var(--errors-wrapper-border-color,#f93e3e)}.swagger-ui .errors-wrapper .errors small{color:var(--errors-wrapper-errors-color,#606060)}.swagger-ui .topbar{background-color:transparent;z-index:1000;padding:0}.swagger-ui .topbar.pinned{position:sticky;top:10px}.swagger-ui .topbar.pinned::before{content:'';position:absolute;inset:-10px 0 0;pointer-events:none;user-select:none;backdrop-filter:blur(20px);-webkit-mask-image:linear-gradient(to bottom,black 40%,transparent);mask-image:linear-gradient(to bottom,black 40%,transparent);background:linear-gradient(90deg,rgba(0,0,0,0) 5%,rgba(8,8,8,.4) 50%,rgba(0,0,0,0) 95%)}.swagger-ui .topbar .topbar-wrapper{background-color:var(--topbar-background-color,#000);border:1px solid var(--topbar-border-color,gray);border-top:0;border-radius:0 0 .5rem .5rem;padding:.5rem;box-shadow:0 5px 15px 0 var(--topbar-pinned-shadow-color,rgba(0,0,0,.5))}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn svg{fill:var(--topbar-pin-icon-color,white)}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn{cursor:pointer;background:0 0;border:none;margin:.2rem;padding:.2rem;transition:.2s;rotate:z 359deg}.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:hover{scale:1.05;animation:1.2s infinite pintobbar}#scroll-to-top-btn:active,.swagger-ui .topbar .topbar-wrapper #pin-topbar-btn:active{scale:0.95}.swagger-ui .topbar.pinned .topbar-wrapper{background-color:var(--topbar-pinned-background-color,rgba(0,0,0,.7));border:1px solid var(--topbar-border-color,gray);border-radius:.5rem;backdrop-filter:blur(20px)}#theme-switcher-select,.swagger-ui .topbar .download-url-wrapper .select-label select,.swagger-ui .topbar .download-url-wrapper input[type=text]{border:2px solid var(--topbar-select-border-color,#62a03f)}.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn{translate:0 5px;rotate:z 0deg}.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:focus-within,.swagger-ui .topbar.pinned .topbar-wrapper #pin-topbar-btn:hover{animation:1.2s infinite unpintobbar}@keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-moz-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@-webkit-keyframes pintobbar{30%{rotate:z 313deg;translate:0px 1px}40%{translate:0 5px}50%{rotate:z 313deg}}@keyframes unpintobbar{10%{translate:0 1px}70%{rotate:z 50deg;translate:-3px 1px}}@-moz-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}@-webkit-keyframes unpintobbar{10%{translate:0 -4px}70%{rotate:z 50deg;translate:-3px 1px}}.swagger-ui .topbar .download-url-wrapper .select-label{color:var(--topbar-select-label-color,#f0f0f0)}.swagger-ui .topbar .download-url-wrapper .download-url-button{background:var(--topbar-download-url-button-background-color,#62a03f);color:var(--topbar-download-url-button-color,#fff)}#theme-switcher-select{min-width:120px;box-shadow:none}.swagger-ui .info a,.swagger-ui .scopes h2 a{color:var(--swagger-info-link,#4990e2)}.swagger-ui .info a:hover{color:var(--swagger-info-link-hover,#1f69c0)}.swagger-ui .info .title small{background:var(--api-version-background-color,#7d8492)}.swagger-ui .info .title small.version-stamp{background-color:var(--api-version-stamp-background-color,#89bf04)}.swagger-ui .info .title small pre{color:var(--api-version-color,#fff)}.swagger-ui .scheme-container{background:0 0;box-shadow:none;margin:0;padding:0}.swagger-ui .auth-wrapper{display:flex;flex:1;justify-content:flex-end;border:1px solid var(--auth-wrapper-border-color,#d5d5d5);padding:0;border-radius:5px;box-shadow:0 3px 5px -5px var(--auth-wrapper-box-shadow-color,#000);background:var(--auth-wrapper-background-color,#fff)}.swagger-ui .auth-container{border-bottom:1px solid var(--auth-container-border-bottom-color,#ebebeb);color:var(--swagger-main-color,#3b4151)}.swagger-ui .auth-container .errors{background-color:var(--auth-container-background-color,#fee);color:var(--auth-container-errors-color,red)}.swagger-ui .btn.authorize{background-color:var(--btn-authorize-background-color,transparent);border-color:var(--btn-authorize-border-color,#49cc90);color:var(--btn-authorize-font-color,#49cc90)}.swagger-ui .btn.authorize svg{fill:var(--btn-authorize-svg-fill-color,#49cc90)}.swagger-ui .opblock-tag-section{display:flex;flex-direction:column;border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;margin-block:1rem;overflow:clip}.swagger-ui .opblock-tag-section .expand-collapse-all-btn{display:none;user-select:none}.swagger-ui .opblock-tag-section.is-open .expand-collapse-all-btn{background:var(--opblock-tag-section-expandcollapse-background-color,rgb(220 220 220 / 45%));color:var(--swagger-main-color,#3b4151);border:1px solid var(--opblock-tag-section-border-color,#ddd);border-radius:.5rem;padding:5px 10px;font-size:medium;margin-inline:10px;display:block}.swagger-ui .opblock-tag{align-items:center;cursor:pointer;display:flex;padding:10px 20px 10px 10px;color:var(--swagger-main-color,#3b4151);font-size:24px;border-bottom:0 dashed var(--opblock-tag-border-bottom-color,#e1e1e1);margin:0;transition:.2s}.swagger-ui .opblock-tag:focus-within,.swagger-ui .opblock-tag:hover{background:var(--opblock-tag-background-color-hover,rgba(0,0,0,.02))}.swagger-ui .opblock-tag+div .operation-tag-content{margin-inline:5px}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag{position:sticky;top:0;z-index:4;margin-bottom:1rem;border-bottom:1px dashed var(--opblock-tag-border-bottom-color,#e1e1e1);background:var(--body-background-color,#fafafa)}#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:focus-within,#STICKY_OPERATIONS.swagger-ui .opblock-tag-section.is-open .opblock-tag:hover{background:var(--opblock-tag-pinned-background-color-hover,#f1f1f1)}.swagger-ui:has(> .topbar.pinned) .opblock-tag-section.is-open .opblock-tag{top:4.2rem}.swagger-ui .opblock{border:1px solid var(--opblock-border-color,#000);box-shadow:0 0 3px var(--opblock-shadow-color,rgba(0,0,0,.19))}.swagger-ui .opblock .tab-header .tab-item.active h4 span:after{background:var(--opblock-tabheader-underline-color,gray)}.swagger-ui .opblock .opblock-summary button svg{fill:var(--opblock-summary-svg-icons-color,#000)}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid var(--opblock-summary-border-bottom-color,#000)}.swagger-ui .opblock.opblock-post,.swagger-ui .opblock.opblock-post .opblock-summary{border-color:var(--opblock-post-border-color,#49cc90)}.swagger-ui .opblock .opblock-section-header{background:var(--opblock-section-header-background-color,hsla(0,0%,100%,.8));box-shadow:0 1px 2px var(--opblock-section-header-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-summary-method{background:var(--opblock-summary-method-background-color,#000);color:var(--opblock-summary-method-color,#fff);text-shadow:0 1px 0 var(--opblock-summary-method-shadow-color,rgba(0,0,0,.1))}.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{color:var(--swagger-main-color,#3b4151);word-break:normal}.swagger-ui .opblock.opblock-post{background:var(--opblock-post-background-color,rgba(73,204,144,.1))}.swagger-ui .opblock.opblock-post .opblock-summary-method{background:var(--opblock-post-method-color,#49cc90)}.swagger-ui .opblock.opblock-put,.swagger-ui .opblock.opblock-put .opblock-summary{border-color:var(--opblock-put-border-color,#fca130)}.swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span:after{background:var(--opblock-post-method-color,#49cc90);border-radius:1rem}.swagger-ui .opblock.opblock-put{background:var(--opblock-put-background-color,rgba(252,161,48,.1))}.swagger-ui .opblock.opblock-put .opblock-summary-method,.swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span:after{background:var(--opblock-put-method-color,#fca130)}.swagger-ui .opblock.opblock-delete,.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:var(--opblock-delete-border-color,#f93e3e)}.swagger-ui .opblock.opblock-delete{background:var(--opblock-delete-background-color,rgba(249,62,62,.1))}.swagger-ui .opblock.opblock-delete .opblock-summary-method,.swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span:after{background:var(--opblock-delete-method-color,#f93e3e)}.swagger-ui .opblock.opblock-get,.swagger-ui .opblock.opblock-get .opblock-summary{border-color:var(--opblock-get-border-color,#61affe)}.swagger-ui .opblock.opblock-get{background:var(--opblock-get-background-color,rgba(97,175,254,.1))}.swagger-ui .opblock.opblock-get .opblock-summary-method,.swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span:after{background:var(--opblock-get-method-color,#61affe)}.swagger-ui .opblock.opblock-patch,.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:var(--opblock-patch-border-color,#50e3c2)}.swagger-ui .opblock.opblock-patch{background:var(--opblock-patch-background-color,rgba(80,227,194,.1))}.swagger-ui .opblock.opblock-patch .opblock-summary-method,.swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span:after{background:var(--opblock-patch-method-color,#50e3c2)}.swagger-ui .opblock.opblock-head,.swagger-ui .opblock.opblock-head .opblock-summary{border-color:var(--opblock-head-border-color,#9012fe)}.swagger-ui .opblock.opblock-head{background:var(--opblock-head-background-color,rgba(144,18,254,.1))}.swagger-ui .opblock.opblock-head .opblock-summary-method,.swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span:after{background:var(--opblock-head-method-color,#9012fe)}.swagger-ui .opblock.opblock-options,.swagger-ui .opblock.opblock-options .opblock-summary{border-color:var(--opblock-options-border-color,#0d5aa7)}.swagger-ui .opblock.opblock-options{background:var(--opblock-options-background-color,rgba(13,90,167,.1))}.swagger-ui .opblock.opblock-options .opblock-summary-method,.swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span:after{background:var(--opblock-options-method-color,#0d5aa7)}.swagger-ui .opblock.opblock-deprecated,.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:var(--opblock-deprecated-border-color,#ebebeb)}.swagger-ui .opblock.opblock-deprecated{background:var(--opblock-deprecated-background-color,hsla(0,0%,92%,.1))}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method,.swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span:after{background:var(--opblock-deprecated-method-color,#ebebeb)}.swagger-ui .tab li:first-of-type:after{background:var(--swagger-tabs-divider-color,rgba(0,0,0,.2))}.swagger-ui .response-col_links .response-undocumented,.swagger-ui .response-col_status .response-undocumented{color:var(--response-undocumented-color,#909090)}.swagger-ui .response-control-media-type--accept-controller select{border-color:var(--response-control-media-type-color,green)}.swagger-ui .response-control-media-type__accept-message{color:var(--response-control-media-type-color,green)}.swagger-ui .opblock-body pre.microlight{background:var(--opblock-pre-microlight-background-color,#333)!important;color:var(--opblock-pre-microlight-color,#fff)!important}.swagger-ui .download-contents{background:var(--download-contents-background-color,#7d8293);color:var(--download-contents-color,#fff)}.swagger-ui .copy-to-clipboard{background:var(--copy-to-clipboard-background-color,#7d8293)}.swagger-ui .copy-to-clipboard button{background:var(--copy-to-clipboard-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .filter .operation-filter-input{border:2px solid var(--filter-operation-filter-input-border-color,#d8dde7)}.swagger-ui .download-url-wrapper .failed,.swagger-ui .filter .failed{color:var(--filter-download-failed-color,red)}.swagger-ui .download-url-wrapper .loading,.swagger-ui .filter .loading{color:var(--filter-download-loading-color,#aaa)}.swagger-ui table.headers .header-example{color:var(--table-headers-example-color,#999)}.swagger-ui table thead tr td,.swagger-ui table thead tr th{border-bottom:1px solid var(--table-thead-border-bottom-color,rgba(59,65,81,.2));color:var(--swagger-main-color,#3b4151)}.swagger-ui .parameter__name.required span{color:var(--parameter-name-required-symbol-color,red)}.swagger-ui .parameter__name.required::after{color:var(--parameter-name-required-color,rgba(255,0,0,.6))}.swagger-ui .parameter__extension,.swagger-ui .parameter__in,.swagger-ui .response__extension{color:var(--parameter-in-extension-color,gray)}.swagger-ui .parameter__deprecated{color:var(--parameter-deprecated-color,red)}.swagger-ui .btn,.swagger-ui select,.swagger-ui textarea{color:var(--swagger-main-color,#3b4151)}.swagger-ui .btn{background:var(--button-background-color,transparent);border:2px solid var(--button-border-color,gray);box-shadow:0 1px 2px var(--button-shadow-color,rgba(0,0,0,.1))}.swagger-ui .btn:hover{box-shadow:0 0 5px var(--button-shadow-hover-color,rgba(0,0,0,.3))}.swagger-ui .btn.cancel{background-color:var(--button-cancel-background-color,transparent);border-color:var(--button-cancel-border-color,#ff6060);color:var(--button-cancel-color,#ff6060)}.swagger-ui .btn.execute{background-color:var(--button-execute-background-color,#4990e2);border-color:var(--button-execute-border-color,#4990e2);color:var(--button-execute-color,#fff)}.swagger-ui button.invalid{background:var(--button-invalid-background-color,#feebeb);border-color:var(--button-invalid-border-color,#f93e3e)}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{fill:var(--expand-operation-svg-arrow-color,#707070)}.swagger-ui .expand-methods:hover svg,.swagger-ui .expand-operation:hover svg{fill:var(--expand-operation-svg-arrow-hover-color,#404040)}.swagger-ui select{border:2px solid var(--select-border-color,#41444e);box-shadow:0 1px 2px 0 var(--select-shadow-color,rgba(0,0,0,.25));cursor:pointer}.swagger-ui select[multiple]{background:var(--select-background-color,#f7f7f7)}.swagger-ui select:disabled{opacity:.5;cursor:not-allowed}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text],.swagger-ui textarea{background:var(--input-background-color,#fff);border:1px solid var(--input-border-color,#d9d9d9);color:var(--swagger-main-color,#3b4151)}.swagger-ui input[type=email].invalid,.swagger-ui input[type=file].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid,.swagger-ui select.invalid,.swagger-ui textarea.invalid{background:var(--input-invalid-background-color,#feebeb);border-color:var(--input-invalid-border-color,#f93e3e);color:var(--input-invalid-color,#3b4151)}.swagger-ui input[disabled],.swagger-ui select[disabled]{background-color:var(--input-disabled-background-color,#fafafa);color:var(--input-disabled-color,#888)}.swagger-ui select[disabled]{border-color:var(--select-disabled-border-color,#888)}.swagger-ui textarea[disabled]{background-color:var(--textarea-disabled-background-color,#41444e);color:var(--textarea-disabled-color,#fff)}.swagger-ui textarea{background:var(--textarea-background-color,hsla(0,0%,100%,.8))}.swagger-ui textarea:focus{border:2px solid var(--textarea-border-focus-color,#61affe)}.swagger-ui textarea.curl{background:var(--textarea-curl-background-color,#41444e);color:var(--textarea-curl-color,#fff)}.swagger-ui .checkbox{color:var(--checkbox-color,#303030)}.swagger-ui .checkbox input[type=checkbox]+label>.item{background:var(--checkbox-label-item-background-color,#e8e8e8);box-shadow:0 0 0 2px var(--checkbox-label-item-shadow-color,#e8e8e8)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:var(--checkbox-label-item-icon, #e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .dialog-ux .backdrop-ux{background:var(--dialog-backdrop-color,rgba(0,0,0,.8))}.swagger-ui .dialog-ux .modal-ux{background:var(--dialog-background-color,#fff);border:1px solid var(--dialog-border-color,#ebebeb);box-shadow:0 10px 30px 0 var(--dialog-shadow-color,rgba(0,0,0,.2))}.swagger-ui .dialog-ux .modal-ux-header{border-bottom:1px solid var(--dialog-border-color,#ebebeb)}.swagger-ui .dialog-ux .modal-ux-header button svg{fill:var(--dialog-close-button-icon-color,black)}.swagger-ui .model .deprecated span,.swagger-ui .model .deprecated td{color:var(--model-deprecated-color,#a0a0a0)!important}.swagger-ui .model-toggle{cursor:pointer;-webkit-transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}.swagger-ui .model-toggle.collapsed{-webkit-transform:rotate(0);transform:rotate(0)}.swagger-ui .model-toggle::after{background:var(--model-arrow-icon, url('data:image/svg+xml;charset=utf-8,') 50% no-repeat)}.swagger-ui .model-hint{background:var(--model-hint-background-color,rgba(0,0,0,.7));color:var(--model-hint-color,#ebebeb)}.swagger-ui .model .property{color:var(--model-property-color,#999)}.swagger-ui .model .property.primitive{color:var(--model-property-primitive-color,#6b6b6b)}.swagger-ui table.model tr.property-row .star{color:var(--model-property-required-symbol-color,red)}.swagger-ui .model .external-docs,.swagger-ui table.model tr.description{color:var(--model-property-description,#666)}.swagger-ui table.model tr.extension{color:var(--model-property-extension,#777)}.swagger-ui section.models{border:1px solid var(--model-section-border-color,rgba(59,65,81,.3));border-radius:.5rem;padding:0;margin:0}.swagger-ui section.models.is-open h4{border-bottom:1px solid var(--model-section-border-color,rgba(59,65,81,.3))}.swagger-ui section.models h4{color:var(--model-section-header-color,#606060);padding:0}.swagger-ui section.models h4:hover{background:var(--model-section-header-hover-background-color,rgba(0,0,0,.02))}.swagger-ui section.models h4 svg{fill:var(--expand-model-svg-arrow-color,#707070)}.swagger-ui section.models h4 button.models-control{color:var(--model-section-header-color,#606060);padding:20px}.swagger-ui section.models h5{color:var(--model-section-little-header-color,#707070)}.swagger-ui section.models .model-container{background:var(--model-container-background-color,rgba(0,0,0,.05))}.swagger-ui section.models .model-container:hover{background:var(--model-container-hover-background-color,rgba(0,0,0,.07))}.swagger-ui .model-box{background:var(--model-box-background-color,rgba(0,0,0,.1))}.swagger-ui .model-title{color:var(--model-title-color,#505050)}.swagger-ui .model-deprecated-warning{color:var(--model-deprecated-warning-color,#f93e3e)}.swagger-ui .prop-type{color:var(--model-prop-type-color,#55a)}.swagger-ui .prop-format{color:var(--model-prop-format-color,#606060)}.swagger-ui .markdown pre,.swagger-ui .renderedMarkdown pre{color:var(--rendered-markdown-pre-color,#000)}.swagger-ui .markdown code,.swagger-ui .renderedMarkdown code{background:var(--rendered-markdown-code-background-color,rgba(0,0,0,.05));color:var(--rendered-markdown-code-color,#9012fe)}.scroll-to-top-wrapper{position:fixed;bottom:0;left:0;right:0;text-align:end;box-sizing:border-box;margin:0 auto;max-width:1460px;padding:0 20px;width:100%;pointer-events:none}#scroll-to-top-btn{background-color:var(--scroll-to-top-button-background-color,#000000d1);border:1px solid var(--scroll-to-top-button-border-color,#d1c9c9);border-radius:50%;color:#fff;cursor:pointer;width:2.5rem;aspect-ratio:1;backdrop-filter:blur(20px);margin:10px;position:relative;transition:background-color .5s,opacity .6s .2s,translate .8s,scale .2s;opacity:0;translate:0 100px;box-shadow:0 0 10px 0 var(--scroll-to-top-button-shadow-color,rgba(0,0,0,.4))}#scroll-to-top-btn.showBtn{opacity:1;translate:0 0;pointer-events:auto}#scroll-to-top-btn::before{position:absolute;content:'';top:10px;left:12px;right:12px;background-color:var(--scroll-to-top-button-icon-color,#fff);border-radius:1rem;height:2px}#scroll-to-top-btn::after{position:absolute;content:'';top:15px;left:50%;border:solid var(--scroll-to-top-button-icon-color,#fff);border-width:0 2px 2px 0;padding:3px;transform:rotate(-135deg);translate:-50% 4px}#scroll-to-top-btn:focus-within,#scroll-to-top-btn:hover{background-color:var(--scroll-to-top-button-hover-background-color,#0d0d0dba)}#scroll-to-top-btn:focus-within::after,#scroll-to-top-btn:hover::after{animation:1.2s ease-in infinite bounceUp}@keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-moz-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@-webkit-keyframes bounceUp{100%{translate:-50% 4px}25%{translate:-50% 6px}60%{translate:-50% -2px}}@media only screen and (max-width:680px){.swagger-ui .opblock .opblock-summary-description,.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{word-break:break-all}}
\ No newline at end of file
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/dark.min.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/dark.min.css
index 0d45e7f..9d48b52 100644
--- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/dark.min.css
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/dark.min.css
@@ -1 +1 @@
-/*Dark Theme*/@media (prefers-color-scheme:dark){:root{--scrollbar-thumb-color:hsl(0, 0%, 45%);--scrollbar-thumb-hover-color:hsl(0, 0%, 55%);--body-background-color:#101010;--swagger-main-color:#ecf0f1;--loading-container-border-color:rgba(200, 200, 200, .1);--loading-container-border-top-color:rgba(255, 255, 255, .6);--errors-wrapper-background-color:rgb(255, 0, 0, .1);--errors-wrapper-border-color:#f93e3e;--errors-wrapper-errors-color:#606060;--topbar-background-color:black;--topbar-pinned-background-color:rgba(0, 0, 0, .3);--topbar-pinned-shadow-color:rgba(0, 0, 0, .6);--topbar-pin-icon-color:white;--topbar-border-color:gray;--topbar-select-border-color:#5b5b5b;--topbar-select-label-color:#dfd9d9;--topbar-download-url-button-background-color:#5b5b5b;--topbar-download-url-button-color:#dfd9d9;--swagger-info-link:#4990e2;--swagger-info-link-hover:#1b78e5;--api-version-background-color:#616161;--api-version-stamp-background-color:#23891f;--api-version-color:#cdcdcd;--auth-wrapper-background-color:black;--auth-wrapper-border-color:#383636;--auth-wrapper-box-shadow-color:#c1c1c1;--auth-container-border-bottom-color:#ebebeb;--auth-container-background-color:#ddd;--auth-container-errors-color:red;--btn-authorize-background-color:transparent;--btn-authorize-border-color:#49cc90;--btn-authorize-font-color:#49cc90;--btn-authorize-svg-fill-color:#49cc90;--opblock-tag-section-border-color:#312c2c;--opblock-border-color:black;--opblock-tag-section-expandcollapse-background-color:rgb(0 0 0 / 45%);--opblock-tag-background-color-hover:rgba(0, 0, 0, .1);--opblock-tag-pinned-background-color-hover:#0e0e0e;--opblock-tag-border-bottom-color:rgba(109, 113, 121, .3);--opblock-shadow-color:rgba(59, 59, 59, .19);--opblock-tabheader-underline-color:rgba(255, 255, 255, .6);--opblock-summary-svg-icons-color:white;--opblock-summary-border-bottom-color:black;--opblock-section-header-background-color:#0000004a;--opblock-section-header-shadow-color:rgba(0, 0, 0, .1);--opblock-summary-method-background-color:black;--opblock-summary-method-color:white;--opblock-summary-method-shadow-color:rgba(0, 0, 0, .1);--opblock-post-background-color:rgba(56, 203, 82, .1);--opblock-post-method-color:#49cc90;--opblock-post-border-color:#49cc90;--opblock-put-background-color:rgba(255, 189, 63, .1);--opblock-put-method-color:#eb9532;--opblock-put-border-color:#eb9532;--opblock-delete-background-color:rgba(255, 4, 4, .1);--opblock-delete-method-color:#c0392b;--opblock-delete-border-color:#c0392b;--opblock-get-background-color:rgb(33, 161, 255, .1);--opblock-get-method-color:#61affe;--opblock-get-border-color:#61affe;--opblock-patch-background-color:rgba(255, 0, 165, .1);--opblock-patch-method-color:#9b59b6;--opblock-patch-border-color:#9b59b6;--opblock-head-background-color:rgba(62, 73, 114, .1);--opblock-head-method-color:#3e4972;--opblock-head-border-color:#3e4972;--opblock-options-background-color:rgba(37, 64, 211, .1);--opblock-options-method-color:#263795;--opblock-options-border-color:#263795;--opblock-deprecated-background-color:hsla(0, 0%, 87%, .1);--opblock-deprecated-method-color:#a59595;--opblock-deprecated-border-color:#a59595;--swagger-tabs-divider-color:gray;--response-undocumented-color:#999;--response-control-media-type-color:green;--opblock-pre-microlight-background-color:#333;--opblock-pre-microlight-color:#ebe3e3;--download-contents-background-color:#7d8293;--download-contents-color:white;--copy-to-clipboard-background-color:#7d8293;--copy-to-clipboard-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--filter-operation-filter-input-border-color:#d8dde7;--filter-download-failed-color:red;--filter-download-loading-color:#aaa;--table-headers-example-color:#999;--table-thead-border-bottom-color:rgba(59, 65, 81, .2);--parameter-name-required-symbol-color:red;--parameter-name-required-color:rgba(255, 0, 0, .8);--parameter-in-extension-color:#878585;--parameter-deprecated-color:red;--button-background-color:transparent;--button-border-color:#a1a1a1;--button-shadow-color:rgba(0, 0, 0, .1);--button-shadow-hover-color:rgba(0, 0, 0, .3);--button-cancel-background-color:transparent;--button-cancel-border-color:#ff6060;--button-cancel-color:#ff6060;--button-execute-background-color:#4990e2;--button-execute-border-color:#5fa9ff;--button-execute-color:white;--button-invalid-background-color:#feebeb;--button-invalid-border-color:#f93e3e;--expand-operation-svg-arrow-color:#aaa;--expand-operation-svg-arrow-hover-color:white;--select-background-icon:#0c0c0c url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat;--select-border-color:#41444e;--select-shadow-color:rgba(0, 0, 0, .25);--input-background-color:#0c0c0c;--input-border-color:#6d6d6d;--input-invalid-background-color:#210101;--input-invalid-border-color:#f93e3e;--input-invalid-color:#f3ebeb;--input-disabled-background-color:#333333;--input-disabled-color:#757575;--select-disabled-border-color:#888;--textarea-disabled-background-color:#2b2b2b;--textarea-disabled-color:#b3b3b3;--textarea-background-color:hsla(0, 0%, 0%, .8);--textarea-border-focus-color:#a5a5a5;--textarea-curl-background-color:#41444e;--textarea-curl-color:#fff;--checkbox-color:#303030;--checkbox-label-item-background-color:#e8e8e8;--checkbox-label-item-shadow-color:#e8e8e8;--checkbox-label-item-icon:#e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--dialog-backdrop-color:rgba(0, 0, 0, .8);--dialog-background-color:#161616;--dialog-border-color:#333436;--dialog-shadow-color:rgba(0, 0, 0, .2);--dialog-close-button-icon-color:#fff;--model-deprecated-color:#a0a0a0;--model-arrow-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--model-hint-background-color:rgba(0, 0, 0, .7);--model-hint-color:#ebebeb;--model-property-color:#999;--model-property-primitive-color:#676767;--model-property-required-symbol-color:red;--model-property-description:#b9b9b9;--model-property-extension:#8d8a8a;--model-section-border-color:rgba(96, 96, 96, .3);--model-section-header-color:white;--model-section-header-hover-background-color:rgba(0, 0, 0, .02);--expand-model-svg-arrow-color:white;--model-section-little-header-color:#707070;--model-container-background-color:rgba(20, 20, 20, .1);--model-container-hover-background-color:rgba(0, 0, 0, .1);--model-box-background-color:rgba(0, 0, 0, .15);--model-title-color:#bec6cf;--model-deprecated-warning-color:#f93e3e;--model-prop-type-color:#6464db;--model-prop-format-color:#b7b7b7;--rendered-markdown-pre-color:#000;--rendered-markdown-code-background-color:rgba(0, 0, 0, .05);--rendered-markdown-code-color:#9012fe;--scroll-to-top-button-background-color:#00000085;--scroll-to-top-button-hover-background-color:#0d0d0d85;--scroll-to-top-button-border-color:#232323;--scroll-to-top-button-icon-color:#d1cccc;--scroll-to-top-button-shadow-color:rgba(0, 0, 0, .3)}}
\ No newline at end of file
+/*Dark Theme https://github.com/teociaps/SwaggerUI.Themes */@media (prefers-color-scheme:dark){:root{--scrollbar-thumb-color:hsl(0, 0%, 45%);--scrollbar-thumb-hover-color:hsl(0, 0%, 55%);--body-background-color:#101010;--swagger-main-color:#ecf0f1;--loading-container-border-color:rgba(200, 200, 200, .1);--loading-container-border-top-color:rgba(255, 255, 255, .6);--errors-wrapper-background-color:rgb(255, 0, 0, .1);--errors-wrapper-border-color:#f93e3e;--errors-wrapper-errors-color:#606060;--topbar-background-color:black;--topbar-pinned-background-color:rgba(0, 0, 0, .3);--topbar-pinned-shadow-color:rgba(0, 0, 0, .6);--topbar-pin-icon-color:white;--topbar-border-color:gray;--topbar-select-border-color:#5b5b5b;--topbar-select-label-color:#dfd9d9;--topbar-download-url-button-background-color:#5b5b5b;--topbar-download-url-button-color:#dfd9d9;--swagger-info-link:#4990e2;--swagger-info-link-hover:#1b78e5;--api-version-background-color:#616161;--api-version-stamp-background-color:#23891f;--api-version-color:#cdcdcd;--auth-wrapper-background-color:black;--auth-wrapper-border-color:#383636;--auth-wrapper-box-shadow-color:#c1c1c1;--auth-container-border-bottom-color:#ebebeb;--auth-container-background-color:#ddd;--auth-container-errors-color:red;--btn-authorize-background-color:transparent;--btn-authorize-border-color:#49cc90;--btn-authorize-font-color:#49cc90;--btn-authorize-svg-fill-color:#49cc90;--opblock-tag-section-border-color:#312c2c;--opblock-border-color:black;--opblock-tag-section-expandcollapse-background-color:rgb(0 0 0 / 45%);--opblock-tag-background-color-hover:rgba(0, 0, 0, .1);--opblock-tag-pinned-background-color-hover:#0e0e0e;--opblock-tag-border-bottom-color:rgba(109, 113, 121, .3);--opblock-shadow-color:rgba(59, 59, 59, .19);--opblock-tabheader-underline-color:rgba(255, 255, 255, .6);--opblock-summary-svg-icons-color:white;--opblock-summary-border-bottom-color:black;--opblock-section-header-background-color:#0000004a;--opblock-section-header-shadow-color:rgba(0, 0, 0, .1);--opblock-summary-method-background-color:black;--opblock-summary-method-color:white;--opblock-summary-method-shadow-color:rgba(0, 0, 0, .1);--opblock-post-background-color:rgba(56, 203, 82, .1);--opblock-post-method-color:#49cc90;--opblock-post-border-color:#49cc90;--opblock-put-background-color:rgba(255, 189, 63, .1);--opblock-put-method-color:#eb9532;--opblock-put-border-color:#eb9532;--opblock-delete-background-color:rgba(255, 4, 4, .1);--opblock-delete-method-color:#c0392b;--opblock-delete-border-color:#c0392b;--opblock-get-background-color:rgb(33, 161, 255, .1);--opblock-get-method-color:#61affe;--opblock-get-border-color:#61affe;--opblock-patch-background-color:rgba(255, 0, 165, .1);--opblock-patch-method-color:#9b59b6;--opblock-patch-border-color:#9b59b6;--opblock-head-background-color:rgba(62, 73, 114, .1);--opblock-head-method-color:#3e4972;--opblock-head-border-color:#3e4972;--opblock-options-background-color:rgba(37, 64, 211, .1);--opblock-options-method-color:#263795;--opblock-options-border-color:#263795;--opblock-deprecated-background-color:hsla(0, 0%, 87%, .1);--opblock-deprecated-method-color:#a59595;--opblock-deprecated-border-color:#a59595;--swagger-tabs-divider-color:gray;--response-undocumented-color:#999;--response-control-media-type-color:green;--opblock-pre-microlight-background-color:#333;--opblock-pre-microlight-color:#ebe3e3;--download-contents-background-color:#7d8293;--download-contents-color:white;--copy-to-clipboard-background-color:#7d8293;--copy-to-clipboard-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--filter-operation-filter-input-border-color:#d8dde7;--filter-download-failed-color:red;--filter-download-loading-color:#aaa;--table-headers-example-color:#999;--table-thead-border-bottom-color:rgba(59, 65, 81, .2);--parameter-name-required-symbol-color:red;--parameter-name-required-color:rgba(255, 0, 0, .8);--parameter-in-extension-color:#878585;--parameter-deprecated-color:red;--button-background-color:transparent;--button-border-color:#a1a1a1;--button-shadow-color:rgba(0, 0, 0, .1);--button-shadow-hover-color:rgba(0, 0, 0, .3);--button-cancel-background-color:transparent;--button-cancel-border-color:#ff6060;--button-cancel-color:#ff6060;--button-execute-background-color:#4990e2;--button-execute-border-color:#5fa9ff;--button-execute-color:white;--button-invalid-background-color:#feebeb;--button-invalid-border-color:#f93e3e;--expand-operation-svg-arrow-color:#aaa;--expand-operation-svg-arrow-hover-color:white;--select-background-icon:#0c0c0c url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat;--select-border-color:#41444e;--select-shadow-color:rgba(0, 0, 0, .25);--input-background-color:#0c0c0c;--input-border-color:#6d6d6d;--input-invalid-background-color:#210101;--input-invalid-border-color:#f93e3e;--input-invalid-color:#f3ebeb;--input-disabled-background-color:#333333;--input-disabled-color:#757575;--select-disabled-border-color:#888;--textarea-disabled-background-color:#2b2b2b;--textarea-disabled-color:#b3b3b3;--textarea-background-color:hsla(0, 0%, 0%, .8);--textarea-border-focus-color:#a5a5a5;--textarea-curl-background-color:#41444e;--textarea-curl-color:#fff;--checkbox-color:#303030;--checkbox-label-item-background-color:#e8e8e8;--checkbox-label-item-shadow-color:#e8e8e8;--checkbox-label-item-icon:#e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--dialog-backdrop-color:rgba(0, 0, 0, .8);--dialog-background-color:#161616;--dialog-border-color:#333436;--dialog-shadow-color:rgba(0, 0, 0, .2);--dialog-close-button-icon-color:#fff;--model-deprecated-color:#a0a0a0;--model-arrow-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--model-hint-background-color:rgba(0, 0, 0, .7);--model-hint-color:#ebebeb;--model-property-color:#999;--model-property-primitive-color:#676767;--model-property-required-symbol-color:red;--model-property-description:#b9b9b9;--model-property-extension:#8d8a8a;--model-section-border-color:rgba(96, 96, 96, .3);--model-section-header-color:white;--model-section-header-hover-background-color:rgba(0, 0, 0, .02);--expand-model-svg-arrow-color:white;--model-section-little-header-color:#707070;--model-container-background-color:rgba(20, 20, 20, .1);--model-container-hover-background-color:rgba(0, 0, 0, .1);--model-box-background-color:rgba(0, 0, 0, .15);--model-title-color:#bec6cf;--model-deprecated-warning-color:#f93e3e;--model-prop-type-color:#6464db;--model-prop-format-color:#b7b7b7;--rendered-markdown-pre-color:#000;--rendered-markdown-code-background-color:rgba(0, 0, 0, .05);--rendered-markdown-code-color:#9012fe;--scroll-to-top-button-background-color:#00000085;--scroll-to-top-button-hover-background-color:#0d0d0d85;--scroll-to-top-button-border-color:#232323;--scroll-to-top-button-icon-color:#d1cccc;--scroll-to-top-button-shadow-color:rgba(0, 0, 0, .3)}}
\ No newline at end of file
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/deepsea.min.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/deepsea.min.css
index b4a4a67..0b0599a 100644
--- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/deepsea.min.css
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/deepsea.min.css
@@ -1 +1 @@
-/*DeepSea Theme*/:root{--scrollbar-thumb-color:hsl(207, 100%, 23%);--scrollbar-thumb-hover-color:hsl(207, 100%, 30%);--body-background-color:#0a1229;--swagger-main-color:#fff;--loading-container-border-color:rgba(200, 200, 200, .1);--loading-container-border-top-color:rgba(255, 255, 255, .6);--errors-wrapper-background-color:rgb(255, 0, 0, .5);--errors-wrapper-border-color:#f95c5c;--errors-wrapper-errors-color:#bdbdbd;--topbar-background-color:#010225;--topbar-pinned-background-color:rgb(0, 0, 30, .5);--topbar-pinned-shadow-color:rgba(0, 0, 16, .5);--topbar-pin-icon-color:white;--topbar-border-color:#008ab3;--topbar-select-border-color:#007dd5;--topbar-select-label-color:#f1f1f1;--topbar-download-url-button-background-color:#165d9a;--topbar-download-url-button-color:#f1f1f1;--swagger-info-link:#99a5ff;--swagger-info-link-hover:#3484d5;--api-version-background-color:#d15647;--api-version-stamp-background-color:#44c0fb;--api-version-color:white;--auth-wrapper-background-color:#1d6968;--auth-wrapper-border-color:#60ffdc;--auth-wrapper-box-shadow-color:rgba(0, 0, 0, .3);--auth-container-border-bottom-color:#171725;--auth-container-background-color:#1c366f;--auth-container-errors-color:red;--btn-authorize-background-color:#1c2541;--btn-authorize-border-color:#45e1d1;--btn-authorize-font-color:#fbffff;--btn-authorize-svg-fill-color:#fbffff;--opblock-tag-section-border-color:#065687;--opblock-border-color:white;--opblock-shadow-color:rgba(0, 0, 0, .2);--opblock-tag-section-expandcollapse-background-color:rgb(0 0 0 / 45%);--opblock-tag-background-color-hover:rgba(0, 0, 0, .05);--opblock-tag-pinned-background-color-hover:#091024;--opblock-tag-border-bottom-color:#065687c9;--opblock-tabheader-underline-color:rgba(255, 255, 255, .6);--opblock-summary-svg-icons-color:white;--opblock-summary-border-bottom-color:white;--opblock-section-header-background-color:#0000004a;--opblock-section-header-shadow-color:rgba(0, 0, 0, .1);--opblock-summary-method-background-color:#3b2e1e;--opblock-summary-method-color:#fff;--opblock-summary-method-shadow-color:rgba(0, 0, 0, .1);--opblock-post-background-color:rgba(33, 161, 255, .1);--opblock-post-method-color:#61affe;--opblock-post-border-color:#61affe;--opblock-put-background-color:rgba(0, 73, 133, .2);--opblock-put-method-color:#004985;--opblock-put-border-color:#004985;--opblock-delete-background-color:rgba(20, 41, 111, .14);--opblock-delete-method-color:#14296f;--opblock-delete-border-color:#14296f;--opblock-get-background-color:rgba(0, 127, 110, .2);--opblock-get-method-color:#007f6e;--opblock-get-border-color:#007f6e;--opblock-patch-background-color:rgba(255, 0, 165, .1);--opblock-patch-method-color:#9b59b6;--opblock-patch-border-color:#9b59b6;--opblock-head-background-color:rgba(43, 55, 99, .36);--opblock-head-method-color:#3e4972;--opblock-head-border-color:#3e4972;--opblock-options-background-color:rgba(129, 0, 100, .3);--opblock-options-method-color:#810064;--opblock-options-border-color:#810064;--opblock-deprecated-background-color:hsla(0, 0%, 100%, .14);--opblock-deprecated-method-color:#a59595;--opblock-deprecated-border-color:#a59595;--swagger-tabs-divider-color:gray;--response-undocumented-color:#b0b0b0;--response-control-media-type-color:#00ffe8;--opblock-pre-microlight-background-color:#1b2847;--opblock-pre-microlight-color:#ebe3e3;--download-contents-background-color:#0e0d2e;--download-contents-color:white;--copy-to-clipboard-background-color:#0e0d2e;--copy-to-clipboard-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--filter-operation-filter-input-border-color:#d8dde7;--filter-download-failed-color:red;--filter-download-loading-color:#c1c1c1;--table-headers-example-color:#c4c4c4;--table-thead-border-bottom-color:rgba(0, 0, 0, .20);--parameter-name-required-symbol-color:red;--parameter-name-required-color:rgba(255, 0, 0, .8);--parameter-in-extension-color:#a8a8a8;--parameter-deprecated-color:red;--button-background-color:transparent;--button-border-color:#1f81ff;--button-shadow-color:rgba(0, 0, 0, .1);--button-shadow-hover-color:rgba(0, 0, 0, .3);--button-cancel-background-color:transparent;--button-cancel-border-color:#ff6060;--button-cancel-color:#ff6060;--button-execute-background-color:#16589f;--button-execute-border-color:#5f9eff;--button-execute-color:white;--button-invalid-background-color:#feebeb;--button-invalid-border-color:#f93e3e;--expand-operation-svg-arrow-color:#dbdbdb;--expand-operation-svg-arrow-hover-color:white;--select-background-icon:#0e0d2edc url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat;--select-border-color:#949cf5;--select-shadow-color:rgba(0, 0, 0, .25);--input-background-color:#0e0d2edc;--input-border-color:#949cf5;--input-invalid-background-color:#630000;--input-invalid-border-color:#f93e3e;--input-invalid-color:#f3ebeb;--input-disabled-background-color:#1b2847;--input-disabled-color:#ebe3e3;--select-disabled-border-color:#171122;--textarea-disabled-background-color:#1b2847;--textarea-disabled-color:#ebe3e3;--textarea-background-color:#0e0d2edc;--textarea-border-focus-color:#d5d5d5;--textarea-curl-background-color:#94cef5;--textarea-curl-color:#fff;--checkbox-color:#151431;--checkbox-label-item-background-color:#165d9a;--checkbox-label-item-shadow-color:#165d9a;--checkbox-label-item-icon:#165d9a url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--dialog-backdrop-color:rgb(2, 0, 18, 0.80);--dialog-background-color:#081c3b;--dialog-border-color:#0e3779;--dialog-shadow-color:rgba(0, 0, 0, .2);--dialog-close-button-icon-color:#fff;--model-deprecated-color:#a0a0a0;--model-arrow-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--model-hint-background-color:rgba(0, 0, 0, .3);--model-hint-color:#ebebeb;--model-property-color:#b5b5b5;--model-property-primitive-color:#8b8b8b;--model-property-required-symbol-color:red;--model-property-description:#b9b9b9;--model-property-extension:#aba6a6;--model-section-border-color:rgba(56, 131, 223, .5);--model-section-header-color:white;--model-section-header-hover-background-color:rgba(0, 0, 0, .02);--expand-model-svg-arrow-color:white;--model-section-little-header-color:#8a8a8a;--model-container-background-color:rgba(20, 20, 20, .05);--model-container-hover-background-color:rgba(0, 0, 0, .1);--model-box-background-color:rgba(0, 0, 0, .15);--model-title-color:#dedede;--model-deprecated-warning-color:#f93e3e;--model-prop-type-color:#4ca3ff;--model-prop-format-color:#fff;--rendered-markdown-pre-color:#000;--rendered-markdown-code-background-color:rgba(0, 0, 0, .05);--rendered-markdown-code-color:#9012fe;--scroll-to-top-button-background-color:#02001ec2;--scroll-to-top-button-hover-background-color:#020d35b8;--scroll-to-top-button-border-color:#002648;--scroll-to-top-button-icon-color:#7becff;--scroll-to-top-button-shadow-color:rgba(8, 25, 114, .5)}
\ No newline at end of file
+/*DeepSea Theme https://github.com/teociaps/SwaggerUI.Themes */:root{--scrollbar-thumb-color:hsl(207, 100%, 23%);--scrollbar-thumb-hover-color:hsl(207, 100%, 30%);--body-background-color:#0a1229;--swagger-main-color:#fff;--loading-container-border-color:rgba(200, 200, 200, .1);--loading-container-border-top-color:rgba(255, 255, 255, .6);--errors-wrapper-background-color:rgb(255, 0, 0, .5);--errors-wrapper-border-color:#f95c5c;--errors-wrapper-errors-color:#bdbdbd;--topbar-background-color:#010225;--topbar-pinned-background-color:rgb(0, 0, 30, .5);--topbar-pinned-shadow-color:rgba(0, 0, 16, .5);--topbar-pin-icon-color:white;--topbar-border-color:#008ab3;--topbar-select-border-color:#007dd5;--topbar-select-label-color:#f1f1f1;--topbar-download-url-button-background-color:#165d9a;--topbar-download-url-button-color:#f1f1f1;--swagger-info-link:#99a5ff;--swagger-info-link-hover:#3484d5;--api-version-background-color:#d15647;--api-version-stamp-background-color:#44c0fb;--api-version-color:white;--auth-wrapper-background-color:#1d6968;--auth-wrapper-border-color:#60ffdc;--auth-wrapper-box-shadow-color:rgba(0, 0, 0, .3);--auth-container-border-bottom-color:#171725;--auth-container-background-color:#1c366f;--auth-container-errors-color:red;--btn-authorize-background-color:#1c2541;--btn-authorize-border-color:#45e1d1;--btn-authorize-font-color:#fbffff;--btn-authorize-svg-fill-color:#fbffff;--opblock-tag-section-border-color:#065687;--opblock-border-color:white;--opblock-shadow-color:rgba(0, 0, 0, .2);--opblock-tag-section-expandcollapse-background-color:rgb(0 0 0 / 45%);--opblock-tag-background-color-hover:rgba(0, 0, 0, .05);--opblock-tag-pinned-background-color-hover:#091024;--opblock-tag-border-bottom-color:#065687c9;--opblock-tabheader-underline-color:rgba(255, 255, 255, .6);--opblock-summary-svg-icons-color:white;--opblock-summary-border-bottom-color:white;--opblock-section-header-background-color:#0000004a;--opblock-section-header-shadow-color:rgba(0, 0, 0, .1);--opblock-summary-method-background-color:#3b2e1e;--opblock-summary-method-color:#fff;--opblock-summary-method-shadow-color:rgba(0, 0, 0, .1);--opblock-post-background-color:rgba(33, 161, 255, .1);--opblock-post-method-color:#61affe;--opblock-post-border-color:#61affe;--opblock-put-background-color:rgba(0, 73, 133, .2);--opblock-put-method-color:#004985;--opblock-put-border-color:#004985;--opblock-delete-background-color:rgba(20, 41, 111, .14);--opblock-delete-method-color:#14296f;--opblock-delete-border-color:#14296f;--opblock-get-background-color:rgba(0, 127, 110, .2);--opblock-get-method-color:#007f6e;--opblock-get-border-color:#007f6e;--opblock-patch-background-color:rgba(255, 0, 165, .1);--opblock-patch-method-color:#9b59b6;--opblock-patch-border-color:#9b59b6;--opblock-head-background-color:rgba(43, 55, 99, .36);--opblock-head-method-color:#3e4972;--opblock-head-border-color:#3e4972;--opblock-options-background-color:rgba(129, 0, 100, .3);--opblock-options-method-color:#810064;--opblock-options-border-color:#810064;--opblock-deprecated-background-color:hsla(0, 0%, 100%, .14);--opblock-deprecated-method-color:#a59595;--opblock-deprecated-border-color:#a59595;--swagger-tabs-divider-color:gray;--response-undocumented-color:#b0b0b0;--response-control-media-type-color:#00ffe8;--opblock-pre-microlight-background-color:#1b2847;--opblock-pre-microlight-color:#ebe3e3;--download-contents-background-color:#0e0d2e;--download-contents-color:white;--copy-to-clipboard-background-color:#0e0d2e;--copy-to-clipboard-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--filter-operation-filter-input-border-color:#d8dde7;--filter-download-failed-color:red;--filter-download-loading-color:#c1c1c1;--table-headers-example-color:#c4c4c4;--table-thead-border-bottom-color:rgba(0, 0, 0, .20);--parameter-name-required-symbol-color:red;--parameter-name-required-color:rgba(255, 0, 0, .8);--parameter-in-extension-color:#a8a8a8;--parameter-deprecated-color:red;--button-background-color:transparent;--button-border-color:#1f81ff;--button-shadow-color:rgba(0, 0, 0, .1);--button-shadow-hover-color:rgba(0, 0, 0, .3);--button-cancel-background-color:transparent;--button-cancel-border-color:#ff6060;--button-cancel-color:#ff6060;--button-execute-background-color:#16589f;--button-execute-border-color:#5f9eff;--button-execute-color:white;--button-invalid-background-color:#feebeb;--button-invalid-border-color:#f93e3e;--expand-operation-svg-arrow-color:#dbdbdb;--expand-operation-svg-arrow-hover-color:white;--select-background-icon:#0e0d2edc url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat;--select-border-color:#949cf5;--select-shadow-color:rgba(0, 0, 0, .25);--input-background-color:#0e0d2edc;--input-border-color:#949cf5;--input-invalid-background-color:#630000;--input-invalid-border-color:#f93e3e;--input-invalid-color:#f3ebeb;--input-disabled-background-color:#1b2847;--input-disabled-color:#ebe3e3;--select-disabled-border-color:#171122;--textarea-disabled-background-color:#1b2847;--textarea-disabled-color:#ebe3e3;--textarea-background-color:#0e0d2edc;--textarea-border-focus-color:#d5d5d5;--textarea-curl-background-color:#94cef5;--textarea-curl-color:#fff;--checkbox-color:#151431;--checkbox-label-item-background-color:#165d9a;--checkbox-label-item-shadow-color:#165d9a;--checkbox-label-item-icon:#165d9a url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--dialog-backdrop-color:rgb(2, 0, 18, 0.80);--dialog-background-color:#081c3b;--dialog-border-color:#0e3779;--dialog-shadow-color:rgba(0, 0, 0, .2);--dialog-close-button-icon-color:#fff;--model-deprecated-color:#a0a0a0;--model-arrow-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--model-hint-background-color:rgba(0, 0, 0, .3);--model-hint-color:#ebebeb;--model-property-color:#b5b5b5;--model-property-primitive-color:#8b8b8b;--model-property-required-symbol-color:red;--model-property-description:#b9b9b9;--model-property-extension:#aba6a6;--model-section-border-color:rgba(56, 131, 223, .5);--model-section-header-color:white;--model-section-header-hover-background-color:rgba(0, 0, 0, .02);--expand-model-svg-arrow-color:white;--model-section-little-header-color:#8a8a8a;--model-container-background-color:rgba(20, 20, 20, .05);--model-container-hover-background-color:rgba(0, 0, 0, .1);--model-box-background-color:rgba(0, 0, 0, .15);--model-title-color:#dedede;--model-deprecated-warning-color:#f93e3e;--model-prop-type-color:#4ca3ff;--model-prop-format-color:#fff;--rendered-markdown-pre-color:#000;--rendered-markdown-code-background-color:rgba(0, 0, 0, .05);--rendered-markdown-code-color:#9012fe;--scroll-to-top-button-background-color:#02001ec2;--scroll-to-top-button-hover-background-color:#020d35b8;--scroll-to-top-button-border-color:#002648;--scroll-to-top-button-icon-color:#7becff;--scroll-to-top-button-shadow-color:rgba(8, 25, 114, .5)}
\ No newline at end of file
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/desert.min.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/desert.min.css
index 03f22c7..fa7f641 100644
--- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/desert.min.css
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/desert.min.css
@@ -1 +1 @@
-/*Desert Theme*/:root{--scrollbar-thumb-color:hsl(40, 40%, 30%);--scrollbar-thumb-hover-color:hsl(40, 40%, 35%);--body-background-color:#c58c59;--swagger-main-color:#ffffff;--loading-container-border-color:rgba(200, 200, 200, .1);--loading-container-border-top-color:rgba(255, 255, 255, .6);--errors-wrapper-background-color:rgb(255, 0, 0, .2);--errors-wrapper-border-color:#f93e3e;--errors-wrapper-errors-color:#3f1616;--topbar-background-color:#573420;--topbar-pinned-background-color:rgba(0, 0, 0, .5);--topbar-pinned-shadow-color:rgba(0, 0, 0, .6);--topbar-pin-icon-color:white;--topbar-border-color:#60360b;--topbar-select-border-color:#8F5933;--topbar-select-label-color:#ffeec8;--topbar-download-url-button-background-color:#774109;--topbar-download-url-button-color:#dfd9d9;--swagger-info-link:#cedaff;--swagger-info-link-hover:#a4d5ff;--api-version-background-color:#85ce34;--api-version-stamp-background-color:#a96105;--api-version-color:#ffffff;--auth-wrapper-background-color:#cd753d;--auth-wrapper-border-color:#b25f2a;--auth-wrapper-box-shadow-color:#6b48178c;--auth-container-border-bottom-color:#ebebeb;--auth-container-background-color:#ddd;--auth-container-errors-color:red;--btn-authorize-background-color:transparent;--btn-authorize-border-color:#a0e249;--btn-authorize-font-color:#caff85;--btn-authorize-svg-fill-color:#caff85;--opblock-tag-section-border-color:#92491a;--opblock-border-color:black;--opblock-tag-section-expandcollapse-background-color:rgb(0 0 0 / 10%);--opblock-tag-background-color-hover:rgba(0, 0, 0, .1);--opblock-tag-pinned-background-color-hover:#bb8656;--opblock-tag-border-bottom-color:rgb(78, 38, 18, 0.30);--opblock-shadow-color:rgb(74, 25, 25, 0.19);--opblock-tabheader-underline-color:rgba(255, 255, 255, .6);--opblock-summary-svg-icons-color:white;--opblock-summary-border-bottom-color:black;--opblock-section-header-background-color:#0000004a;--opblock-section-header-shadow-color:rgba(0, 0, 0, .1);--opblock-summary-method-background-color:black;--opblock-summary-method-color:white;--opblock-summary-method-shadow-color:rgba(0, 0, 0, .1);--opblock-post-background-color:rgb(125 113 58 / 24%);--opblock-post-method-color:#7D713A;--opblock-post-border-color:#7D713A;--opblock-put-background-color:rgb(193 91 22 / 16%);--opblock-put-method-color:#c15b16;--opblock-put-border-color:#c15b16;--opblock-delete-background-color:rgb(157 66 26 / 17%);--opblock-delete-method-color:#9d421a;--opblock-delete-border-color:#9d421a;--opblock-get-background-color:rgb(84 150 182 / 18%);--opblock-get-method-color:#5496B6;--opblock-get-border-color:#5496B6;--opblock-patch-background-color:rgb(141 111 155 / 18%);--opblock-patch-method-color:#8D6F9B;--opblock-patch-border-color:#8D6F9B;--opblock-head-background-color:rgb(67 64 99 / 18%);--opblock-head-method-color:#434063;--opblock-head-border-color:#434063;--opblock-options-background-color:rgb(241 145 121 / 20%);--opblock-options-method-color:#F19179;--opblock-options-border-color:#F19179;--opblock-deprecated-background-color:hsla(0, 0%, 87%, .1);--opblock-deprecated-method-color:#a59595;--opblock-deprecated-border-color:#a59595;--swagger-tabs-divider-color:#d9d0d0;--response-undocumented-color:#ece2e2;--response-control-media-type-color:#5dff5d;--opblock-pre-microlight-background-color:#301d00;--opblock-pre-microlight-color:#ebe3e3;--download-contents-background-color:#896648;--download-contents-color:white;--copy-to-clipboard-background-color:#896648;--copy-to-clipboard-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--filter-operation-filter-input-border-color:#d8dde7;--filter-download-failed-color:red;--filter-download-loading-color:#aaa;--table-headers-example-color:#ece2e2;--table-thead-border-bottom-color:rgb(79, 52, 52, 0.20);--parameter-name-required-symbol-color:#590000;--parameter-name-required-color:rgb(53 0 0 / 80%);--parameter-in-extension-color:#dfd7d7;--parameter-deprecated-color:#610000;--button-background-color:transparent;--button-border-color:#ffdbaf;--button-shadow-color:rgba(0, 0, 0, .1);--button-shadow-hover-color:rgba(0, 0, 0, .3);--button-cancel-background-color:transparent;--button-cancel-border-color:#da4e4e;--button-cancel-color:#ffb8b8;--button-execute-background-color:#4990e2;--button-execute-border-color:#9fccff;--button-execute-color:white;--button-invalid-background-color:#feebeb;--button-invalid-border-color:#f93e3e;--expand-operation-svg-arrow-color:white;--expand-operation-svg-arrow-hover-color:white;--select-background-icon:#301d00 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat;--select-border-color:#8F5933;--select-shadow-color:rgba(0, 0, 0, .25);--input-background-color:#301d00;--input-border-color:#8F5933;--input-invalid-background-color:#6f0f0f;--input-invalid-border-color:#f93e3e;--input-invalid-color:#ffb3b3;--input-disabled-background-color:#572f25;--input-disabled-color:#b3b3b3;--select-disabled-border-color:#888;--textarea-disabled-background-color:#2b2b2b;--textarea-disabled-color:#b3b3b3;--textarea-background-color:hsla(0, 0%, 0%, .8);--textarea-border-focus-color:#b5794f;--textarea-curl-background-color:#301d00;--textarea-curl-color:#fff;--checkbox-color:#303030;--checkbox-label-item-background-color:#e8e8e8;--checkbox-label-item-shadow-color:#e8e8e8;--checkbox-label-item-icon:#e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--dialog-backdrop-color:rgb(20 0 0 / 80%);--dialog-background-color:#935F39;--dialog-border-color:#F2D58C;--dialog-shadow-color:rgba(0, 0, 0, .2);--dialog-close-button-icon-color:#fff;--model-deprecated-color:#e1c3c3;--model-arrow-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--model-hint-background-color:rgba(0, 0, 0, .7);--model-hint-color:#ebebeb;--model-property-color:#e9dede;--model-property-primitive-color:#fdebeb;--model-property-required-symbol-color:red;--model-property-description:#b9b9b9;--model-property-extension:#cdbebe;--model-section-border-color:rgb(67 1 1 / 30%);--model-section-header-color:white;--model-section-header-hover-background-color:rgba(0, 0, 0, .02);--expand-model-svg-arrow-color:white;--model-section-little-header-color:#e3d2d2;--model-container-background-color:rgba(20, 20, 20, .1);--model-container-hover-background-color:rgba(0, 0, 0, .1);--model-box-background-color:rgba(0, 0, 0, .15);--model-title-color:#ffffff;--model-deprecated-warning-color:#f93e3e;--model-prop-type-color:#fffa4d;--model-prop-format-color:#f9ffc5;--rendered-markdown-pre-color:#000;--rendered-markdown-code-background-color:rgba(0, 0, 0, .05);--rendered-markdown-code-color:#9012fe;--scroll-to-top-button-background-color:#2c120285;--scroll-to-top-button-hover-background-color:#3d210c85;--scroll-to-top-button-border-color:#60360b;--scroll-to-top-button-icon-color:#ffedbc;--scroll-to-top-button-shadow-color:rgba(0, 0, 0, .3)}
\ No newline at end of file
+/*Desert Theme https://github.com/teociaps/SwaggerUI.Themes */:root{--scrollbar-thumb-color:hsl(40, 40%, 30%);--scrollbar-thumb-hover-color:hsl(40, 40%, 35%);--body-background-color:#c58c59;--swagger-main-color:#ffffff;--loading-container-border-color:rgba(200, 200, 200, .1);--loading-container-border-top-color:rgba(255, 255, 255, .6);--errors-wrapper-background-color:rgb(255, 0, 0, .2);--errors-wrapper-border-color:#f93e3e;--errors-wrapper-errors-color:#3f1616;--topbar-background-color:#573420;--topbar-pinned-background-color:rgba(0, 0, 0, .5);--topbar-pinned-shadow-color:rgba(0, 0, 0, .6);--topbar-pin-icon-color:white;--topbar-border-color:#60360b;--topbar-select-border-color:#8F5933;--topbar-select-label-color:#ffeec8;--topbar-download-url-button-background-color:#774109;--topbar-download-url-button-color:#dfd9d9;--swagger-info-link:#cedaff;--swagger-info-link-hover:#a4d5ff;--api-version-background-color:#85ce34;--api-version-stamp-background-color:#a96105;--api-version-color:#ffffff;--auth-wrapper-background-color:#cd753d;--auth-wrapper-border-color:#b25f2a;--auth-wrapper-box-shadow-color:#6b48178c;--auth-container-border-bottom-color:#ebebeb;--auth-container-background-color:#ddd;--auth-container-errors-color:red;--btn-authorize-background-color:transparent;--btn-authorize-border-color:#a0e249;--btn-authorize-font-color:#caff85;--btn-authorize-svg-fill-color:#caff85;--opblock-tag-section-border-color:#92491a;--opblock-border-color:black;--opblock-tag-section-expandcollapse-background-color:rgb(0 0 0 / 10%);--opblock-tag-background-color-hover:rgba(0, 0, 0, .1);--opblock-tag-pinned-background-color-hover:#bb8656;--opblock-tag-border-bottom-color:rgb(78, 38, 18, 0.30);--opblock-shadow-color:rgb(74, 25, 25, 0.19);--opblock-tabheader-underline-color:rgba(255, 255, 255, .6);--opblock-summary-svg-icons-color:white;--opblock-summary-border-bottom-color:black;--opblock-section-header-background-color:#0000004a;--opblock-section-header-shadow-color:rgba(0, 0, 0, .1);--opblock-summary-method-background-color:black;--opblock-summary-method-color:white;--opblock-summary-method-shadow-color:rgba(0, 0, 0, .1);--opblock-post-background-color:rgb(125 113 58 / 24%);--opblock-post-method-color:#7D713A;--opblock-post-border-color:#7D713A;--opblock-put-background-color:rgb(193 91 22 / 16%);--opblock-put-method-color:#c15b16;--opblock-put-border-color:#c15b16;--opblock-delete-background-color:rgb(157 66 26 / 17%);--opblock-delete-method-color:#9d421a;--opblock-delete-border-color:#9d421a;--opblock-get-background-color:rgb(84 150 182 / 18%);--opblock-get-method-color:#5496B6;--opblock-get-border-color:#5496B6;--opblock-patch-background-color:rgb(141 111 155 / 18%);--opblock-patch-method-color:#8D6F9B;--opblock-patch-border-color:#8D6F9B;--opblock-head-background-color:rgb(67 64 99 / 18%);--opblock-head-method-color:#434063;--opblock-head-border-color:#434063;--opblock-options-background-color:rgb(241 145 121 / 20%);--opblock-options-method-color:#F19179;--opblock-options-border-color:#F19179;--opblock-deprecated-background-color:hsla(0, 0%, 87%, .1);--opblock-deprecated-method-color:#a59595;--opblock-deprecated-border-color:#a59595;--swagger-tabs-divider-color:#d9d0d0;--response-undocumented-color:#ece2e2;--response-control-media-type-color:#5dff5d;--opblock-pre-microlight-background-color:#301d00;--opblock-pre-microlight-color:#ebe3e3;--download-contents-background-color:#896648;--download-contents-color:white;--copy-to-clipboard-background-color:#896648;--copy-to-clipboard-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--filter-operation-filter-input-border-color:#d8dde7;--filter-download-failed-color:red;--filter-download-loading-color:#aaa;--table-headers-example-color:#ece2e2;--table-thead-border-bottom-color:rgb(79, 52, 52, 0.20);--parameter-name-required-symbol-color:#590000;--parameter-name-required-color:rgb(53 0 0 / 80%);--parameter-in-extension-color:#dfd7d7;--parameter-deprecated-color:#610000;--button-background-color:transparent;--button-border-color:#ffdbaf;--button-shadow-color:rgba(0, 0, 0, .1);--button-shadow-hover-color:rgba(0, 0, 0, .3);--button-cancel-background-color:transparent;--button-cancel-border-color:#da4e4e;--button-cancel-color:#ffb8b8;--button-execute-background-color:#4990e2;--button-execute-border-color:#9fccff;--button-execute-color:white;--button-invalid-background-color:#feebeb;--button-invalid-border-color:#f93e3e;--expand-operation-svg-arrow-color:white;--expand-operation-svg-arrow-hover-color:white;--select-background-icon:#301d00 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat;--select-border-color:#8F5933;--select-shadow-color:rgba(0, 0, 0, .25);--input-background-color:#301d00;--input-border-color:#8F5933;--input-invalid-background-color:#6f0f0f;--input-invalid-border-color:#f93e3e;--input-invalid-color:#ffb3b3;--input-disabled-background-color:#572f25;--input-disabled-color:#b3b3b3;--select-disabled-border-color:#888;--textarea-disabled-background-color:#2b2b2b;--textarea-disabled-color:#b3b3b3;--textarea-background-color:hsla(0, 0%, 0%, .8);--textarea-border-focus-color:#b5794f;--textarea-curl-background-color:#301d00;--textarea-curl-color:#fff;--checkbox-color:#303030;--checkbox-label-item-background-color:#e8e8e8;--checkbox-label-item-shadow-color:#e8e8e8;--checkbox-label-item-icon:#e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--dialog-backdrop-color:rgb(20 0 0 / 80%);--dialog-background-color:#935F39;--dialog-border-color:#F2D58C;--dialog-shadow-color:rgba(0, 0, 0, .2);--dialog-close-button-icon-color:#fff;--model-deprecated-color:#e1c3c3;--model-arrow-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--model-hint-background-color:rgba(0, 0, 0, .7);--model-hint-color:#ebebeb;--model-property-color:#e9dede;--model-property-primitive-color:#fdebeb;--model-property-required-symbol-color:red;--model-property-description:#b9b9b9;--model-property-extension:#cdbebe;--model-section-border-color:rgb(67 1 1 / 30%);--model-section-header-color:white;--model-section-header-hover-background-color:rgba(0, 0, 0, .02);--expand-model-svg-arrow-color:white;--model-section-little-header-color:#e3d2d2;--model-container-background-color:rgba(20, 20, 20, .1);--model-container-hover-background-color:rgba(0, 0, 0, .1);--model-box-background-color:rgba(0, 0, 0, .15);--model-title-color:#ffffff;--model-deprecated-warning-color:#f93e3e;--model-prop-type-color:#fffa4d;--model-prop-format-color:#f9ffc5;--rendered-markdown-pre-color:#000;--rendered-markdown-code-background-color:rgba(0, 0, 0, .05);--rendered-markdown-code-color:#9012fe;--scroll-to-top-button-background-color:#2c120285;--scroll-to-top-button-hover-background-color:#3d210c85;--scroll-to-top-button-border-color:#60360b;--scroll-to-top-button-icon-color:#ffedbc;--scroll-to-top-button-shadow-color:rgba(0, 0, 0, .3)}
\ No newline at end of file
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/forest.min.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/forest.min.css
index a50bd88..e72e613 100644
--- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/forest.min.css
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/forest.min.css
@@ -1 +1 @@
-/*Forest Theme*/:root{--scrollbar-thumb-color:hsl(115, 100%, 30%);--scrollbar-thumb-hover-color:hsl(115, 100%, 40%);--body-background-color:#0e2702;--swagger-main-color:#fff;--loading-container-border-color:rgba(200, 200, 200, .1);--loading-container-border-top-color:rgba(255, 255, 255, .6);--errors-wrapper-background-color:rgb(255, 0, 0, .5);--errors-wrapper-border-color:#f95c5c;--errors-wrapper-errors-color:#bdbdbd;--topbar-background-color:#0f1a01;--topbar-pinned-background-color:rgb(0, 25, 2, .5);--topbar-pinned-shadow-color:rgba(0, 10, 2, .5);--topbar-pin-icon-color:white;--topbar-border-color:#095300;--topbar-select-border-color:#43b715;--topbar-select-label-color:#f1f1f1;--topbar-download-url-button-background-color:#43b715;--topbar-download-url-button-color:#f1f1f1;--swagger-info-link:#e9d98d;--swagger-info-link-hover:#bccf29;--api-version-background-color:#d4bf88;--api-version-stamp-background-color:#907a48;--api-version-color:black;--auth-wrapper-background-color:#2d2010;--auth-wrapper-border-color:#422e15;--auth-wrapper-box-shadow-color:rgba(0, 0, 0, .3);--auth-container-border-bottom-color:#251717;--auth-container-background-color:#3a4928;--auth-container-errors-color:red;--btn-authorize-background-color:#451f1f;--btn-authorize-border-color:#ededed;--btn-authorize-font-color:#fbffff;--btn-authorize-svg-fill-color:#fbffff;--opblock-tag-section-border-color:rgb(62, 165, 55);--opblock-border-color:white;--opblock-shadow-color:rgba(0, 0, 0, .2);--opblock-tag-section-expandcollapse-background-color:rgb(0 0 0 / 45%);--opblock-tag-background-color-hover:rgba(0, 0, 0, .05);--opblock-tag-pinned-background-color-hover:#0d2402;--opblock-tag-border-bottom-color:rgba(212, 191, 136, .5);--opblock-tabheader-underline-color:rgba(255, 255, 255, .6);--opblock-summary-svg-icons-color:white;--opblock-summary-border-bottom-color:white;--opblock-section-header-background-color:#0000004a;--opblock-section-header-shadow-color:rgba(0, 0, 0, .1);--opblock-summary-method-background-color:#3b2e1e;--opblock-summary-method-color:#fff;--opblock-summary-method-shadow-color:rgba(0, 0, 0, .1);--opblock-post-background-color:rgba(62, 163, 54, .24);--opblock-post-method-color:#228738;--opblock-post-border-color:#228738;--opblock-put-background-color:rgba(255, 167, 0, .3);--opblock-put-method-color:#c95f00;--opblock-put-border-color:#c95f00;--opblock-delete-background-color:rgba(255, 0, 0, .14);--opblock-delete-method-color:#631f03;--opblock-delete-border-color:#631f03;--opblock-get-background-color:rgba(151, 255, 0, .17);--opblock-get-method-color:#9f9400;--opblock-get-border-color:#9f9400;--opblock-patch-background-color:rgba(133, 117, 69, .25);--opblock-patch-method-color:#857545;--opblock-patch-border-color:#857545;--opblock-head-background-color:rgba(55, 50, 6, .8);--opblock-head-method-color:#4b4719;--opblock-head-border-color:#4b4719;--opblock-options-background-color:rgba(19, 22, 19, .3);--opblock-options-method-color:#131613;--opblock-options-border-color:#131613;--opblock-deprecated-background-color:hsla(0, 0%, 100%, .14);--opblock-deprecated-method-color:#a59595;--opblock-deprecated-border-color:#a59595;--swagger-tabs-divider-color:gray;--response-undocumented-color:#b0b0b0;--response-control-media-type-color:#74ff74;--opblock-pre-microlight-background-color:#47331b;--opblock-pre-microlight-color:#ebe3e3;--download-contents-background-color:#2e0d0d;--download-contents-color:white;--copy-to-clipboard-background-color:#2e0d0d;--copy-to-clipboard-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--filter-operation-filter-input-border-color:#d8dde7;--filter-download-failed-color:red;--filter-download-loading-color:#c1c1c1;--table-headers-example-color:#c4c4c4;--table-thead-border-bottom-color:rgb(0, 0, 0, 0.20);--parameter-name-required-symbol-color:red;--parameter-name-required-color:rgba(255, 0, 0, .8);--parameter-in-extension-color:#878585;--parameter-deprecated-color:red;--button-background-color:transparent;--button-border-color:#58a851;--button-shadow-color:rgba(0, 0, 0, .1);--button-shadow-hover-color:rgba(0, 0, 0, .3);--button-cancel-background-color:transparent;--button-cancel-border-color:#ff6060;--button-cancel-color:#ff6060;--button-execute-background-color:#346719;--button-execute-border-color:#478d26;--button-execute-color:white;--button-invalid-background-color:#feebeb;--button-invalid-border-color:#f93e3e;--expand-operation-svg-arrow-color:#dbdbdb;--expand-operation-svg-arrow-hover-color:white;--select-background-icon:#2b1414e0 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat;--select-border-color:#9ef594;--select-shadow-color:rgba(0, 0, 0, .25);--input-background-color:#2b1414e0;--input-border-color:#9ef594;--input-invalid-background-color:#510f0f;--input-invalid-border-color:#f93e3e;--input-invalid-color:#f3ebeb;--input-disabled-background-color:#47331b;--input-disabled-color:#ebe3e3;--select-disabled-border-color:#211;--textarea-disabled-background-color:#47331b;--textarea-disabled-color:#ebe3e3;--textarea-background-color:hsla(0, 36%, 12%, .8);--textarea-border-focus-color:#d5d5d5;--textarea-curl-background-color:#9ef594;--textarea-curl-color:#fff;--checkbox-color:#311414;--checkbox-label-item-background-color:#2c7a30;--checkbox-label-item-shadow-color:#2c7a30;--checkbox-label-item-icon:#2c7a30 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--dialog-backdrop-color:rgba(2, 18, 0, .8);--dialog-background-color:#29351b;--dialog-border-color:#4b6b3c;--dialog-shadow-color:rgba(0, 0, 0, .2);--dialog-close-button-icon-color:#fff;--model-deprecated-color:#a0a0a0;--model-arrow-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--model-hint-background-color:rgba(0, 0, 0, .3);--model-hint-color:#ebebeb;--model-property-color:#b5b5b5;--model-property-primitive-color:#8b8b8b;--model-property-required-symbol-color:red;--model-property-description:#b9b9b9;--model-property-extension:#aba6a6;--model-section-border-color:rgba(212, 191, 136, .5);--model-section-header-color:white;--model-section-header-hover-background-color:rgba(0, 0, 0, .02);--expand-model-svg-arrow-color:white;--model-section-little-header-color:#707070;--model-container-background-color:rgba(20, 20, 20, .05);--model-container-hover-background-color:rgba(0, 0, 0, .1);--model-box-background-color:rgba(0, 0, 0, .15);--model-title-color:#d9d9d9;--model-deprecated-warning-color:#f93e3e;--model-prop-type-color:#2fff41;--model-prop-format-color:#fff;--rendered-markdown-pre-color:#000;--rendered-markdown-code-background-color:rgba(0, 0, 0, .05);--rendered-markdown-code-color:#9012fe;--scroll-to-top-button-background-color:#002c02db;--scroll-to-top-button-hover-background-color:#003e03ca;--scroll-to-top-button-border-color:#43b715;--scroll-to-top-button-icon-color:#7cff54;--scroll-to-top-button-shadow-color:rgba(8, 114, 36, .50)}
\ No newline at end of file
+/*Forest Theme https://github.com/teociaps/SwaggerUI.Themes */:root{--scrollbar-thumb-color:hsl(115, 100%, 30%);--scrollbar-thumb-hover-color:hsl(115, 100%, 40%);--body-background-color:#0e2702;--swagger-main-color:#fff;--loading-container-border-color:rgba(200, 200, 200, .1);--loading-container-border-top-color:rgba(255, 255, 255, .6);--errors-wrapper-background-color:rgb(255, 0, 0, .5);--errors-wrapper-border-color:#f95c5c;--errors-wrapper-errors-color:#bdbdbd;--topbar-background-color:#0f1a01;--topbar-pinned-background-color:rgb(0, 25, 2, .5);--topbar-pinned-shadow-color:rgba(0, 10, 2, .5);--topbar-pin-icon-color:white;--topbar-border-color:#095300;--topbar-select-border-color:#43b715;--topbar-select-label-color:#f1f1f1;--topbar-download-url-button-background-color:#43b715;--topbar-download-url-button-color:#f1f1f1;--swagger-info-link:#e9d98d;--swagger-info-link-hover:#bccf29;--api-version-background-color:#d4bf88;--api-version-stamp-background-color:#907a48;--api-version-color:black;--auth-wrapper-background-color:#2d2010;--auth-wrapper-border-color:#422e15;--auth-wrapper-box-shadow-color:rgba(0, 0, 0, .3);--auth-container-border-bottom-color:#251717;--auth-container-background-color:#3a4928;--auth-container-errors-color:red;--btn-authorize-background-color:#451f1f;--btn-authorize-border-color:#ededed;--btn-authorize-font-color:#fbffff;--btn-authorize-svg-fill-color:#fbffff;--opblock-tag-section-border-color:rgb(62, 165, 55);--opblock-border-color:white;--opblock-shadow-color:rgba(0, 0, 0, .2);--opblock-tag-section-expandcollapse-background-color:rgb(0 0 0 / 45%);--opblock-tag-background-color-hover:rgba(0, 0, 0, .05);--opblock-tag-pinned-background-color-hover:#0d2402;--opblock-tag-border-bottom-color:rgba(212, 191, 136, .5);--opblock-tabheader-underline-color:rgba(255, 255, 255, .6);--opblock-summary-svg-icons-color:white;--opblock-summary-border-bottom-color:white;--opblock-section-header-background-color:#0000004a;--opblock-section-header-shadow-color:rgba(0, 0, 0, .1);--opblock-summary-method-background-color:#3b2e1e;--opblock-summary-method-color:#fff;--opblock-summary-method-shadow-color:rgba(0, 0, 0, .1);--opblock-post-background-color:rgba(62, 163, 54, .24);--opblock-post-method-color:#228738;--opblock-post-border-color:#228738;--opblock-put-background-color:rgba(255, 167, 0, .3);--opblock-put-method-color:#c95f00;--opblock-put-border-color:#c95f00;--opblock-delete-background-color:rgba(255, 0, 0, .14);--opblock-delete-method-color:#631f03;--opblock-delete-border-color:#631f03;--opblock-get-background-color:rgba(151, 255, 0, .17);--opblock-get-method-color:#9f9400;--opblock-get-border-color:#9f9400;--opblock-patch-background-color:rgba(133, 117, 69, .25);--opblock-patch-method-color:#857545;--opblock-patch-border-color:#857545;--opblock-head-background-color:rgba(55, 50, 6, .8);--opblock-head-method-color:#4b4719;--opblock-head-border-color:#4b4719;--opblock-options-background-color:rgba(19, 22, 19, .3);--opblock-options-method-color:#131613;--opblock-options-border-color:#131613;--opblock-deprecated-background-color:hsla(0, 0%, 100%, .14);--opblock-deprecated-method-color:#a59595;--opblock-deprecated-border-color:#a59595;--swagger-tabs-divider-color:gray;--response-undocumented-color:#b0b0b0;--response-control-media-type-color:#74ff74;--opblock-pre-microlight-background-color:#47331b;--opblock-pre-microlight-color:#ebe3e3;--download-contents-background-color:#2e0d0d;--download-contents-color:white;--copy-to-clipboard-background-color:#2e0d0d;--copy-to-clipboard-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--filter-operation-filter-input-border-color:#d8dde7;--filter-download-failed-color:red;--filter-download-loading-color:#c1c1c1;--table-headers-example-color:#c4c4c4;--table-thead-border-bottom-color:rgb(0, 0, 0, 0.20);--parameter-name-required-symbol-color:red;--parameter-name-required-color:rgba(255, 0, 0, .8);--parameter-in-extension-color:#878585;--parameter-deprecated-color:red;--button-background-color:transparent;--button-border-color:#58a851;--button-shadow-color:rgba(0, 0, 0, .1);--button-shadow-hover-color:rgba(0, 0, 0, .3);--button-cancel-background-color:transparent;--button-cancel-border-color:#ff6060;--button-cancel-color:#ff6060;--button-execute-background-color:#346719;--button-execute-border-color:#478d26;--button-execute-color:white;--button-invalid-background-color:#feebeb;--button-invalid-border-color:#f93e3e;--expand-operation-svg-arrow-color:#dbdbdb;--expand-operation-svg-arrow-hover-color:white;--select-background-icon:#2b1414e0 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat;--select-border-color:#9ef594;--select-shadow-color:rgba(0, 0, 0, .25);--input-background-color:#2b1414e0;--input-border-color:#9ef594;--input-invalid-background-color:#510f0f;--input-invalid-border-color:#f93e3e;--input-invalid-color:#f3ebeb;--input-disabled-background-color:#47331b;--input-disabled-color:#ebe3e3;--select-disabled-border-color:#211;--textarea-disabled-background-color:#47331b;--textarea-disabled-color:#ebe3e3;--textarea-background-color:hsla(0, 36%, 12%, .8);--textarea-border-focus-color:#d5d5d5;--textarea-curl-background-color:#9ef594;--textarea-curl-color:#fff;--checkbox-color:#311414;--checkbox-label-item-background-color:#2c7a30;--checkbox-label-item-shadow-color:#2c7a30;--checkbox-label-item-icon:#2c7a30 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--dialog-backdrop-color:rgba(2, 18, 0, .8);--dialog-background-color:#29351b;--dialog-border-color:#4b6b3c;--dialog-shadow-color:rgba(0, 0, 0, .2);--dialog-close-button-icon-color:#fff;--model-deprecated-color:#a0a0a0;--model-arrow-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--model-hint-background-color:rgba(0, 0, 0, .3);--model-hint-color:#ebebeb;--model-property-color:#b5b5b5;--model-property-primitive-color:#8b8b8b;--model-property-required-symbol-color:red;--model-property-description:#b9b9b9;--model-property-extension:#aba6a6;--model-section-border-color:rgba(212, 191, 136, .5);--model-section-header-color:white;--model-section-header-hover-background-color:rgba(0, 0, 0, .02);--expand-model-svg-arrow-color:white;--model-section-little-header-color:#707070;--model-container-background-color:rgba(20, 20, 20, .05);--model-container-hover-background-color:rgba(0, 0, 0, .1);--model-box-background-color:rgba(0, 0, 0, .15);--model-title-color:#d9d9d9;--model-deprecated-warning-color:#f93e3e;--model-prop-type-color:#2fff41;--model-prop-format-color:#fff;--rendered-markdown-pre-color:#000;--rendered-markdown-code-background-color:rgba(0, 0, 0, .05);--rendered-markdown-code-color:#9012fe;--scroll-to-top-button-background-color:#002c02db;--scroll-to-top-button-hover-background-color:#003e03ca;--scroll-to-top-button-border-color:#43b715;--scroll-to-top-button-icon-color:#7cff54;--scroll-to-top-button-shadow-color:rgba(8, 114, 36, .50)}
\ No newline at end of file
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/futuristic.min.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/futuristic.min.css
index c62cf55..982d607 100644
--- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/futuristic.min.css
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/futuristic.min.css
@@ -1 +1 @@
-/*Futuristic Theme*/:root{--scrollbar-thumb-color:hsl(194, 100%, 50%);--scrollbar-thumb-hover-color:hsl(194, 100%, 60%);--body-background-color:#020511;--swagger-main-color:#f6f6f6;--loading-container-border-color:rgba(200, 200, 200, .1);--loading-container-border-top-color:rgba(255, 255, 255, .6);--errors-wrapper-background-color:rgb(255, 0, 0, .1);--errors-wrapper-border-color:#f12323;--errors-wrapper-errors-color:#d5d5d5;--topbar-background-color:#04091c;--topbar-pinned-background-color:rgb(0, 1, 29, .6);--topbar-pinned-shadow-color:rgba(35, 116, 125, .6);--topbar-pin-icon-color:white;--topbar-border-color:#00f1ff;--topbar-select-border-color:#00f1ff;--topbar-select-label-color:#efe9e9;--topbar-download-url-button-background-color:#3c76c6;--topbar-download-url-button-color:#efe9e9;--swagger-info-link:#75b5ff;--swagger-info-link-hover:#a1d0eb;--api-version-background-color:#00bb87;--api-version-stamp-background-color:#443390;--api-version-color:#ffffff;--auth-wrapper-background-color:#0f0222;--auth-wrapper-border-color:#00f1ff;--auth-wrapper-box-shadow-color:#c1c1c1;--auth-container-border-bottom-color:#ebebeb;--auth-container-background-color:#ddd;--auth-container-errors-color:red;--btn-authorize-background-color:transparent;--btn-authorize-border-color:#17ecb2;--btn-authorize-font-color:#17ecb2;--btn-authorize-svg-fill-color:#17ecb2;--opblock-tag-section-border-color:#418f89;--opblock-border-color:#00ffea;--opblock-tag-section-expandcollapse-background-color:rgb(0 0 0 / 45%);--opblock-tag-background-color-hover:rgba(4, 7, 44, .2);--opblock-tag-pinned-background-color-hover:#03081c;--opblock-tag-border-bottom-color:rgba(0, 255, 234, .5);--opblock-shadow-color:rgba(255, 255, 255, .5);--opblock-tabheader-underline-color:rgba(255, 255, 255, .6);--opblock-summary-svg-icons-color:white;--opblock-summary-border-bottom-color:black;--opblock-section-header-background-color:#0000004a;--opblock-section-header-shadow-color:rgba(0, 0, 0, .1);--opblock-summary-method-background-color:black;--opblock-summary-method-color:white;--opblock-summary-method-shadow-color:rgba(0, 0, 0, .1);--opblock-post-background-color:rgba(63, 169, 233, .15);--opblock-post-method-color:#40aced;--opblock-post-border-color:#40aced;--opblock-put-background-color:rgba(105, 7, 212, .15);--opblock-put-method-color:#6907D4;--opblock-put-border-color:#6907D4;--opblock-delete-background-color:rgba(201, 47, 97, .15);--opblock-delete-method-color:#C92F61;--opblock-delete-border-color:#C92F61;--opblock-get-background-color:rgba(23, 236, 178, .15);--opblock-get-method-color:#17ECB2;--opblock-get-border-color:#17ECB2;--opblock-patch-background-color:rgba(101, 39, 132, .25);--opblock-patch-method-color:#652784;--opblock-patch-border-color:#652784;--opblock-head-background-color:rgba(39, 39, 101, .25);--opblock-head-method-color:#272765;--opblock-head-border-color:#272765;--opblock-options-background-color:rgba(179, 54, 178, .2);--opblock-options-method-color:#B336B2;--opblock-options-border-color:#B336B2;--opblock-deprecated-background-color:hsla(0, 0%, 87%, .1);--opblock-deprecated-method-color:#a59595;--opblock-deprecated-border-color:#a59595;--swagger-tabs-divider-color:gray;--response-undocumented-color:#999;--response-control-media-type-color:#00ff68;--opblock-pre-microlight-background-color:#01236291;--opblock-pre-microlight-color:#ebe3e3;--download-contents-background-color:#00a5d8;--download-contents-color:white;--copy-to-clipboard-background-color:#00a5d8;--copy-to-clipboard-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--filter-operation-filter-input-border-color:#d8dde7;--filter-download-failed-color:red;--filter-download-loading-color:#aaa;--table-headers-example-color:#999;--table-thead-border-bottom-color:rgba(59, 65, 81, .2);--parameter-name-required-symbol-color:red;--parameter-name-required-color:rgba(255, 0, 0, .8);--parameter-in-extension-color:#9a9a9a;--parameter-deprecated-color:red;--button-background-color:transparent;--button-border-color:#dadada;--button-shadow-color:rgba(0, 0, 0, .1);--button-shadow-hover-color:rgba(0, 0, 0, .3);--button-cancel-background-color:transparent;--button-cancel-border-color:#ff6060;--button-cancel-color:#ff6060;--button-execute-background-color:rgba(64, 172, 237, .5);--button-execute-border-color:#5fa9ff;--button-execute-color:#ebe3e3;--button-invalid-background-color:#feebeb;--button-invalid-border-color:#f93e3e;--expand-operation-svg-arrow-color:#aaa;--expand-operation-svg-arrow-hover-color:white;--select-background-icon:#030329 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat;--select-border-color:#2c4367;--select-shadow-color:rgba(0, 0, 0, .25);--input-background-color:#010123;--input-border-color:#124b81;--input-invalid-background-color:#210101;--input-invalid-border-color:#f93e3e;--input-invalid-color:#f3ebeb;--input-disabled-background-color:#1b2833;--input-disabled-color:#1b2833;--select-disabled-border-color:#575875;--textarea-disabled-background-color:#1b2833;--textarea-disabled-color:#8da5d0;--textarea-background-color:hsl(241.44deg 100% 5.98% / 80%);--textarea-border-focus-color:#59a5b2;--textarea-curl-background-color:#213168;--textarea-curl-color:#fff;--checkbox-color:#0f1639;--checkbox-label-item-background-color:#e8e8e8;--checkbox-label-item-shadow-color:#e8e8e8;--checkbox-label-item-icon:#e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--dialog-backdrop-color:rgba(3, 0, 16, .85);--dialog-background-color:#100223;--dialog-border-color:#40216b;--dialog-shadow-color:rgba(0, 0, 0, .2);--dialog-close-button-icon-color:#fff;--model-deprecated-color:#a0a0a0;--model-arrow-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--model-hint-background-color:rgba(30, 122, 167, .7);--model-hint-color:#ebebeb;--model-property-color:#c9c9c9;--model-property-primitive-color:#676767;--model-property-required-symbol-color:red;--model-property-description:#b9b9b9;--model-property-extension:#8d8a8a;--model-section-border-color:rgba(200, 200, 200, .3);--model-section-header-color:white;--model-section-header-hover-background-color:rgba(0, 0, 0, .02);--expand-model-svg-arrow-color:white;--model-section-little-header-color:#9a9a9a;--model-container-background-color:rgba(13, 32, 77, .1);--model-container-hover-background-color:rgba(70, 0, 179, .1);--model-box-background-color:rgba(101, 0, 255, .1);--model-title-color:#e7e9ea;--model-deprecated-warning-color:#f93e3e;--model-prop-type-color:#25e1ff;--model-prop-format-color:#cfcfcf;--rendered-markdown-pre-color:#000;--rendered-markdown-code-background-color:rgba(0, 0, 0, .05);--rendered-markdown-code-color:#9012fe;--scroll-to-top-button-background-color:#02001285;--scroll-to-top-button-hover-background-color:#02002085;--scroll-to-top-button-border-color:#00c2ff;--scroll-to-top-button-icon-color:#d1cccc;--scroll-to-top-button-shadow-color:rgba(30, 134, 130, .3)}.swagger-ui .topbar .topbar-wrapper{border:3px solid var(--topbar-border-color);border-top:0;box-shadow:0 0 8px 4px var(--topbar-pinned-shadow-color),inset 0 -4px 10px 0 var(--topbar-pinned-shadow-color)}.swagger-ui .topbar.pinned .topbar-wrapper{border:2px solid var(--topbar-border-color);box-shadow:0 0 8px 4px var(--topbar-pinned-shadow-color),inset 0 0 8px 4px var(--topbar-pinned-shadow-color)}.swagger-ui .auth-wrapper{box-shadow:0 0 3px 0 var(--auth-wrapper-border-color),inset 0 0 3px 0 var(--auth-wrapper-border-color)}.swagger-ui .btn.authorize:hover{box-shadow:0 0 3px 0 var(--btn-authorize-border-color),inset 0 0 3px 0 var(--btn-authorize-border-color);text-shadow:0 0 1px var(--btn-authorize-border-color)}.swagger-ui .opblock-tag-section{box-shadow:0 0 3px 0 var(--opblock-tag-section-border-color),inset 0 0 3px 0 var(--opblock-tag-section-border-color)}.swagger-ui .opblock{box-shadow:0 0 3px 0 var(--opblock-shadow-color),inset 0 0 5px 0 var(--opblock-shadow-color)}.swagger-ui .btn:hover{box-shadow:0 0 3px 0 var(--button-border-color),inset 0 0 3px 0 var(--button-border-color)}.swagger-ui .dialog-ux .modal-ux{box-shadow:0 0 5px 0 var(--dialog-border-color),inset 0 0 5px 0 var(--dialog-border-color)}.swagger-ui section.models{box-shadow:0 0 3px 0 var(--model-section-border-color),inset 0 0 3px 0 var(--model-section-border-color)}
\ No newline at end of file
+/*Futuristic Theme https://github.com/teociaps/SwaggerUI.Themes */:root{--scrollbar-thumb-color:hsl(194, 100%, 50%);--scrollbar-thumb-hover-color:hsl(194, 100%, 60%);--body-background-color:#020511;--swagger-main-color:#f6f6f6;--loading-container-border-color:rgba(200, 200, 200, .1);--loading-container-border-top-color:rgba(255, 255, 255, .6);--errors-wrapper-background-color:rgb(255, 0, 0, .1);--errors-wrapper-border-color:#f12323;--errors-wrapper-errors-color:#d5d5d5;--topbar-background-color:#04091c;--topbar-pinned-background-color:rgb(0, 1, 29, .6);--topbar-pinned-shadow-color:rgba(35, 116, 125, .6);--topbar-pin-icon-color:white;--topbar-border-color:#00f1ff;--topbar-select-border-color:#00f1ff;--topbar-select-label-color:#efe9e9;--topbar-download-url-button-background-color:#3c76c6;--topbar-download-url-button-color:#efe9e9;--swagger-info-link:#75b5ff;--swagger-info-link-hover:#a1d0eb;--api-version-background-color:#00bb87;--api-version-stamp-background-color:#443390;--api-version-color:#ffffff;--auth-wrapper-background-color:#0f0222;--auth-wrapper-border-color:#00f1ff;--auth-wrapper-box-shadow-color:#c1c1c1;--auth-container-border-bottom-color:#ebebeb;--auth-container-background-color:#ddd;--auth-container-errors-color:red;--btn-authorize-background-color:transparent;--btn-authorize-border-color:#17ecb2;--btn-authorize-font-color:#17ecb2;--btn-authorize-svg-fill-color:#17ecb2;--opblock-tag-section-border-color:#418f89;--opblock-border-color:#00ffea;--opblock-tag-section-expandcollapse-background-color:rgb(0 0 0 / 45%);--opblock-tag-background-color-hover:rgba(4, 7, 44, .2);--opblock-tag-pinned-background-color-hover:#03081c;--opblock-tag-border-bottom-color:rgba(0, 255, 234, .5);--opblock-shadow-color:rgba(255, 255, 255, .5);--opblock-tabheader-underline-color:rgba(255, 255, 255, .6);--opblock-summary-svg-icons-color:white;--opblock-summary-border-bottom-color:black;--opblock-section-header-background-color:#0000004a;--opblock-section-header-shadow-color:rgba(0, 0, 0, .1);--opblock-summary-method-background-color:black;--opblock-summary-method-color:white;--opblock-summary-method-shadow-color:rgba(0, 0, 0, .1);--opblock-post-background-color:rgba(63, 169, 233, .15);--opblock-post-method-color:#40aced;--opblock-post-border-color:#40aced;--opblock-put-background-color:rgba(105, 7, 212, .15);--opblock-put-method-color:#6907D4;--opblock-put-border-color:#6907D4;--opblock-delete-background-color:rgba(201, 47, 97, .15);--opblock-delete-method-color:#C92F61;--opblock-delete-border-color:#C92F61;--opblock-get-background-color:rgba(23, 236, 178, .15);--opblock-get-method-color:#17ECB2;--opblock-get-border-color:#17ECB2;--opblock-patch-background-color:rgba(101, 39, 132, .25);--opblock-patch-method-color:#652784;--opblock-patch-border-color:#652784;--opblock-head-background-color:rgba(39, 39, 101, .25);--opblock-head-method-color:#272765;--opblock-head-border-color:#272765;--opblock-options-background-color:rgba(179, 54, 178, .2);--opblock-options-method-color:#B336B2;--opblock-options-border-color:#B336B2;--opblock-deprecated-background-color:hsla(0, 0%, 87%, .1);--opblock-deprecated-method-color:#a59595;--opblock-deprecated-border-color:#a59595;--swagger-tabs-divider-color:gray;--response-undocumented-color:#999;--response-control-media-type-color:#00ff68;--opblock-pre-microlight-background-color:#01236291;--opblock-pre-microlight-color:#ebe3e3;--download-contents-background-color:#00a5d8;--download-contents-color:white;--copy-to-clipboard-background-color:#00a5d8;--copy-to-clipboard-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--filter-operation-filter-input-border-color:#d8dde7;--filter-download-failed-color:red;--filter-download-loading-color:#aaa;--table-headers-example-color:#999;--table-thead-border-bottom-color:rgba(59, 65, 81, .2);--parameter-name-required-symbol-color:red;--parameter-name-required-color:rgba(255, 0, 0, .8);--parameter-in-extension-color:#9a9a9a;--parameter-deprecated-color:red;--button-background-color:transparent;--button-border-color:#dadada;--button-shadow-color:rgba(0, 0, 0, .1);--button-shadow-hover-color:rgba(0, 0, 0, .3);--button-cancel-background-color:transparent;--button-cancel-border-color:#ff6060;--button-cancel-color:#ff6060;--button-execute-background-color:rgba(64, 172, 237, .5);--button-execute-border-color:#5fa9ff;--button-execute-color:#ebe3e3;--button-invalid-background-color:#feebeb;--button-invalid-border-color:#f93e3e;--expand-operation-svg-arrow-color:#aaa;--expand-operation-svg-arrow-hover-color:white;--select-background-icon:#030329 url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat;--select-border-color:#2c4367;--select-shadow-color:rgba(0, 0, 0, .25);--input-background-color:#010123;--input-border-color:#124b81;--input-invalid-background-color:#210101;--input-invalid-border-color:#f93e3e;--input-invalid-color:#f3ebeb;--input-disabled-background-color:#1b2833;--input-disabled-color:#1b2833;--select-disabled-border-color:#575875;--textarea-disabled-background-color:#1b2833;--textarea-disabled-color:#8da5d0;--textarea-background-color:hsl(241.44deg 100% 5.98% / 80%);--textarea-border-focus-color:#59a5b2;--textarea-curl-background-color:#213168;--textarea-curl-color:#fff;--checkbox-color:#0f1639;--checkbox-label-item-background-color:#e8e8e8;--checkbox-label-item-shadow-color:#e8e8e8;--checkbox-label-item-icon:#e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--dialog-backdrop-color:rgba(3, 0, 16, .85);--dialog-background-color:#100223;--dialog-border-color:#40216b;--dialog-shadow-color:rgba(0, 0, 0, .2);--dialog-close-button-icon-color:#fff;--model-deprecated-color:#a0a0a0;--model-arrow-icon:url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;--model-hint-background-color:rgba(30, 122, 167, .7);--model-hint-color:#ebebeb;--model-property-color:#c9c9c9;--model-property-primitive-color:#676767;--model-property-required-symbol-color:red;--model-property-description:#b9b9b9;--model-property-extension:#8d8a8a;--model-section-border-color:rgba(200, 200, 200, .3);--model-section-header-color:white;--model-section-header-hover-background-color:rgba(0, 0, 0, .02);--expand-model-svg-arrow-color:white;--model-section-little-header-color:#9a9a9a;--model-container-background-color:rgba(13, 32, 77, .1);--model-container-hover-background-color:rgba(70, 0, 179, .1);--model-box-background-color:rgba(101, 0, 255, .1);--model-title-color:#e7e9ea;--model-deprecated-warning-color:#f93e3e;--model-prop-type-color:#25e1ff;--model-prop-format-color:#cfcfcf;--rendered-markdown-pre-color:#000;--rendered-markdown-code-background-color:rgba(0, 0, 0, .05);--rendered-markdown-code-color:#9012fe;--scroll-to-top-button-background-color:#02001285;--scroll-to-top-button-hover-background-color:#02002085;--scroll-to-top-button-border-color:#00c2ff;--scroll-to-top-button-icon-color:#d1cccc;--scroll-to-top-button-shadow-color:rgba(30, 134, 130, .3)}.swagger-ui .topbar .topbar-wrapper{border:3px solid var(--topbar-border-color);border-top:0;box-shadow:0 0 8px 4px var(--topbar-pinned-shadow-color),inset 0 -4px 10px 0 var(--topbar-pinned-shadow-color)}.swagger-ui .topbar.pinned .topbar-wrapper{border:2px solid var(--topbar-border-color);box-shadow:0 0 8px 4px var(--topbar-pinned-shadow-color),inset 0 0 8px 4px var(--topbar-pinned-shadow-color)}.swagger-ui .auth-wrapper{box-shadow:0 0 3px 0 var(--auth-wrapper-border-color),inset 0 0 3px 0 var(--auth-wrapper-border-color)}.swagger-ui .btn.authorize:hover{box-shadow:0 0 3px 0 var(--btn-authorize-border-color),inset 0 0 3px 0 var(--btn-authorize-border-color);text-shadow:0 0 1px var(--btn-authorize-border-color)}.swagger-ui .opblock-tag-section{box-shadow:0 0 3px 0 var(--opblock-tag-section-border-color),inset 0 0 3px 0 var(--opblock-tag-section-border-color)}.swagger-ui .opblock{box-shadow:0 0 3px 0 var(--opblock-shadow-color),inset 0 0 5px 0 var(--opblock-shadow-color)}.swagger-ui .btn:hover{box-shadow:0 0 3px 0 var(--button-border-color),inset 0 0 3px 0 var(--button-border-color)}.swagger-ui .dialog-ux .modal-ux{box-shadow:0 0 5px 0 var(--dialog-border-color),inset 0 0 5px 0 var(--dialog-border-color)}.swagger-ui section.models{box-shadow:0 0 3px 0 var(--model-section-border-color),inset 0 0 3px 0 var(--model-section-border-color)}
\ No newline at end of file
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/light.min.css b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/light.min.css
index 5e13d71..14cb473 100644
--- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/light.min.css
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/light.min.css
@@ -1 +1 @@
-/*Light Theme*/
\ No newline at end of file
+/*Light Theme https://github.com/teociaps/SwaggerUI.Themes */
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Theme.cs b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Theme.cs
index 3f6f42f..64359e2 100644
--- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Theme.cs
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Theme.cs
@@ -43,14 +43,4 @@ protected Theme(string fileName, bool useMinified = false) : base(fileName, useM
/// Apply a desert tones theme to your Swagger UI.
///
public static Theme Desert => new("desert.css", true);
-
- ///
- protected override string GetThemeName()
- {
- var nameWithoutExtension = FileName
- .Replace(".min.css", "", StringComparison.OrdinalIgnoreCase)
- .Replace(".css", "", StringComparison.OrdinalIgnoreCase);
-
- return char.ToUpper(nameWithoutExtension[0]) + nameWithoutExtension[1..];
- }
}
\ No newline at end of file
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeBuilderHelpers.cs b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeBuilderHelpers.cs
new file mode 100644
index 0000000..b518122
--- /dev/null
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeBuilderHelpers.cs
@@ -0,0 +1,70 @@
+using Microsoft.AspNetCore.Builder;
+using System.Reflection;
+
+namespace AspNetCore.Swagger.Themes;
+
+///
+/// Shared logic for Swagger UI builder extensions.
+///
+internal static class ThemeBuilderHelpers
+{
+ internal static string GetConfiguredJavaScript(IDictionary additionalSettings) =>
+ AdvancedOptions.Apply(FileProvider.GetResourceText(FileProvider.JsFilename), additionalSettings, MimeTypes.Text.Javascript);
+
+ internal static void RegisterJavaScriptEndpoint(IApplicationBuilder application, string javascript) =>
+ FileProvider.AddGetEndpoint(application, FileProvider.ScriptsPath + FileProvider.JsFilename, javascript, MimeTypes.Text.Javascript);
+
+ internal static void ConfigureThemeWithSwitcher(
+ IApplicationBuilder application,
+ BaseTheme theme,
+ IDictionary additionalSettings,
+ ThemeSwitcherOptions switcherOptions,
+ Action loadAllThemesCallback)
+ {
+ ThemeSwitcher.Configure(application, theme, additionalSettings, switcherOptions);
+
+ // Check if theme switcher is enabled
+ if (!additionalSettings.TryGetValue(AdvancedOptions.ThemeSwitcher, out var enabled) || enabled is not true)
+ return;
+
+ switcherOptions ??= ThemeSwitcherOptions.All();
+
+ // Load all available themes
+ foreach (var availableTheme in ThemeSwitcher.GetAvailableThemes(theme, switcherOptions))
+ loadAllThemesCallback(availableTheme);
+ }
+
+ internal static void ConfigureCustomThemeWithSwitcher(
+ IApplicationBuilder application,
+ string themeName,
+ bool isStandalone,
+ IDictionary additionalSettings,
+ ThemeSwitcherOptions switcherOptions)
+ {
+ if (isStandalone)
+ return;
+
+ if (!additionalSettings.TryGetValue(AdvancedOptions.ThemeSwitcher, out var enabled) || enabled is not true)
+ return;
+
+ switcherOptions ??= ThemeSwitcherOptions.All();
+
+ var filteredThemes = ThemeSwitcher.GetFilteredThemes(switcherOptions).ToList();
+ FileProvider.ExposeThemeMetadata(application, filteredThemes, themeName, switcherOptions.ThemeDisplayFormat);
+ FileProvider.FreezeCollections();
+ }
+
+ internal static (string ThemeContent, string CommonContent, bool LoadJs, bool IsStandalone, string ThemeName) LoadAssemblyTheme(Assembly assembly, string cssFilename)
+ {
+ var themeContent = FileProvider.GetResourceText(cssFilename, assembly, out var commonContent, out var loadJs);
+ var isStandalone = string.IsNullOrEmpty(commonContent);
+ var themeName = ExtractThemeName(cssFilename);
+
+ return (themeContent, commonContent, loadJs, isStandalone, themeName);
+ }
+
+ private static string ExtractThemeName(string cssFilename) =>
+ Path.GetFileNameWithoutExtension(cssFilename)
+ .Replace("standalone.", "", StringComparison.OrdinalIgnoreCase)
+ .Replace(".min", "", StringComparison.OrdinalIgnoreCase);
+}
\ No newline at end of file
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeSwitcher.cs b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeSwitcher.cs
new file mode 100644
index 0000000..ca6358d
--- /dev/null
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeSwitcher.cs
@@ -0,0 +1,325 @@
+using Microsoft.AspNetCore.Builder;
+using System.Collections.Frozen;
+using System.Reflection;
+
+namespace AspNetCore.Swagger.Themes;
+
+///
+/// Centralized manager for theme registration and switcher functionality.
+/// Single source of truth for all theme operations.
+///
+internal static class ThemeSwitcher
+{
+ private static readonly FrozenSet s_predefinedThemes = new[]
+ {
+ Theme.Dark, Theme.Light, Theme.Forest, Theme.DeepSea, Theme.Desert, Theme.Futuristic
+ }.ToFrozenSet();
+
+ private static readonly Dictionary s_registeredThemes = new(StringComparer.OrdinalIgnoreCase);
+ private static FrozenDictionary s_frozenThemes;
+ private static readonly HashSet s_scannedTypes = [];
+
+ ///
+ /// Configures theme switcher for a given theme and options.
+ ///
+ internal static void Configure(
+ IApplicationBuilder application,
+ BaseTheme theme,
+ IDictionary additionalSettings,
+ ThemeSwitcherOptions switcherOptions = null)
+ {
+ RegisterTheme(theme, theme.FileName, isStandalone: false);
+
+ if (!additionalSettings.TryGetValue(AdvancedOptions.ThemeSwitcher, out var enabled) || enabled is not true)
+ return;
+
+ switcherOptions ??= ThemeSwitcherOptions.All();
+
+ if (switcherOptions.CustomThemeMode is CustomThemeMode.AutoDiscover)
+ AutoDiscoverCustomThemes(theme);
+
+ RegisterPredefinedThemes(switcherOptions);
+
+ var filteredThemes = GetFilteredThemes(switcherOptions).ToList();
+
+ if (!filteredThemes.Any(rt => rt.Name.Equals(theme.GetThemeName(), StringComparison.OrdinalIgnoreCase)))
+ {
+ var currentRegistered = s_registeredThemes.TryGetValue(theme.GetThemeName(), out var rt)
+ ? rt
+ : new RegisteredTheme(theme, FileProvider.StylesPath + theme.FileName, false);
+ filteredThemes.Insert(0, currentRegistered);
+ }
+
+ // Validate with actual discovered themes
+ switcherOptions.Validate(theme.GetThemeName(), filteredThemes);
+
+ FileProvider.ExposeThemeMetadata(application, filteredThemes, theme.GetThemeName(), switcherOptions.ThemeDisplayFormat);
+ FreezeCollections();
+ }
+
+ ///
+ /// Registers a theme for switching.
+ ///
+ internal static void RegisterTheme(BaseTheme theme, string cssPath, bool isStandalone)
+ {
+ var themeName = theme.GetThemeName();
+
+ if (s_registeredThemes.ContainsKey(themeName))
+ return;
+
+ s_registeredThemes[themeName] = new RegisteredTheme(
+ theme,
+ FileProvider.StylesPath + cssPath,
+ isStandalone
+ );
+ }
+
+ ///
+ /// Registers a custom theme from assembly.
+ ///
+ internal static void RegisterCustomTheme(string cssFilename, bool isStandalone)
+ {
+ var themeName = ExtractThemeName(cssFilename);
+ var cssPath = FileProvider.StylesPath + cssFilename;
+
+ if (s_registeredThemes.ContainsKey(themeName))
+ return;
+
+ s_registeredThemes[themeName] = new RegisteredTheme(
+ null,
+ cssPath,
+ isStandalone
+ );
+ }
+
+ ///
+ /// Registers theme endpoints for CSS files.
+ ///
+ internal static void RegisterThemeEndpoints(IApplicationBuilder application, BaseTheme theme, IDictionary advancedOptions)
+ {
+ RegisterTheme(theme, theme.FileName, isStandalone: false);
+
+ var commonContent = FileProvider.GetResourceText(theme.Common.FileName);
+ commonContent = AdvancedOptions.Apply(commonContent, advancedOptions, MimeTypes.Text.Css);
+ var commonPath = FileProvider.StylesPath + theme.Common.FileName;
+ FileProvider.AddGetEndpoint(application, commonPath, commonContent);
+
+ var themeContent = FileProvider.GetResourceText(theme.FileName, theme.GetType());
+ var themePath = FileProvider.StylesPath + theme.FileName;
+ FileProvider.AddGetEndpoint(application, themePath, themeContent);
+ }
+
+ ///
+ /// Loads theme content (common + theme CSS combined).
+ ///
+ internal static string LoadThemeContent(BaseTheme theme, IDictionary advancedOptions)
+ {
+ var commonCss = FileProvider.GetResourceText(theme.Common.FileName);
+ commonCss = AdvancedOptions.Apply(commonCss, advancedOptions, MimeTypes.Text.Css);
+
+ var themeCss = FileProvider.GetResourceText(theme.FileName, theme.GetType());
+
+ return commonCss + Environment.NewLine + themeCss;
+ }
+
+ ///
+ /// Creates a link tag for a theme.
+ ///
+ internal static string CreateThemeLink(BaseTheme theme, bool disabled = false)
+ {
+ var themePath = FileProvider.StylesPath + theme.FileName;
+ var disabledAttr = disabled ? @" disabled=""disabled""" : "";
+ return $@"";
+ }
+
+ ///
+ /// Creates a script that marks the current theme links.
+ ///
+ internal static string CreateCurrentThemeMarkerScript(BaseTheme currentTheme) =>
+ $$"""
+
+ """;
+
+ ///
+ /// Auto-discovers all custom theme properties from the current theme's type and related types.
+ ///
+ private static void AutoDiscoverCustomThemes(BaseTheme currentTheme)
+ {
+ var currentType = currentTheme.GetType();
+ var currentAssembly = currentType.Assembly;
+
+ // Scan the current theme's assembly
+ ScanAssemblyForThemes(currentAssembly);
+
+ // Also scan the entry assembly if it's different
+ var entryAssembly = Assembly.GetEntryAssembly();
+ if (entryAssembly is not null && entryAssembly != currentAssembly)
+ {
+ ScanAssemblyForThemes(entryAssembly);
+ }
+ }
+
+ ///
+ /// Scans an assembly for custom theme types.
+ ///
+ private static void ScanAssemblyForThemes(Assembly assembly)
+ {
+ var themeTypes = assembly.GetTypes()
+ .Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(BaseTheme)) && t != typeof(Theme))
+ .ToList();
+
+ foreach (var type in themeTypes)
+ ScanTypeForThemes(type);
+ }
+
+ ///
+ /// Scans a type for static properties returning BaseTheme and registers them.
+ ///
+ private static void ScanTypeForThemes(Type type)
+ {
+ if (!s_scannedTypes.Add(type))
+ return;
+
+ var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Static)
+ .Where(p => typeof(BaseTheme).IsAssignableFrom(p.PropertyType))
+ .ToList();
+
+ foreach (var property in properties)
+ {
+ try
+ {
+ if (property.GetValue(null) is not BaseTheme themeInstance)
+ continue;
+
+ if (IsStandaloneTheme(themeInstance))
+ continue;
+
+ RegisterTheme(themeInstance, themeInstance.FileName, isStandalone: false);
+ }
+ catch
+ {
+ // Silently skip properties that can't be accessed
+ }
+ }
+ }
+
+ ///
+ /// Determines if a theme is standalone by checking its filename.
+ ///
+ private static bool IsStandaloneTheme(BaseTheme theme) =>
+ theme.FileName.Contains("standalone", StringComparison.OrdinalIgnoreCase);
+
+ ///
+ /// Gets all predefined themes.
+ ///
+ internal static IEnumerable GetAllPredefinedThemes() => s_predefinedThemes;
+
+ ///
+ /// Gets available themes (excluding current theme).
+ ///
+ internal static IEnumerable GetAvailableThemes(BaseTheme currentTheme, ThemeSwitcherOptions options)
+ {
+ var currentThemeName = currentTheme.GetThemeName();
+ return GetFilteredThemes(options)
+ .Where(rt => !rt.Name.Equals(currentThemeName, StringComparison.OrdinalIgnoreCase))
+ .Select(rt => rt.Theme!)
+ .Where(t => t is not null);
+ }
+
+ ///
+ /// Gets filtered themes based on options.
+ ///
+ internal static IEnumerable GetFilteredThemes(ThemeSwitcherOptions options)
+ {
+ var themes = s_frozenThemes?.Values ?? s_registeredThemes.ToFrozenDictionary().Values;
+
+ return themes
+ .Where(rt => !rt.IsStandalone)
+ .Where(rt => ShouldIncludeTheme(rt, options));
+ }
+
+ ///
+ /// Determines if a theme is predefined by checking its name.
+ ///
+ internal static bool IsPredefinedTheme(BaseTheme theme) =>
+ FileProvider.IsPredefinedTheme(theme.GetThemeName());
+
+ ///
+ /// Determines if a theme should be included based on options.
+ ///
+ private static bool ShouldIncludeTheme(RegisteredTheme registeredTheme, ThemeSwitcherOptions options)
+ {
+ var isPredefined = FileProvider.IsPredefinedTheme(registeredTheme.Name);
+
+ if (options.ExcludedThemes.Any(t => t.GetThemeName().Equals(registeredTheme.Name, StringComparison.OrdinalIgnoreCase)))
+ return false;
+
+ if (isPredefined)
+ {
+ return options.IncludeAllPredefinedThemes ||
+ options.IncludedThemes.Any(t => t.GetThemeName().Equals(registeredTheme.Name, StringComparison.OrdinalIgnoreCase));
+ }
+ else
+ {
+ if (options.CustomThemeMode is CustomThemeMode.None)
+ return false;
+
+ if (!options.IncludeAllPredefinedThemes && options.IncludedThemes.Count > 0)
+ return options.IncludedThemes.Any(t => t.GetThemeName().Equals(registeredTheme.Name, StringComparison.OrdinalIgnoreCase));
+
+ return true;
+ }
+ }
+
+ ///
+ /// Registers all predefined themes based on options.
+ ///
+ private static void RegisterPredefinedThemes(ThemeSwitcherOptions options)
+ {
+ foreach (var theme in s_predefinedThemes)
+ {
+ var registered = new RegisteredTheme(theme, FileProvider.StylesPath + theme.FileName, false);
+ if (ShouldIncludeTheme(registered, options))
+ RegisterTheme(theme, theme.FileName, isStandalone: false);
+ }
+ }
+
+ ///
+ /// Freezes collections after startup for better read performance.
+ ///
+ private static void FreezeCollections()
+ {
+ if (s_frozenThemes == null && s_registeredThemes.Count > 0)
+ s_frozenThemes = s_registeredThemes.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
+ }
+
+ ///
+ /// Extracts theme name from CSS filename.
+ ///
+ private static string ExtractThemeName(string cssFilename) =>
+ Path.GetFileNameWithoutExtension(cssFilename)
+ .Replace("standalone.", "", StringComparison.OrdinalIgnoreCase)
+ .Replace(".min", "", StringComparison.OrdinalIgnoreCase);
+}
+
+///
+/// Represents a registered theme with metadata.
+///
+internal record RegisteredTheme(BaseTheme Theme, string CssPath, bool IsStandalone)
+{
+ public string Name => Theme?.GetThemeName() ??
+ Path.GetFileNameWithoutExtension(CssPath)
+ .Replace("standalone.", "", StringComparison.OrdinalIgnoreCase)
+ .Replace(".min", "", StringComparison.OrdinalIgnoreCase);
+}
\ No newline at end of file
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeSwitcherOptions.cs b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeSwitcherOptions.cs
new file mode 100644
index 0000000..27f30bb
--- /dev/null
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeSwitcherOptions.cs
@@ -0,0 +1,224 @@
+namespace AspNetCore.Swagger.Themes;
+
+///
+/// Configuration options for the theme switcher feature.
+///
+public class ThemeSwitcherOptions
+{
+ ///
+ /// Gets or sets whether to include all predefined themes in the switcher.
+ /// Default is .
+ ///
+ public bool IncludeAllPredefinedThemes { get; set; } = true;
+
+ ///
+ /// Gets specific themes to include (both predefined and custom).
+ ///
+ public List IncludedThemes { get; } = [];
+
+ ///
+ /// Gets themes to exclude from the switcher (both predefined and custom).
+ ///
+ public List ExcludedThemes { get; } = [];
+
+ ///
+ /// Gets or sets the custom theme inclusion strategy.
+ /// Default is .
+ ///
+ public CustomThemeMode CustomThemeMode { get; set; } = CustomThemeMode.AutoDiscover;
+
+ ///
+ /// Gets or sets the display name format for themes in the dropdown.
+ /// Available placeholders: {name} (e.g., "Theme: {name}" becomes "Theme: Dark").
+ /// Default is "{name}".
+ ///
+ public string ThemeDisplayFormat { get; set; } = "{name}";
+
+ ///
+ /// Validates the configuration with actual discovered themes.
+ ///
+ /// The default theme name.
+ /// The list of themes discovered and filtered. Pass to skip runtime validation.
+ internal void Validate(string defaultTheme, IList discoveredThemes = null)
+ {
+ // If discovered themes provided, validate with actual runtime data
+ if (discoveredThemes is not null)
+ {
+ if (discoveredThemes.Count < 2)
+ {
+ throw new InvalidOperationException(
+ "Theme switcher requires at least 2 themes to be available. " +
+ $"Only {discoveredThemes.Count} theme(s) were found after auto-discovery and filtering. " +
+ "Ensure you have enough themes available or adjust your ThemeSwitcherOptions configuration.");
+ }
+
+ // Ensure default theme is in the discovered list
+ if (!string.IsNullOrEmpty(defaultTheme) &&
+ !discoveredThemes.Any(t => t.Name.Equals(defaultTheme, StringComparison.OrdinalIgnoreCase)))
+ {
+ throw new InvalidOperationException(
+ $"Default theme '{defaultTheme}' was not found in the discovered themes. " +
+ "Ensure the default theme is included in your configuration.");
+ }
+
+ return; // Runtime validation complete
+ }
+
+ // Static validation (without runtime discovery data)
+ // Calculate available predefined themes
+ var predefinedCount = IncludeAllPredefinedThemes
+ ? 6 - ExcludedThemes.Count(ThemeSwitcher.IsPredefinedTheme)
+ : IncludedThemes.Count(ThemeSwitcher.IsPredefinedTheme);
+
+ // Calculate available custom themes (only for non-AutoDiscover modes)
+ var customCount = 0;
+ if (CustomThemeMode is CustomThemeMode.ExplicitOnly)
+ {
+ customCount = IncludedThemes.Count(t => !ThemeSwitcher.IsPredefinedTheme(t));
+ }
+ // Note: For AutoDiscover mode, we can't validate custom theme count here
+ // as discovery happens later at runtime
+
+ var availableCount = predefinedCount + customCount;
+
+ // Only validate minimum theme count for non-AutoDiscover modes
+ if (CustomThemeMode is not CustomThemeMode.AutoDiscover && availableCount < 2)
+ {
+ throw new InvalidOperationException(
+ "Theme switcher requires at least 2 themes to be available. " +
+ "Current configuration would result in fewer than 2 themes.");
+ }
+
+ // For AutoDiscover mode with no predefined themes, just warn that it relies on discovery
+ if (CustomThemeMode is CustomThemeMode.AutoDiscover && predefinedCount == 0)
+ {
+ // This is valid but relies on auto-discovery finding at least 2 custom themes
+ // We can't validate this here, so we allow it to proceed
+ }
+
+ // Ensure default theme is included
+ if (string.IsNullOrEmpty(defaultTheme)) return;
+
+ var isCustomTheme = !FileProvider.IsPredefinedTheme(defaultTheme);
+
+ if (!IncludeAllPredefinedThemes &&
+ !IncludedThemes.Any(t => t.GetThemeName().Equals(defaultTheme, StringComparison.OrdinalIgnoreCase)))
+ {
+ if (!isCustomTheme || CustomThemeMode is CustomThemeMode.None)
+ {
+ throw new InvalidOperationException(
+ $"Default theme '{defaultTheme}' must be included in the theme switcher.");
+ }
+ // For custom themes with AutoDiscover, allow it (will be validated at runtime)
+ }
+
+ if (ExcludedThemes.Any(t => t.GetThemeName().Equals(defaultTheme, StringComparison.OrdinalIgnoreCase)))
+ {
+ throw new InvalidOperationException(
+ $"Default theme '{defaultTheme}' cannot be excluded from the theme switcher.");
+ }
+ }
+
+ ///
+ /// Includes specific themes in the switcher (both predefined and custom).
+ /// This sets to false and clears .
+ ///
+ /// The themes to include.
+ /// The current options instance for method chaining.
+ public ThemeSwitcherOptions WithThemes(params BaseTheme[] themes)
+ {
+ IncludeAllPredefinedThemes = false;
+ IncludedThemes.Clear();
+ IncludedThemes.AddRange(themes);
+ return this;
+ }
+
+ ///
+ /// Excludes specific themes from the switcher (both predefined and custom).
+ ///
+ /// The themes to exclude.
+ /// The current options instance for method chaining.
+ public ThemeSwitcherOptions ExcludeThemes(params BaseTheme[] themes)
+ {
+ ExcludedThemes.Clear();
+ ExcludedThemes.AddRange(themes);
+ return this;
+ }
+
+ ///
+ /// Sets whether to include all predefined themes.
+ ///
+ /// to include all predefined themes, otherwise.
+ /// The current options instance for method chaining.
+ public ThemeSwitcherOptions WithAllPredefinedThemes(bool include = true)
+ {
+ IncludeAllPredefinedThemes = include;
+ return this;
+ }
+
+ ///
+ /// Sets whether to include custom themes.
+ ///
+ /// The custom theme inclusion mode.
+ /// The current options instance for method chaining.
+ public ThemeSwitcherOptions WithCustomThemes(CustomThemeMode mode)
+ {
+ CustomThemeMode = mode;
+ return this;
+ }
+
+ ///
+ /// Sets the display format for theme names.
+ ///
+ /// The format string (use {name} as placeholder).
+ /// The current options instance for method chaining.
+ public ThemeSwitcherOptions WithDisplayFormat(string format)
+ {
+ ThemeDisplayFormat = format ?? "{name}";
+ return this;
+ }
+
+ ///
+ /// Creates a configuration that includes all themes (predefined + custom with auto-discovery).
+ ///
+ public static ThemeSwitcherOptions All() => new();
+
+ ///
+ /// Creates a configuration that includes only predefined themes.
+ ///
+ public static ThemeSwitcherOptions PredefinedOnly() => new()
+ {
+ IncludeAllPredefinedThemes = true,
+ CustomThemeMode = CustomThemeMode.None
+ };
+
+ ///
+ /// Creates a configuration that includes only custom themes with auto-discovery.
+ ///
+ public static ThemeSwitcherOptions CustomOnly() => new()
+ {
+ IncludeAllPredefinedThemes = false,
+ CustomThemeMode = CustomThemeMode.AutoDiscover
+ };
+}
+
+///
+/// Defines how custom themes should be handled in the theme switcher.
+///
+public enum CustomThemeMode
+{
+ ///
+ /// Do not include any custom themes.
+ ///
+ None = 0,
+
+ ///
+ /// Only include explicitly registered custom themes (no auto-discovery).
+ ///
+ ExplicitOnly = 1,
+
+ ///
+ /// Auto-discover and include all custom theme properties from custom theme classes.
+ ///
+ AutoDiscover = 2
+}
\ No newline at end of file
diff --git a/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIBuilderExtensions.cs b/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIBuilderExtensions.cs
index 397824d..af1269c 100644
--- a/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIBuilderExtensions.cs
+++ b/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIBuilderExtensions.cs
@@ -1,11 +1,12 @@
using AspNetCore.Swagger.Themes;
using Swashbuckle.AspNetCore.SwaggerUI;
using System.Reflection;
+using System.Text;
namespace Microsoft.AspNetCore.Builder;
///
-/// Extensions methods for .
+/// Extension methods for to configure Swagger UI with themes.
///
public static class SwaggerUIBuilderExtensions
{
@@ -23,10 +24,9 @@ public static IApplicationBuilder UseSwaggerUI(
SwaggerUIOptions options)
{
ArgumentNullException.ThrowIfNull(theme);
-
options ??= new SwaggerUIOptions();
- ConfigureSwaggerUIOptions(application, options, theme).Invoke(options);
+ ConfigureTheme(application, options, theme).Invoke(options);
return application.UseSwaggerUI(options);
}
@@ -45,19 +45,15 @@ public static IApplicationBuilder UseSwaggerUI(
{
ArgumentNullException.ThrowIfNull(theme);
- var options = new SwaggerUIOptions();
- setupAction?.Invoke(options);
-
- var optionsAction = ConfigureSwaggerUIOptions(application, options, theme);
-
- if (setupAction is not null)
- optionsAction += setupAction;
-
- return application.UseSwaggerUI(optionsAction);
+ return application.UseSwaggerUI(opt =>
+ {
+ setupAction?.Invoke(opt);
+ ConfigureThemeInternal(application, opt, theme);
+ });
}
///
- /// Registers the Swagger UI middleware applying the provided CSS theme and optional setup action.
+ /// Registers the Swagger UI middleware applying the provided CSS theme.
///
/// The application builder instance.
/// The CSS theme to apply.
@@ -71,15 +67,16 @@ public static IApplicationBuilder UseSwaggerUI(
{
ArgumentNullException.ThrowIfNull(cssThemeContent);
- const string CustomCssThemePath = $"{FileProvider.StylesPath}custom.css";
- FileProvider.AddGetEndpoint(application, CustomCssThemePath, cssThemeContent);
- setupAction += options => options.InjectStylesheet(CustomCssThemePath);
+ const string customPath = $"{FileProvider.StylesPath}custom.css";
+ FileProvider.AddGetEndpoint(application, customPath, cssThemeContent);
+ ThemeSwitcher.RegisterCustomTheme("custom.css", isStandalone: true);
+ setupAction += options => options.InjectStylesheet(customPath);
return application.UseSwaggerUI(setupAction);
}
///
- /// Registers the Swagger UI middleware applying the provided CSS theme and optional setup action.
+ /// Registers the Swagger UI middleware applying a CSS theme from an assembly.
///
/// The application builder instance.
/// The assembly where the embedded CSS file is situated.
@@ -99,78 +96,111 @@ public static IApplicationBuilder UseSwaggerUI(
var options = new SwaggerUIOptions();
setupAction?.Invoke(options);
- var theme = FileProvider.GetResourceText(cssFilename, assembly, out var commonTheme, out var loadJs);
+ var (themeContent, commonContent, loadJs, isStandalone, themeName) =
+ ThemeBuilderHelpers.LoadAssemblyTheme(assembly, cssFilename);
+
+ var customPath = FileProvider.StylesPath + cssFilename;
+ ThemeSwitcher.RegisterCustomTheme(cssFilename, isStandalone);
- if (!string.IsNullOrEmpty(commonTheme))
+ if (!isStandalone)
{
- commonTheme = AdvancedOptions.Apply(commonTheme, options.ConfigObject.AdditionalItems, MimeTypes.Text.Css);
- const string CommonCssThemePath = $"{FileProvider.StylesPath}common.css";
- FileProvider.AddGetEndpoint(application, CommonCssThemePath, commonTheme);
- setupAction += options => options.InjectStylesheet(CommonCssThemePath);
+ commonContent = AdvancedOptions.Apply(commonContent, options.ConfigObject.AdditionalItems, MimeTypes.Text.Css);
+
+ const string commonPath = $"{FileProvider.StylesPath}common.css";
+ FileProvider.AddGetEndpoint(application, commonPath, commonContent);
+ setupAction += opt => opt.InjectStylesheet(commonPath);
if (loadJs && AdvancedOptions.AnyJsFeatureEnabled(options.ConfigObject.AdditionalItems))
+ {
setupAction += InjectJavascript(application, options);
+ setupAction += opt => ThemeBuilderHelpers.ConfigureCustomThemeWithSwitcher(
+ application, themeName, isStandalone, options.ConfigObject.AdditionalItems, opt.GetThemeSwitcherOptions());
+ }
}
- FileProvider.AddGetEndpoint(application, FileProvider.StylesPath + cssFilename, theme);
- setupAction += options => options.InjectStylesheet(FileProvider.StylesPath + cssFilename);
+ FileProvider.AddGetEndpoint(application, customPath, themeContent);
+ setupAction += opt => opt.InjectStylesheet(customPath);
return application.UseSwaggerUI(setupAction);
}
- #region Private
-
- private static Action ConfigureSwaggerUIOptions(IApplicationBuilder application, SwaggerUIOptions options, BaseTheme theme)
+ private static void ConfigureThemeInternal(
+ IApplicationBuilder application,
+ SwaggerUIOptions options,
+ BaseTheme theme)
{
- ImportSwaggerTheme(application, options, theme);
+ // Register theme endpoints
+ ThemeSwitcher.RegisterThemeEndpoints(application, theme, options.ConfigObject.AdditionalItems);
- var optionsAction = InjectCommonTheme(application, options, theme);
- optionsAction += InjectTheme(theme);
+ // Inject stylesheets
+ var commonPath = FileProvider.StylesPath + theme.Common.FileName;
+ var themePath = FileProvider.StylesPath + theme.FileName;
+ options.InjectStylesheet(commonPath);
+ options.InjectStylesheet(themePath);
+ // Configure JS features if enabled
if (theme.LoadAdditionalJs && AdvancedOptions.AnyJsFeatureEnabled(options.ConfigObject.AdditionalItems))
- optionsAction += InjectJavascript(application, options);
-
- return optionsAction;
+ {
+ InjectJavascriptInternal(application, options);
+ ConfigureThemeSwitcher(application, options, theme);
+ }
}
- private static void ImportSwaggerTheme(IApplicationBuilder application, SwaggerUIOptions options, BaseTheme theme, bool isCommonTheme = false)
+ private static Action ConfigureTheme(
+ IApplicationBuilder application,
+ SwaggerUIOptions options,
+ BaseTheme theme)
{
- var themeContent = FileProvider.GetResourceText(theme.FileName, theme.GetType());
-
- if (isCommonTheme)
- themeContent = AdvancedOptions.Apply(themeContent, options.ConfigObject.AdditionalItems, MimeTypes.Text.Css);
-
- FileProvider.AddGetEndpoint(application, ComposeThemePath(theme), themeContent);
+ // This is only used by the first overload
+ return opt => ConfigureThemeInternal(application, opt, theme);
}
- private static Action InjectTheme(BaseTheme theme)
+ private static void InjectJavascriptInternal(
+ IApplicationBuilder application,
+ SwaggerUIOptions options)
{
- return options => options.InjectStylesheet(ComposeThemePath(theme));
- }
+ var javascript = ThemeBuilderHelpers.GetConfiguredJavaScript(options.ConfigObject.AdditionalItems);
+ ThemeBuilderHelpers.RegisterJavaScriptEndpoint(application, javascript);
- private static string ComposeThemePath(BaseTheme theme)
- {
- return FileProvider.StylesPath + theme.FileName;
+ const string jsPath = FileProvider.ScriptsPath + FileProvider.JsFilename;
+ options.InjectJavascript(jsPath);
}
- private static Action InjectCommonTheme(IApplicationBuilder application, SwaggerUIOptions options, BaseTheme theme)
+ private static Action InjectJavascript(
+ IApplicationBuilder application,
+ SwaggerUIOptions options)
{
- var commonTheme = theme.Common;
- ImportSwaggerTheme(application, options, commonTheme, true);
-
- return InjectTheme(commonTheme);
+ return opt => InjectJavascriptInternal(application, opt);
}
- private static Action InjectJavascript(IApplicationBuilder application, SwaggerUIOptions options)
+ private static void ConfigureThemeSwitcher(
+ IApplicationBuilder application,
+ SwaggerUIOptions options,
+ BaseTheme theme)
{
- var javascript = FileProvider.GetResourceText(FileProvider.JsFilename);
- javascript = AdvancedOptions.Apply(javascript, options.ConfigObject.AdditionalItems, MimeTypes.Text.Javascript);
-
- const string FullPath = FileProvider.ScriptsPath + FileProvider.JsFilename;
- FileProvider.AddGetEndpoint(application, FullPath, javascript, MimeTypes.Text.Javascript);
-
- return options => options.InjectJavascript(FullPath);
+ var headContent = new StringBuilder();
+
+ // Get switcher options from cache
+ var switcherOptions = options.GetThemeSwitcherOptions();
+
+ ThemeBuilderHelpers.ConfigureThemeWithSwitcher(
+ application,
+ theme,
+ options.ConfigObject.AdditionalItems,
+ switcherOptions,
+ availableTheme =>
+ {
+ // Register endpoint for each available theme
+ ThemeSwitcher.RegisterThemeEndpoints(application, availableTheme, options.ConfigObject.AdditionalItems);
+
+ // Inject as disabled stylesheet
+ headContent.AppendLine(ThemeSwitcher.CreateThemeLink(availableTheme, disabled: true));
+ });
+
+ // Mark current theme
+ headContent.AppendLine(ThemeSwitcher.CreateCurrentThemeMarkerScript(theme));
+
+ // Append to options
+ options.HeadContent += headContent.ToString();
}
-
- #endregion Private
}
\ No newline at end of file
diff --git a/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIOptionsExtensions.cs b/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIOptionsExtensions.cs
index a83de7e..92a0cf3 100644
--- a/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIOptionsExtensions.cs
+++ b/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIOptionsExtensions.cs
@@ -1,76 +1,104 @@
using AspNetCore.Swagger.Themes;
-using Swashbuckle.AspNetCore.SwaggerUI;
namespace Microsoft.AspNetCore.Builder;
///
-/// Extension methods for .
+/// Extension methods for .
///
public static class SwaggerUIOptionsExtensions
{
- ///
- /// Enables all the advanced options:
- ///
- ///
- /// Pinnable topbar:
- ///
- ///
- ///
- /// Back to top button:
- ///
- ///
- ///
- /// Sticky operations:
- ///
- ///
- ///
- /// Expand or collapse all operations:
- ///
- ///
- ///
- ///
- /// The SwaggerUI options.
- public static void EnableAllAdvancedOptions(this SwaggerUIOptions options)
- {
- options.EnablePinnableTopbar();
- options.ShowBackToTopButton();
- options.EnableStickyOperations();
- options.EnableExpandOrCollapseAllOperations();
- }
+ private static ThemeSwitcherOptions s_switcherOptions;
- ///
- /// Enables the pinnable topbar feature.
- ///
- /// The SwaggerUI options.
- public static void EnablePinnableTopbar(this SwaggerUIOptions options)
+ extension(Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIOptions options)
{
- options.ConfigObject.AdditionalItems.EnablePinnableTopbar();
- }
- ///
- /// Shows a button to scroll back to the top of the page.
- ///
- /// The SwaggerUI options.
- public static void ShowBackToTopButton(this SwaggerUIOptions options)
- {
- options.ConfigObject.AdditionalItems.EnableBackToTop();
- }
+ ///
+ /// Enables all the advanced options:
+ ///
+ ///
+ /// Pinnable topbar:
+ ///
+ ///
+ ///
+ /// Back to top button:
+ ///
+ ///
+ ///
+ /// Sticky operations:
+ ///
+ ///
+ ///
+ /// Expand or collapse all operations:
+ ///
+ ///
+ ///
+ /// Theme switcher:
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Configuration options for the theme switcher.
+ /// If , all themes (predefined and custom) will be available.
+ /// Use factory methods for common scenarios.
+ ///
+ public void EnableAllAdvancedOptions(ThemeSwitcherOptions switcherOptions = null)
+ {
+ options.EnablePinnableTopbar();
+ options.ShowBackToTopButton();
+ options.EnableStickyOperations();
+ options.EnableExpandOrCollapseAllOperations();
+ options.EnableThemeSwitcher(switcherOptions);
+ }
- ///
- /// Enables sticky operations.
- ///
- /// The SwaggerUI options.
- public static void EnableStickyOperations(this SwaggerUIOptions options)
- {
- options.ConfigObject.AdditionalItems.EnableStickyOperations();
- }
+ ///
+ /// Enables the pinnable topbar feature.
+ ///
+ public void EnablePinnableTopbar() =>
+ options.ConfigObject.AdditionalItems.EnablePinnableTopbar();
- ///
- /// Enables the expand or collapse functionality for all operations inside a tag.
- ///
- /// The SwaggerUI options.
- public static void EnableExpandOrCollapseAllOperations(this SwaggerUIOptions options)
- {
- options.ConfigObject.AdditionalItems.EnableExpandOrCollapseAllOperations();
+ ///
+ /// Shows a button to scroll back to the top of the page.
+ ///
+ public void ShowBackToTopButton() =>
+ options.ConfigObject.AdditionalItems.EnableBackToTop();
+
+ ///
+ /// Enables sticky operations.
+ ///
+ public void EnableStickyOperations() =>
+ options.ConfigObject.AdditionalItems.EnableStickyOperations();
+
+ ///
+ /// Enables the expand or collapse functionality for all operations inside a tag.
+ ///
+ public void EnableExpandOrCollapseAllOperations() =>
+ options.ConfigObject.AdditionalItems.EnableExpandOrCollapseAllOperations();
+
+ ///
+ /// Enables the theme switcher that allows users to change themes at runtime.
+ /// The selected theme is persisted in browser local storage.
+ ///
+ ///
+ /// Note: not available for standalone themes as they don't include JavaScript.
+ ///
+ ///
+ /// Configuration options for the theme switcher.
+ /// If null, all themes (predefined and custom) will be available.
+ /// Use factory methods for common scenarios.
+ ///
+ public void EnableThemeSwitcher(ThemeSwitcherOptions switcherOptions = null)
+ {
+ options.ConfigObject.AdditionalItems.EnableThemeSwitcher();
+
+ if (switcherOptions is not null)
+ s_switcherOptions = switcherOptions;
+ }
+
+ ///
+ /// Gets the theme switcher options.
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "extension method")]
+ internal ThemeSwitcherOptions GetThemeSwitcherOptions() => s_switcherOptions;
}
}
\ No newline at end of file
diff --git a/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/NSwagBuilderExtensions.cs b/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/NSwagBuilderExtensions.cs
index 7b40eef..afc3b44 100644
--- a/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/NSwagBuilderExtensions.cs
+++ b/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/NSwagBuilderExtensions.cs
@@ -6,12 +6,12 @@
namespace Microsoft.AspNetCore.Builder;
///
-/// Extensions methods for .
+/// Extension methods for to configure NSwag Swagger UI with themes.
///
public static class NSwagBuilderExtensions
{
///
- /// Registers the Swagger UI middleware with a specified theme and optional settings setup action.
+ /// Registers the Swagger UI middleware with the specified theme and optional settings setup action.
///
/// The application builder instance.
/// The theme to apply.
@@ -25,14 +25,17 @@ public static IApplicationBuilder UseSwaggerUi(
{
ArgumentNullException.ThrowIfNull(theme);
- return application.UseSwaggerUi(uiSettings =>
+ return application.UseSwaggerUi(settings =>
{
- configureSettings?.Invoke(uiSettings);
+ configureSettings?.Invoke(settings);
- uiSettings.CustomInlineStyles = GetSwaggerThemeCss(theme, uiSettings);
+ settings.CustomInlineStyles = ThemeSwitcher.LoadThemeContent(theme, settings.AdditionalSettings);
- if (theme.LoadAdditionalJs && AdvancedOptions.AnyJsFeatureEnabled(uiSettings.AdditionalSettings))
- AddCustomJavascript(application, uiSettings);
+ if (theme.LoadAdditionalJs && AdvancedOptions.AnyJsFeatureEnabled(settings.AdditionalSettings))
+ {
+ InjectJavascript(application, settings);
+ ConfigureThemeSwitcher(application, settings, theme);
+ }
});
}
@@ -51,13 +54,14 @@ public static IApplicationBuilder UseSwaggerUi(
{
ArgumentNullException.ThrowIfNull(cssThemeContent);
- setupAction += options => options.CustomInlineStyles = cssThemeContent;
+ ThemeSwitcher.RegisterCustomTheme("custom.css", isStandalone: true);
+ setupAction += opt => opt.CustomInlineStyles = cssThemeContent;
return application.UseSwaggerUi(setupAction);
}
///
- /// Registers the Swagger UI middleware applying the provided CSS theme and optional setup action.
+ /// Registers the Swagger UI middleware applying a CSS theme from an assembly with optional setup action.
///
/// The application builder instance.
/// The assembly where the embedded CSS file is situated.
@@ -77,55 +81,60 @@ public static IApplicationBuilder UseSwaggerUi(
var settings = new SwaggerUiSettings();
configureSettings?.Invoke(settings);
- var theme = FileProvider.GetResourceText(cssFilename, assembly, out var commonTheme, out var loadJs);
+ var (themeContent, commonContent, loadJs, isStandalone, themeName) =
+ ThemeBuilderHelpers.LoadAssemblyTheme(assembly, cssFilename);
+
+ ThemeSwitcher.RegisterCustomTheme(cssFilename, isStandalone);
- if (!string.IsNullOrEmpty(commonTheme))
+ if (!isStandalone)
{
- commonTheme = AdvancedOptions.Apply(commonTheme, settings.AdditionalSettings, MimeTypes.Text.Css);
- theme = commonTheme + Environment.NewLine + theme;
+ commonContent = AdvancedOptions.Apply(commonContent, settings.AdditionalSettings, MimeTypes.Text.Css);
+ themeContent = commonContent + Environment.NewLine + themeContent;
if (loadJs && AdvancedOptions.AnyJsFeatureEnabled(settings.AdditionalSettings))
- configureSettings += settings => AddCustomJavascript(application, settings);
+ {
+ configureSettings += s => InjectJavascript(application, s);
+ configureSettings += s => ThemeBuilderHelpers.ConfigureCustomThemeWithSwitcher(
+ application, themeName, isStandalone, s.AdditionalSettings, s.GetThemeSwitcherOptions());
+ }
}
- configureSettings += options => options.CustomInlineStyles = theme;
+ configureSettings += opt => opt.CustomInlineStyles = themeContent;
return application.UseSwaggerUi(configureSettings);
}
- // TODO: add other extension methods from nswag?
-
- #region Private
-
- private static string GetSwaggerThemeCss(BaseTheme theme, SwaggerUiSettings settings)
+ private static void InjectJavascript(IApplicationBuilder application, SwaggerUiSettings settings)
{
- var sb = new StringBuilder();
-
- string baseCss = FileProvider.GetResourceText(theme.Common.FileName);
- baseCss = AdvancedOptions.Apply(baseCss, settings.AdditionalSettings, MimeTypes.Text.Css);
-
- string themeCss = FileProvider.GetResourceText(theme.FileName, theme.GetType());
+ var javascript = ThemeBuilderHelpers.GetConfiguredJavaScript(settings.AdditionalSettings);
+ ThemeBuilderHelpers.RegisterJavaScriptEndpoint(application, javascript);
- sb.Append(baseCss);
- sb.Append('\n');
- sb.Append(themeCss);
-
- return sb.ToString();
+ const string jsPath = FileProvider.ScriptsPath + FileProvider.JsFilename;
+ settings.CustomJavaScriptPath = jsPath;
}
- private static string GetSwaggerThemeJavascriptPath(IApplicationBuilder application, SwaggerUiSettings settings)
+ private static void ConfigureThemeSwitcher(
+ IApplicationBuilder application,
+ SwaggerUiSettings settings,
+ BaseTheme theme)
{
- string javascript = FileProvider.GetResourceText(FileProvider.JsFilename);
- javascript = AdvancedOptions.Apply(javascript, settings.AdditionalSettings, MimeTypes.Text.Javascript);
-
- const string FullPath = FileProvider.ScriptsPath + FileProvider.JsFilename;
- FileProvider.AddGetEndpoint(application, FullPath, javascript, MimeTypes.Text.Javascript);
-
- return FullPath;
+ var headContent = new StringBuilder();
+ var switcherOptions = settings.GetThemeSwitcherOptions();
+
+ ThemeBuilderHelpers.ConfigureThemeWithSwitcher(
+ application,
+ theme,
+ settings.AdditionalSettings,
+ switcherOptions,
+ availableTheme =>
+ {
+ var themeCss = ThemeSwitcher.LoadThemeContent(availableTheme, settings.AdditionalSettings);
+ var themePath = $"{FileProvider.StylesPath}{availableTheme.FileName}";
+ FileProvider.AddGetEndpoint(application, themePath, themeCss);
+
+ headContent.AppendLine(ThemeSwitcher.CreateThemeLink(availableTheme, disabled: true));
+ });
+
+ settings.CustomHeadContent += headContent.ToString();
}
-
- private static void AddCustomJavascript(IApplicationBuilder application, SwaggerUiSettings settings)
- => settings.CustomJavaScriptPath = GetSwaggerThemeJavascriptPath(application, settings);
-
- #endregion Private
}
\ No newline at end of file
diff --git a/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/SwaggerUiSettingsExtensions.cs b/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/SwaggerUiSettingsExtensions.cs
index ca89fb3..066efbf 100644
--- a/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/SwaggerUiSettingsExtensions.cs
+++ b/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/SwaggerUiSettingsExtensions.cs
@@ -8,69 +8,97 @@ namespace Microsoft.AspNetCore.Builder;
///
public static class SwaggerUiSettingsExtensions
{
- ///
- /// Enables all the advanced options:
- ///
- ///
- /// Pinnable topbar:
- ///
- ///
- ///
- /// Back to top button
- ///
- ///
- ///
- /// Sticky operations
- ///
- ///
- ///
- /// Expand or collapse all operations
- ///
- ///
- ///
- ///
- /// The SwaggerUi options.
- public static void EnableAllAdvancedOptions(this SwaggerUiSettings settings)
- {
- settings.EnablePinnableTopbar();
- settings.ShowBackToTopButton();
- settings.EnableStickyOperations();
- settings.EnableExpandOrCollapseAllOperations();
- }
+ private static ThemeSwitcherOptions s_switcherOptions;
- ///
- /// Enables the pinnable topbar feature.
- ///
- /// The SwaggerUi settings.
- public static void EnablePinnableTopbar(this SwaggerUiSettings settings)
+ extension(SwaggerUiSettings settings)
{
- settings.AdditionalSettings.EnablePinnableTopbar();
- }
+ ///
+ /// Enables all the advanced options:
+ ///
+ ///
+ /// Pinnable topbar:
+ ///
+ ///
+ ///
+ /// Back to top button:
+ ///
+ ///
+ ///
+ /// Sticky operations:
+ ///
+ ///
+ ///
+ /// Expand or collapse all operations:
+ ///
+ ///
+ ///
+ /// Theme switcher:
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Configuration options for the theme switcher.
+ /// If , all themes (predefined and custom) will be available.
+ /// Use factory methods for common scenarios.
+ ///
+ public void EnableAllAdvancedOptions(ThemeSwitcherOptions switcherOptions = null)
+ {
+ settings.EnablePinnableTopbar();
+ settings.ShowBackToTopButton();
+ settings.EnableStickyOperations();
+ settings.EnableExpandOrCollapseAllOperations();
+ settings.EnableThemeSwitcher(switcherOptions);
+ }
- ///
- /// Shows a button to scroll back to the top of the page.
- ///
- /// The SwaggerUi settings.
- public static void ShowBackToTopButton(this SwaggerUiSettings settings)
- {
- settings.AdditionalSettings.EnableBackToTop();
- }
+ ///
+ /// Enables the pinnable topbar feature.
+ ///
+ public void EnablePinnableTopbar() =>
+ settings.AdditionalSettings.EnablePinnableTopbar();
- ///
- /// Enables sticky operations.
- ///
- /// The SwaggerUi settings.
- public static void EnableStickyOperations(this SwaggerUiSettings settings)
- {
- settings.AdditionalSettings.EnableStickyOperations();
- }
+ ///
+ /// Shows a button to scroll back to the top of the page.
+ ///
+ public void ShowBackToTopButton() =>
+ settings.AdditionalSettings.EnableBackToTop();
- ///
- /// Enables the expand or collapse functionality for all operations inside a tag.
- ///
- /// The SwaggerUi settings.
- public static void EnableExpandOrCollapseAllOperations(this SwaggerUiSettings settings)
- {
- settings.AdditionalSettings.EnableExpandOrCollapseAllOperations();
+ ///
+ /// Enables sticky operations.
+ ///
+ public void EnableStickyOperations() =>
+ settings.AdditionalSettings.EnableStickyOperations();
+
+ ///
+ /// Enables the expand or collapse functionality for all operations inside a tag.
+ ///
+ public void EnableExpandOrCollapseAllOperations() =>
+ settings.AdditionalSettings.EnableExpandOrCollapseAllOperations();
+
+ ///
+ /// Enables the theme switcher that allows users to change themes at runtime.
+ /// The selected theme is persisted in browser local storage.
+ ///
+ ///
+ /// Note: not available for standalone themes as they don't include JavaScript.
+ ///
+ ///
+ /// Configuration options for the theme switcher.
+ /// If null, all themes (predefined and custom) will be available.
+ /// Use factory methods for common scenarios.
+ ///
+ public void EnableThemeSwitcher(ThemeSwitcherOptions switcherOptions = null)
+ {
+ settings.AdditionalSettings.EnableThemeSwitcher();
+
+ if (switcherOptions is not null)
+ s_switcherOptions = switcherOptions;
+ }
+
+ ///
+ /// Gets the theme switcher options.
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "extension method")]
+ internal ThemeSwitcherOptions GetThemeSwitcherOptions() => s_switcherOptions;
}
}
\ No newline at end of file
diff --git a/tests/AspNetCore.SwaggerUI.Themes.Tests/FileProviderMiddlewareTests.cs b/tests/AspNetCore.SwaggerUI.Themes.Tests/FileProviderMiddlewareTests.cs
new file mode 100644
index 0000000..89f1aac
--- /dev/null
+++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/FileProviderMiddlewareTests.cs
@@ -0,0 +1,117 @@
+using AspNetCore.Swagger.Themes.Tests.Utilities;
+using Shouldly;
+using static AspNetCore.Swagger.Themes.FileProvider;
+
+namespace AspNetCore.Swagger.Themes.Tests;
+
+///
+/// Tests for FileProvider with non-WebApplication scenarios (middleware-based).
+/// These tests are isolated to avoid conflicts with WebApplication endpoint registrations.
+///
+public class FileProviderMiddlewareTests
+{
+ private readonly Dictionary _advancedOptions = new()
+ {
+ { AdvancedOptions.PinnableTopbar, true },
+ { AdvancedOptions.StickyOperations, true },
+ { AdvancedOptions.BackToTop, true },
+ { AdvancedOptions.ExpandOrCollapseAllOperations, true }
+ };
+
+ [Theory]
+ [ClassData(typeof(ThemeTestData))]
+ public async Task AddGetEndpoint_ShouldReturnCssContent_WhenNotWebApplication(BaseTheme theme)
+ {
+ // Arrange
+ var mockAppBuilder = new MockApplicationBuilder();
+ var path = $"/test-middleware{StylesPath}{theme.FileName}"; // Use unique path to avoid conflicts
+ var content = GetResourceText(theme.FileName, theme.GetType());
+
+ // Act
+ AddGetEndpoint(mockAppBuilder, path, content);
+ var app = mockAppBuilder.Build();
+
+ // Simulate a request
+ var context = MockApplicationBuilder.CreateHttpContext(path);
+ await app.Invoke(context);
+ await context.Response.Body.FlushAsync();
+
+ // Assert
+ context.Response.StatusCode.ShouldBe(200);
+ context.Response.ContentType.ShouldBe(MimeTypes.Text.Css);
+
+ context.Response.Body.Seek(0, SeekOrigin.Begin);
+ using var reader = new StreamReader(context.Response.Body);
+ var responseBody = await reader.ReadToEndAsync();
+ responseBody.ShouldBe(content);
+ }
+
+ [Fact]
+ public async Task AddGetEndpoint_ShouldReturn404_WhenPathDoesNotMatch()
+ {
+ // Arrange
+ var mockAppBuilder = new MockApplicationBuilder();
+ const string registeredPath = "/test-middleware/styles/dark.min.css";
+ const string requestPath = "/test-middleware/styles/light.min.css";
+ const string content = "body { background: #000; }";
+
+ // Act
+ AddGetEndpoint(mockAppBuilder, registeredPath, content);
+ var app = mockAppBuilder.Build();
+
+ var context = MockApplicationBuilder.CreateHttpContext(requestPath);
+ await app.Invoke(context);
+
+ // Assert
+ context.Response.StatusCode.ShouldBe(404); // Middleware didn't match, falls through to 404
+ }
+
+ [Fact]
+ public async Task AddGetEndpoint_ShouldSetCacheHeaders()
+ {
+ // Arrange
+ var mockAppBuilder = new MockApplicationBuilder();
+ const string path = "/test-middleware/cached-style.css";
+ const string content = "body { }";
+
+ // Act
+ AddGetEndpoint(mockAppBuilder, path, content);
+ var app = mockAppBuilder.Build();
+
+ var context = MockApplicationBuilder.CreateHttpContext(path);
+ await app.Invoke(context);
+
+ // Assert
+ context.Response.Headers.ShouldContainKey("Cache-Control");
+ context.Response.Headers["Cache-Control"].ToString().ShouldBe("max-age=3600");
+
+ context.Response.Headers.ShouldContainKey("Expires");
+ context.Response.Headers["Expires"].ToString().ShouldNotBeEmpty();
+ }
+
+ [Fact]
+ public async Task AddGetEndpoint_ShouldHandleCustomContentType()
+ {
+ // Arrange
+ var mockAppBuilder = new MockApplicationBuilder();
+ const string path = "/test-middleware/script.js";
+ const string content = "console.log('test');";
+ const string contentType = MimeTypes.Text.Javascript;
+
+ // Act
+ AddGetEndpoint(mockAppBuilder, path, content, contentType);
+ var app = mockAppBuilder.Build();
+
+ var context = MockApplicationBuilder.CreateHttpContext(path);
+ await app.Invoke(context);
+
+ // Assert
+ context.Response.StatusCode.ShouldBe(200);
+ context.Response.ContentType.ShouldBe(contentType);
+
+ context.Response.Body.Seek(0, SeekOrigin.Begin);
+ using var reader = new StreamReader(context.Response.Body);
+ var responseBody = await reader.ReadToEndAsync();
+ responseBody.ShouldBe(content);
+ }
+}
\ No newline at end of file
diff --git a/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeProviderTests.cs b/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeProviderTests.cs
index bb27dec..257f5a9 100644
--- a/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeProviderTests.cs
+++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeProviderTests.cs
@@ -5,17 +5,21 @@
namespace AspNetCore.Swagger.Themes.Tests;
+///
+/// Tests for ThemeProvider functionality using WebApplicationFactory.
+/// Tests for non-WebApplication scenarios are in FileProviderMiddlewareTests.
+///
public class ThemeProviderTests : IClassFixture>
{
private readonly ThemeProviderWebApplicationFactory _themeProviderWebApplicationFactory;
private readonly Dictionary _advancedOptions = new()
- {
- { AdvancedOptions.PinnableTopbar, true },
- { AdvancedOptions.StickyOperations, true },
- { AdvancedOptions.BackToTop, true },
- { AdvancedOptions.ExpandOrCollapseAllOperations, true }
- };
+ {
+ { AdvancedOptions.PinnableTopbar, true },
+ { AdvancedOptions.StickyOperations, true },
+ { AdvancedOptions.BackToTop, true },
+ { AdvancedOptions.ExpandOrCollapseAllOperations, true }
+ };
public ThemeProviderTests(ThemeProviderWebApplicationFactory themeProviderWebApplicationFactory)
{
@@ -46,26 +50,26 @@ public void GetResourceText_ThrowsInvalidOperationException_WhenNotCssFile()
// Act & Assert
var exception = Assert.Throws(() => GetResourceText(InvalidFileName, assembly, out string commonStyle, out bool loadJs));
- exception.Message.ShouldContain("not a valid name for CSS files");
+ exception.Message.ShouldContain("is not a valid CSS file. Must end with '.css' or '.min.css'.");
}
[Theory]
[ClassData(typeof(ThemeTestData))]
- public void GetResourceText_ShouldEmbedAndRetrieveThemeStyleFromExecutingAssembly(BaseTheme Theme)
+ public void GetResourceText_ShouldEmbedAndRetrieveThemeStyleFromExecutingAssembly(BaseTheme theme)
{
// Arrange/Act
- var styleText = GetResourceText(Theme.FileName, Theme.GetType());
+ var styleText = GetResourceText(theme.FileName, theme.GetType());
// Assert - Verify correct header format based on file type
- if (Theme.FileName.EndsWith(".min.css"))
+ if (theme.FileName.EndsWith(".min.css"))
{
- styleText.ShouldStartWith($"/*{Theme}*/");
+ styleText.ShouldStartWith($"/*{theme} https://github.com/teociaps/SwaggerUI.Themes */");
}
else
{
styleText.ShouldStartWith($"""
/*
- {Theme}
+ {theme}
https://github.com/teociaps/SwaggerUI.Themes
*/
@@ -78,7 +82,7 @@ public void GetResourceText_ShouldEmbedAndRetrieveThemeStyleFromExecutingAssembl
var minJsFile = GetResourceText(JsFilename);
// Assert
- minJsFile.ShouldStartWith("/*Swagger UI*/");
+ minJsFile.ShouldStartWith("/*Swagger UI https://github.com/teociaps/SwaggerUI.Themes */");
}
}
@@ -96,7 +100,7 @@ public void GetResourceText_ShouldThrowFileNotFoundException_WhenExternalCssWith
public void GetResourceText_ShouldGetCommonCssStyleWithJS_WhenExternalCssLoadedWithinAssemblyNamespace()
{
// Arrange
- const string ExternalFileName = "custom.Theme.css";
+ const string ExternalFileName = "custom.theme.css";
// Act
var styleContent = GetResourceText(ExternalFileName, Assembly.GetExecutingAssembly(), out var commonStyle, out var loadJs);
@@ -114,8 +118,8 @@ Test Custom Theme
}
""");
- // Common Theme is always minified version
- commonStyle.ShouldStartWith("/*Common Theme*/");
+ // Common theme is always minified version
+ commonStyle.ShouldStartWith("/*Common Theme https://github.com/teociaps/SwaggerUI.Themes */");
loadJs.ShouldBeTrue();
}
@@ -123,7 +127,7 @@ Test Custom Theme
public void GetResourceText_ShouldNotLoadCommonStyleOrJS_WhenStandaloneStyleInCustomNamespace()
{
// Arrange
- const string ExternalFileName = "standalone.Theme.css";
+ const string ExternalFileName = "standalone.theme.css";
// Act
var styleContent = GetResourceText(ExternalFileName, Assembly.GetExecutingAssembly(), out var commonStyle, out var loadJs);
@@ -136,10 +140,10 @@ Test Standalone Theme
https://github.com/teociaps/SwaggerUI.Themes
*/
- /* Standalone Theme - should NOT load common.css or ui.js */
+ /* Standalone theme - should NOT load common.css or ui.js */
""");
- // Standalone Theme should NOT load common Theme or JS
+ // Standalone theme should NOT load common theme or JS
commonStyle.ShouldBeEmpty();
loadJs.ShouldBeFalse();
}
@@ -147,7 +151,7 @@ Test Standalone Theme
[Theory]
[InlineData("standalone.custom.css")]
[InlineData("STANDALONE.theme.css")]
- [InlineData("my.standalone.Theme.css")]
+ [InlineData("my.standalone.theme.css")]
public void GetResourceText_ShouldRecognizeStandaloneKeyword_CaseInsensitive(string fileName)
{
// Note: This test verifies the logic without actually having these files
@@ -159,11 +163,11 @@ public void GetResourceText_ShouldRecognizeStandaloneKeyword_CaseInsensitive(str
[Theory]
[ClassData(typeof(ThemeTestData))]
- public async Task AddGetEndpoint_ShouldReturnStyleContent_WhenWebApplication(BaseTheme Theme)
+ public async Task AddGetEndpoint_ShouldReturnStyleContent_WhenWebApplication(BaseTheme theme)
{
// Arrange
- var fullPath = StylesPath + Theme.FileName;
- var styleText = GetResourceText(Theme.FileName, Theme.GetType());
+ var fullPath = StylesPath + theme.FileName;
+ var styleText = GetResourceText(theme.FileName, theme.GetType());
// Act
var response = await _themeProviderWebApplicationFactory.Client.GetAsync(fullPath);
@@ -172,34 +176,4 @@ public async Task AddGetEndpoint_ShouldReturnStyleContent_WhenWebApplication(Bas
response.StatusCode.ShouldBe(System.Net.HttpStatusCode.OK);
(await response.Content.ReadAsStringAsync()).ShouldBeEquivalentTo(styleText);
}
-
- [Theory]
- [ClassData(typeof(ThemeTestData))]
- public async Task AddGetEndpoint_ShouldReturnCssContent_WhenNotWebApplication(BaseTheme Theme)
- {
- // Arrange
- var mockAppBuilder = new MockApplicationBuilder();
- var path = StylesPath + Theme.FileName;
- var content = GetResourceText(Theme.FileName, Theme.GetType());
-
- // Act
- AddGetEndpoint(mockAppBuilder, path, content);
- var app = mockAppBuilder.Build();
-
- // Simulate a request
- var context = MockApplicationBuilder.CreateHttpContext(path);
- await app.Invoke(context);
-
- await context.Response.Body.FlushAsync();
-
- // Assert
- Assert.Equal(200, context.Response.StatusCode);
- Assert.Equal(MimeTypes.Text.Css, context.Response.ContentType);
-
- context.Response.Body.Seek(0, SeekOrigin.Begin);
-
- using var reader = new StreamReader(context.Response.Body);
- var responseBody = await reader.ReadToEndAsync();
- Assert.Equal(content, responseBody);
- }
}
\ No newline at end of file
diff --git a/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeSwitcherTests.cs b/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeSwitcherTests.cs
new file mode 100644
index 0000000..2832b76
--- /dev/null
+++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeSwitcherTests.cs
@@ -0,0 +1,488 @@
+using Shouldly;
+
+namespace AspNetCore.Swagger.Themes.Tests;
+
+///
+/// Comprehensive tests for the Theme Switcher functionality.
+///
+public class ThemeSwitcherTests
+{
+ #region ThemeSwitcherOptions Tests
+
+ [Fact]
+ public void ThemeSwitcherOptions_DefaultValues_AreCorrect()
+ {
+ // Arrange & Act
+ var options = new ThemeSwitcherOptions();
+
+ // Assert
+ options.IncludeAllPredefinedThemes.ShouldBeTrue();
+ options.CustomThemeMode.ShouldBe(CustomThemeMode.AutoDiscover);
+ options.ThemeDisplayFormat.ShouldBe("{name}");
+ options.IncludedThemes.ShouldBeEmpty();
+ options.ExcludedThemes.ShouldBeEmpty();
+ }
+
+ [Fact]
+ public void ThemeSwitcherOptions_All_ReturnsDefaultConfiguration()
+ {
+ // Arrange & Act
+ var options = ThemeSwitcherOptions.All();
+
+ // Assert
+ options.IncludeAllPredefinedThemes.ShouldBeTrue();
+ options.CustomThemeMode.ShouldBe(CustomThemeMode.AutoDiscover);
+ }
+
+ [Fact]
+ public void ThemeSwitcherOptions_PredefinedOnly_ExcludesCustomThemes()
+ {
+ // Arrange & Act
+ var options = ThemeSwitcherOptions.PredefinedOnly();
+
+ // Assert
+ options.IncludeAllPredefinedThemes.ShouldBeTrue();
+ options.CustomThemeMode.ShouldBe(CustomThemeMode.None);
+ }
+
+ [Fact]
+ public void ThemeSwitcherOptions_CustomOnly_ExcludesPredefinedThemes()
+ {
+ // Arrange & Act
+ var options = ThemeSwitcherOptions.CustomOnly();
+
+ // Assert
+ options.IncludeAllPredefinedThemes.ShouldBeFalse();
+ options.CustomThemeMode.ShouldBe(CustomThemeMode.AutoDiscover);
+ }
+
+ #endregion ThemeSwitcherOptions Tests
+
+ #region CustomThemeMode Tests
+
+ [Fact]
+ public void CustomThemeMode_None_ExcludesAllCustomThemes()
+ {
+ // Arrange
+ var options = new ThemeSwitcherOptions
+ {
+ CustomThemeMode = CustomThemeMode.None
+ };
+
+ // Assert
+ options.CustomThemeMode.ShouldBe(CustomThemeMode.None);
+ }
+
+ [Fact]
+ public void CustomThemeMode_ExplicitOnly_IncludesOnlyRegisteredCustomThemes()
+ {
+ // Arrange
+ var options = new ThemeSwitcherOptions
+ {
+ CustomThemeMode = CustomThemeMode.ExplicitOnly
+ };
+
+ // Assert
+ options.CustomThemeMode.ShouldBe(CustomThemeMode.ExplicitOnly);
+ }
+
+ [Fact]
+ public void CustomThemeMode_AutoDiscover_EnablesAutoDiscovery()
+ {
+ // Arrange
+ var options = new ThemeSwitcherOptions
+ {
+ CustomThemeMode = CustomThemeMode.AutoDiscover
+ };
+
+ // Assert
+ options.CustomThemeMode.ShouldBe(CustomThemeMode.AutoDiscover);
+ }
+
+ #endregion CustomThemeMode Tests
+
+ #region Fluent API Tests
+
+ [Fact]
+ public void WithThemes_SetsIncludedThemesAndDisablesIncludeAll()
+ {
+ // Arrange
+ var options = new ThemeSwitcherOptions();
+ var darkTheme = Theme.Dark;
+ var lightTheme = Theme.Light;
+
+ // Act
+ options.WithThemes(darkTheme, lightTheme);
+
+ // Assert
+ options.IncludeAllPredefinedThemes.ShouldBeFalse();
+ options.IncludedThemes.Count.ShouldBe(2);
+ options.IncludedThemes.ShouldContain(darkTheme);
+ options.IncludedThemes.ShouldContain(lightTheme);
+ }
+
+ [Fact]
+ public void WithThemes_ClearsPreviousIncludedThemes()
+ {
+ // Arrange
+ var options = new ThemeSwitcherOptions();
+ var darkTheme = Theme.Dark;
+ var lightTheme = Theme.Light;
+ var forestTheme = Theme.Forest;
+
+ options.WithThemes(darkTheme);
+
+ // Act
+ options.WithThemes(lightTheme, forestTheme);
+
+ // Assert
+ options.IncludedThemes.Count.ShouldBe(2);
+ options.IncludedThemes.ShouldNotContain(darkTheme);
+ options.IncludedThemes.ShouldContain(lightTheme);
+ options.IncludedThemes.ShouldContain(forestTheme);
+ }
+
+ [Fact]
+ public void ExcludeThemes_SetsExcludedThemes()
+ {
+ // Arrange
+ var options = new ThemeSwitcherOptions();
+ var futuristicTheme = Theme.Futuristic;
+ var desertTheme = Theme.Desert;
+
+ // Act
+ options.ExcludeThemes(futuristicTheme, desertTheme);
+
+ // Assert
+ options.ExcludedThemes.Count.ShouldBe(2);
+ options.ExcludedThemes.ShouldContain(futuristicTheme);
+ options.ExcludedThemes.ShouldContain(desertTheme);
+ }
+
+ [Fact]
+ public void ExcludeThemes_ClearsPreviousExclusions()
+ {
+ // Arrange
+ var options = new ThemeSwitcherOptions();
+ var darkTheme = Theme.Dark;
+ var lightTheme = Theme.Light;
+
+ options.ExcludeThemes(darkTheme);
+
+ // Act
+ options.ExcludeThemes(lightTheme);
+
+ // Assert
+ options.ExcludedThemes.Count.ShouldBe(1);
+ options.ExcludedThemes.ShouldNotContain(darkTheme);
+ options.ExcludedThemes.ShouldContain(lightTheme);
+ }
+
+ [Fact]
+ public void WithDisplayFormat_SetsCustomFormat()
+ {
+ // Arrange
+ var options = new ThemeSwitcherOptions();
+
+ // Act
+ options.WithDisplayFormat("Theme: {name}");
+
+ // Assert
+ options.ThemeDisplayFormat.ShouldBe("Theme: {name}");
+ }
+
+ [Fact]
+ public void WithDisplayFormat_NullSetsDefaultFormat()
+ {
+ // Arrange
+ var options = new ThemeSwitcherOptions();
+ options.WithDisplayFormat("Custom");
+
+ // Act
+ options.WithDisplayFormat(null);
+
+ // Assert
+ options.ThemeDisplayFormat.ShouldBe("{name}");
+ }
+
+ [Fact]
+ public void WithAllPredefinedThemes_SetsFlag()
+ {
+ // Arrange
+ var options = new ThemeSwitcherOptions();
+
+ // Act
+ options.WithAllPredefinedThemes(false);
+
+ // Assert
+ options.IncludeAllPredefinedThemes.ShouldBeFalse();
+ }
+
+ [Fact]
+ public void WithCustomThemes_SetsMode()
+ {
+ // Arrange
+ var options = new ThemeSwitcherOptions();
+
+ // Act
+ options.WithCustomThemes(CustomThemeMode.ExplicitOnly);
+
+ // Assert
+ options.CustomThemeMode.ShouldBe(CustomThemeMode.ExplicitOnly);
+ }
+
+ [Fact]
+ public void FluentAPI_CanChainMultipleMethods()
+ {
+ // Arrange
+ var darkTheme = Theme.Dark;
+ var lightTheme = Theme.Light;
+ var futuristicTheme = Theme.Futuristic;
+
+ // Act
+ var options = new ThemeSwitcherOptions()
+ .WithThemes(darkTheme, lightTheme)
+ .ExcludeThemes(futuristicTheme)
+ .WithDisplayFormat("🎨 {name}")
+ .WithCustomThemes(CustomThemeMode.AutoDiscover);
+
+ // Assert
+ options.IncludeAllPredefinedThemes.ShouldBeFalse();
+ options.IncludedThemes.Count.ShouldBe(2);
+ options.ExcludedThemes.Count.ShouldBe(1);
+ options.ThemeDisplayFormat.ShouldBe("🎨 {name}");
+ options.CustomThemeMode.ShouldBe(CustomThemeMode.AutoDiscover);
+ }
+
+ #endregion Fluent API Tests
+
+ #region Validation Tests
+
+ [Fact]
+ public void Validate_ThrowsWhenLessThanTwoThemesAvailable()
+ {
+ // Arrange
+ var darkTheme = Theme.Dark;
+ var options = new ThemeSwitcherOptions()
+ .WithThemes(darkTheme)
+ .WithCustomThemes(CustomThemeMode.None);
+
+ // Act & Assert
+ var exception = Should.Throw(() => options.Validate("Dark"));
+ exception.Message.ShouldContain("at least 2 themes");
+ }
+
+ [Fact]
+ public void Validate_PassesWithTwoOrMoreThemes()
+ {
+ // Arrange
+ var darkTheme = Theme.Dark;
+ var lightTheme = Theme.Light;
+ var options = new ThemeSwitcherOptions()
+ .WithThemes(darkTheme, lightTheme)
+ .WithCustomThemes(CustomThemeMode.None);
+
+ // Act & Assert
+ Should.NotThrow(() => options.Validate("Dark"));
+ }
+
+ [Fact]
+ public void Validate_ThrowsWhenDefaultThemeExcluded()
+ {
+ // Arrange
+ var darkTheme = Theme.Dark;
+ var options = new ThemeSwitcherOptions()
+ .ExcludeThemes(darkTheme);
+
+ // Act & Assert
+ var exception = Should.Throw(() => options.Validate("Dark"));
+ exception.Message.ShouldContain("cannot be excluded");
+ }
+
+ [Fact]
+ public void Validate_ThrowsWhenDefaultThemeNotIncludedInSpecificList()
+ {
+ // Arrange
+ var lightTheme = Theme.Light;
+ var forestTheme = Theme.Forest;
+ var options = new ThemeSwitcherOptions()
+ .WithThemes(lightTheme, forestTheme)
+ .WithCustomThemes(CustomThemeMode.None);
+
+ // Act & Assert
+ var exception = Should.Throw(() => options.Validate("Dark"));
+ exception.Message.ShouldContain("must be included");
+ }
+
+ [Fact]
+ public void Validate_PassesWhenDefaultThemeIncluded()
+ {
+ // Arrange
+ var darkTheme = Theme.Dark;
+ var lightTheme = Theme.Light;
+ var options = new ThemeSwitcherOptions()
+ .WithThemes(darkTheme, lightTheme);
+
+ // Act & Assert
+ Should.NotThrow(() => options.Validate("Dark"));
+ }
+
+ #endregion Validation Tests
+
+ #region FileProvider Tests
+
+ [Fact]
+ public void FileProvider_IsPredefinedTheme_ReturnsTrueForPredefinedThemes()
+ {
+ // Arrange & Act & Assert
+ FileProvider.IsPredefinedTheme("Dark").ShouldBeTrue();
+ FileProvider.IsPredefinedTheme("Light").ShouldBeTrue();
+ FileProvider.IsPredefinedTheme("Forest").ShouldBeTrue();
+ FileProvider.IsPredefinedTheme("DeepSea").ShouldBeTrue();
+ FileProvider.IsPredefinedTheme("Desert").ShouldBeTrue();
+ FileProvider.IsPredefinedTheme("Futuristic").ShouldBeTrue();
+ }
+
+ [Fact]
+ public void FileProvider_IsPredefinedTheme_IsCaseInsensitive()
+ {
+ // Arrange & Act & Assert
+ FileProvider.IsPredefinedTheme("dark").ShouldBeTrue();
+ FileProvider.IsPredefinedTheme("LIGHT").ShouldBeTrue();
+ FileProvider.IsPredefinedTheme("FoReSt").ShouldBeTrue();
+ }
+
+ [Fact]
+ public void FileProvider_IsPredefinedTheme_ReturnsFalseForCustomThemes()
+ {
+ // Arrange & Act & Assert
+ FileProvider.IsPredefinedTheme("Custom").ShouldBeFalse();
+ FileProvider.IsPredefinedTheme("MyTheme").ShouldBeFalse();
+ FileProvider.IsPredefinedTheme("").ShouldBeFalse();
+ FileProvider.IsPredefinedTheme("NotATheme").ShouldBeFalse();
+ }
+
+ #endregion FileProvider Tests
+
+ #region Theme Name Tests
+
+ [Fact]
+ public void Theme_GetThemeName_ReturnsCorrectNames()
+ {
+ // Arrange & Act & Assert
+ Theme.Dark.GetThemeName().ShouldBe("Dark");
+ Theme.Light.GetThemeName().ShouldBe("Light");
+ Theme.Forest.GetThemeName().ShouldBe("Forest");
+ Theme.DeepSea.GetThemeName().ShouldBe("Deepsea");
+ Theme.Desert.GetThemeName().ShouldBe("Desert");
+ Theme.Futuristic.GetThemeName().ShouldBe("Futuristic");
+ }
+
+ [Fact]
+ public void Theme_FileName_HasCorrectExtension()
+ {
+ // Arrange & Act & Assert
+ Theme.Dark.FileName.ShouldEndWith(".min.css");
+ Theme.Light.FileName.ShouldEndWith(".min.css");
+ Theme.Forest.FileName.ShouldEndWith(".min.css");
+ }
+
+ #endregion Theme Name Tests
+
+ #region Integration Scenarios
+
+ [Theory]
+ [InlineData(true, CustomThemeMode.AutoDiscover)] // All themes
+ [InlineData(true, CustomThemeMode.None)] // Predefined only
+ [InlineData(false, CustomThemeMode.AutoDiscover)] // Custom with specific predefined
+ public void Scenario_DifferentConfigurations_AreValid(bool includePredefined, CustomThemeMode customMode)
+ {
+ // Arrange
+ var options = new ThemeSwitcherOptions
+ {
+ IncludeAllPredefinedThemes = includePredefined,
+ CustomThemeMode = customMode
+ };
+
+ // Act & Assert
+ if (includePredefined || customMode != CustomThemeMode.None)
+ Should.NotThrow(() => options.Validate("Custom"));
+ }
+
+ [Fact]
+ public void Scenario_CustomOnlyWithExplicitThemes_IsValid()
+ {
+ // Arrange - Custom only with explicit themes provided
+ var customTheme1 = new TestCustomTheme("test1.css");
+ var customTheme2 = new TestCustomTheme("test2.css");
+
+ var options = new ThemeSwitcherOptions()
+ .WithThemes(customTheme1, customTheme2)
+ .WithCustomThemes(CustomThemeMode.ExplicitOnly);
+
+ // Act & Assert
+ Should.NotThrow(() => options.Validate(customTheme1.GetThemeName()));
+ }
+
+ [Fact]
+ public void Scenario_ExcludeMultipleThemes_WorksCorrectly()
+ {
+ // Arrange
+ var futuristicTheme = Theme.Futuristic;
+ var desertTheme = Theme.Desert;
+ var forestTheme = Theme.Forest;
+
+ var options = new ThemeSwitcherOptions()
+ .ExcludeThemes(futuristicTheme, desertTheme, forestTheme);
+
+ // Act
+ var excluded = options.ExcludedThemes;
+
+ // Assert
+ excluded.Count.ShouldBe(3);
+ excluded.ShouldContain(futuristicTheme);
+ excluded.ShouldContain(desertTheme);
+ excluded.ShouldContain(forestTheme);
+ }
+
+ [Fact]
+ public void Scenario_MixPredefinedAndCustom_WorksCorrectly()
+ {
+ // Arrange
+ var darkTheme = Theme.Dark;
+ var lightTheme = Theme.Light;
+ var customTheme = new TestCustomTheme("test.css");
+ var options = new ThemeSwitcherOptions()
+ .WithThemes(darkTheme, lightTheme, customTheme)
+ .WithCustomThemes(CustomThemeMode.ExplicitOnly);
+
+ // Act
+ var included = options.IncludedThemes;
+
+ // Assert
+ included.Count.ShouldBe(3);
+ included.ShouldContain(darkTheme);
+ included.ShouldContain(lightTheme);
+ included.ShouldContain(customTheme);
+ }
+
+ [Fact]
+ public void Scenario_CustomThemeModes_WorkCorrectly()
+ {
+ // Test None mode
+ var noneOptions = ThemeSwitcherOptions.PredefinedOnly();
+ noneOptions.CustomThemeMode.ShouldBe(CustomThemeMode.None);
+
+ // Test ExplicitOnly mode
+ var explicitOptions = new ThemeSwitcherOptions().WithCustomThemes(CustomThemeMode.ExplicitOnly);
+ explicitOptions.CustomThemeMode.ShouldBe(CustomThemeMode.ExplicitOnly);
+
+ // Test AutoDiscover mode (default)
+ var autoOptions = ThemeSwitcherOptions.All();
+ autoOptions.CustomThemeMode.ShouldBe(CustomThemeMode.AutoDiscover);
+ }
+
+ #endregion Integration Scenarios
+
+ private class TestCustomTheme(string fileName) : Theme(fileName);
+}
\ No newline at end of file
From acf9e6ac9d93c1c97006b6101989ad4058357fa0 Mon Sep 17 00:00:00 2001
From: Teuz
Date: Sat, 22 Nov 2025 12:07:04 +0100
Subject: [PATCH 06/18] Support nested SwaggerThemes folders + fix standalone
filename matching
- Support organizing themes in subfolders (SwaggerThemes.CompanyThemes.*, etc.)
- Fix exact filename matching: "custom.css" no longer matches "standalone.custom.css"
- Add comprehensive tests with real duplicate files
---
.../AspNetCore/Swagger/Themes/FileProvider.cs | 43 +++-
.../AspNetCore.Swagger.Themes.Tests.csproj | 4 +
.../SwaggerThemes/Custom/custom.css | 9 +
.../SwaggerThemes/Duplicates/duplicate.css | 9 +
.../SwaggerThemes/duplicate.css | 9 +
.../SwaggerThemes/standalone.test.css | 13 ++
.../ThemeProviderTests.cs | 186 ++++++++++++++++++
7 files changed, 267 insertions(+), 6 deletions(-)
create mode 100644 tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerThemes/Custom/custom.css
create mode 100644 tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerThemes/Duplicates/duplicate.css
create mode 100644 tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerThemes/duplicate.css
create mode 100644 tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerThemes/standalone.test.css
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs
index 31b0f25..baa53f2 100644
--- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs
@@ -62,14 +62,45 @@ internal static string GetResourceText(string fileName, Assembly assembly, out s
if (!IsCssFile(fileName))
throw new InvalidOperationException($"{fileName} is not a valid CSS file. Must end with '.css' or '.min.css'.");
- var resourceNamespaces = assembly.GetManifestResourceNames()
- .Where(n => n.EndsWith(_CustomStylesNamespace + fileName, StringComparison.OrdinalIgnoreCase)) // TODO: add other namespaces?
- .ToArray();
+ // Get all SwaggerThemes resources
+ var swaggerThemesResources = assembly.GetManifestResourceNames()
+ .Where(n => n.Contains(_CustomStylesNamespace, StringComparison.OrdinalIgnoreCase))
+ .Where(resourceName =>
+ {
+ // Must end with ".{fileName}" to ensure exact match
+ if (!resourceName.EndsWith("." + fileName, StringComparison.OrdinalIgnoreCase))
+ return false;
+
+ // Special case: Exclude "standalone.{fileName}" when searching for "{fileName}"
+ // This prevents "{fileName}.css" from matching "standalone.{fileName}.css"
+ var idx = resourceName.LastIndexOf(_CustomStylesNamespace, StringComparison.OrdinalIgnoreCase);
+ if (idx != -1)
+ {
+ var afterNamespace = resourceName[(idx + _CustomStylesNamespace.Length)..];
- if (resourceNamespaces.Length != 1)
- throw new InvalidOperationException($"Can't find {fileName} or it appears more than once in {assembly.GetName().Name}.");
+ if (afterNamespace.StartsWith("standalone.", StringComparison.OrdinalIgnoreCase) &&
+ !fileName.StartsWith("standalone.", StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ })
+ .ToList();
+
+ if (swaggerThemesResources.Count == 0)
+ throw new FileNotFoundException($"Can't find {fileName} in any {_CustomStylesNamespace}* namespace in {assembly.GetName().Name}.");
+
+ if (swaggerThemesResources.Count > 1)
+ {
+ var matchingPaths = string.Join(", ", swaggerThemesResources);
+ throw new InvalidOperationException(
+ $"Found {fileName} in multiple locations in {assembly.GetName().Name}: {matchingPaths}. " +
+ "Ensure the file name is unique across all theme folders.");
+ }
- var resourceName = resourceNamespaces[0];
+ var resourceName = swaggerThemesResources[0];
commonStyle = RetrieveCommonStyleFromCustom(resourceName, out loadJs);
using var stream = assembly.GetManifestResourceStream(resourceName)
diff --git a/tests/AspNetCore.SwaggerUI.Themes.Tests/AspNetCore.Swagger.Themes.Tests.csproj b/tests/AspNetCore.SwaggerUI.Themes.Tests/AspNetCore.Swagger.Themes.Tests.csproj
index 1cf3e55..45ffea8 100644
--- a/tests/AspNetCore.SwaggerUI.Themes.Tests/AspNetCore.Swagger.Themes.Tests.csproj
+++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/AspNetCore.Swagger.Themes.Tests.csproj
@@ -12,7 +12,11 @@
+
+
+
+
diff --git a/tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerThemes/Custom/custom.css b/tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerThemes/Custom/custom.css
new file mode 100644
index 0000000..9e8a02e
--- /dev/null
+++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerThemes/Custom/custom.css
@@ -0,0 +1,9 @@
+/*
+ Test Custom Theme
+
+ https://github.com/teociaps/SwaggerUI.Themes
+*/
+
+body {
+ background-color: var(--body-background-color, #fafafa);
+}
diff --git a/tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerThemes/Duplicates/duplicate.css b/tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerThemes/Duplicates/duplicate.css
new file mode 100644
index 0000000..e40fd5c
--- /dev/null
+++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerThemes/Duplicates/duplicate.css
@@ -0,0 +1,9 @@
+/*
+ Duplicate Theme - Location B
+
+ https://github.com/teociaps/SwaggerUI.Themes
+*/
+
+body {
+ background-color: #duplicate-b;
+}
diff --git a/tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerThemes/duplicate.css b/tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerThemes/duplicate.css
new file mode 100644
index 0000000..279e918
--- /dev/null
+++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerThemes/duplicate.css
@@ -0,0 +1,9 @@
+/*
+ Duplicate Theme - Location A
+
+ https://github.com/teociaps/SwaggerUI.Themes
+*/
+
+body {
+ background-color: #duplicate-a;
+}
diff --git a/tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerThemes/standalone.test.css b/tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerThemes/standalone.test.css
new file mode 100644
index 0000000..8e55f72
--- /dev/null
+++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/SwaggerThemes/standalone.test.css
@@ -0,0 +1,13 @@
+/*
+ Standalone Test Theme
+
+ https://github.com/teociaps/SwaggerUI.Themes
+*/
+
+:root {
+ --test-standalone-color: #ff0000;
+}
+
+body {
+ background-color: var(--test-standalone-color);
+}
diff --git a/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeProviderTests.cs b/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeProviderTests.cs
index 257f5a9..5609bba 100644
--- a/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeProviderTests.cs
+++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeProviderTests.cs
@@ -176,4 +176,190 @@ public async Task AddGetEndpoint_ShouldReturnStyleContent_WhenWebApplication(Bas
response.StatusCode.ShouldBe(System.Net.HttpStatusCode.OK);
(await response.Content.ReadAsStringAsync()).ShouldBeEquivalentTo(styleText);
}
+
+ [Fact]
+ public void GetResourceText_ShouldFindThemeInSubfolder()
+ {
+ // Arrange
+ const string FileName = "custom.css"; // Exists in SwaggerThemes.Custom folder
+
+ // Act
+ var styleContent = GetResourceText(FileName, Assembly.GetExecutingAssembly(), out var commonStyle, out var loadJs);
+
+ // Assert
+ styleContent.ShouldNotBeEmpty();
+ styleContent.ShouldContain("Test Custom Theme");
+ commonStyle.ShouldNotBeEmpty();
+ loadJs.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void GetResourceText_ShouldThrowFileNotFoundException_WhenFileNotInAnySubfolder()
+ {
+ // Arrange
+ const string NonExistentFileName = "nonexistent-theme.css";
+ var assembly = Assembly.GetExecutingAssembly();
+
+ // Act & Assert
+ var exception = Should.Throw(() =>
+ GetResourceText(NonExistentFileName, assembly, out _, out _));
+
+ exception.Message.ShouldContain("Can't find");
+ exception.Message.ShouldContain("SwaggerThemes.*");
+ exception.Message.ShouldContain(NonExistentFileName);
+ }
+
+ [Theory]
+ [InlineData("custom.css", "SwaggerThemes")]
+ [InlineData("standalone.theme.css", "SwaggerThemes")]
+ [InlineData("custom.theme.css", "SwaggerThemes")]
+ public void GetResourceText_ShouldFindTheme_InAnySwaggerThemesNamespace(string fileName, string expectedNamespacePrefix)
+ {
+ // Arrange
+ var assembly = Assembly.GetExecutingAssembly();
+
+ // Act
+ var styleContent = GetResourceText(fileName, assembly, out _, out _);
+
+ // Assert
+ styleContent.ShouldNotBeEmpty();
+
+ // Verify the file was found in a SwaggerThemes.* namespace
+ var resourceNames = assembly.GetManifestResourceNames()
+ .Where(n => n.Contains(expectedNamespacePrefix) && n.EndsWith(fileName))
+ .ToList();
+
+ resourceNames.ShouldNotBeEmpty();
+ resourceNames.ShouldHaveSingleItem();
+ }
+
+ [Fact]
+ public void GetResourceText_ShouldBeFlexible_WithNestedFolderStructure()
+ {
+ // This test verifies that the search works regardless of folder depth
+ // Examples of valid structures:
+ // - SwaggerThemes.custom.css
+ // - SwaggerThemes.Custom.custom.css
+
+ // Arrange
+ const string FileName = "custom.css";
+ var assembly = Assembly.GetExecutingAssembly();
+
+ // Act
+ var result = GetResourceText(FileName, assembly, out var commonStyle, out var loadJs);
+
+ // Assert
+ result.ShouldNotBeEmpty();
+ // The function should find it regardless of how deep in the folder structure it is
+ }
+
+ [Theory]
+ [InlineData("custom.css", true)]
+ [InlineData("standalone.theme.css", false)]
+ [InlineData("custom.theme.css", true)]
+ public void GetResourceText_ShouldCorrectlyDetectStandalone_InSubfolders(string fileName, bool shouldLoadCommon)
+ {
+ // Arrange
+ var assembly = Assembly.GetExecutingAssembly();
+
+ // Act
+ GetResourceText(fileName, assembly, out var commonStyle, out var loadJs);
+
+ // Assert
+ if (shouldLoadCommon)
+ {
+ commonStyle.ShouldNotBeEmpty("Non-standalone themes should load common.css");
+ loadJs.ShouldBeTrue("Non-standalone themes should enable JS");
+ }
+ else
+ {
+ commonStyle.ShouldBeEmpty("Standalone themes should NOT load common.css");
+ loadJs.ShouldBeFalse("Standalone themes should NOT enable JS");
+ }
+ }
+
+ [Fact]
+ public void GetResourceText_ShouldWorkWith_RealWorldFolderStructure()
+ {
+ // This test validates the real-world usage pattern from samples
+ // Sample folder structure:
+ // SwaggerThemes/
+ // Custom/
+ // custom.css
+ // standalone.theme.css
+
+ // Arrange
+ var assembly = Assembly.GetExecutingAssembly();
+
+ // Act & Assert - should find files in any subfolder
+ var customCss = GetResourceText("custom.css", assembly, out _, out _);
+ customCss.ShouldNotBeEmpty();
+
+ var standaloneCss = GetResourceText("standalone.theme.css", assembly, out var standaloneCommon, out var standaloneJs);
+ standaloneCss.ShouldNotBeEmpty();
+ standaloneCommon.ShouldBeEmpty();
+ standaloneJs.ShouldBeFalse();
+ }
+
+ [Fact]
+ public void GetResourceText_ShouldThrowInvalidOperationException_WhenMultipleFilesWithSameName()
+ {
+ // Intentionally created duplicate files:
+ // - SwaggerThemes/duplicate.css
+ // - SwaggerThemes/Duplicates/duplicate.css
+
+ // Arrange
+ var assembly = Assembly.GetExecutingAssembly();
+
+ // Verify both files exist
+ var allResources = assembly.GetManifestResourceNames()
+ .Where(n => n.EndsWith(".duplicate.css"))
+ .ToList();
+
+ allResources.Count.ShouldBe(2, "Should have exactly 2 duplicate.css files for this test");
+
+ // Act & Assert - Should throw InvalidOperationException with clear error message
+ var exception = Should.Throw(() =>
+ GetResourceText("duplicate.css", assembly, out _, out _));
+
+ // Verify error message is helpful
+ exception.Message.ShouldContain("Found duplicate.css in multiple locations");
+ exception.Message.ShouldContain("SwaggerThemes.duplicate.css");
+ exception.Message.ShouldContain("SwaggerThemes.Duplicates.duplicate.css");
+ exception.Message.ShouldContain("Ensure the file name is unique across all theme folders");
+ }
+
+ [Fact]
+ public void GetResourceText_ShouldNotMatch_StandalonePrefix_WhenSearchingNonStandalone()
+ {
+ // Arrange
+ var assembly = Assembly.GetExecutingAssembly();
+
+ // Verify standalone.test.css exists
+ var standaloneExists = assembly.GetManifestResourceNames()
+ .Any(n => n.EndsWith(".standalone.test.css"));
+ standaloneExists.ShouldBeTrue("standalone.test.css should exist in test assembly");
+
+ // Act & Assert - Searching for "test.css" should not find "standalone.test.css"
+ var exception = Should.Throw(() =>
+ GetResourceText("test.css", assembly, out _, out _));
+
+ exception.Message.ShouldContain("Can't find test.css");
+ }
+
+ [Fact]
+ public void GetResourceText_ShouldMatch_StandaloneFile_WhenExplicitlySearching()
+ {
+ // Arrange
+ var assembly = Assembly.GetExecutingAssembly();
+
+ // Act - Explicitly search for "standalone.theme.css"
+ var content = GetResourceText("standalone.theme.css", assembly, out var commonStyle, out var loadJs);
+
+ // Assert
+ content.ShouldNotBeEmpty();
+ content.ShouldContain("Test Standalone Theme");
+ commonStyle.ShouldBeEmpty("Standalone themes don't load common.css");
+ loadJs.ShouldBeFalse("Standalone themes don't load JS");
+ }
}
\ No newline at end of file
From e133e41c814de14d2a58f0d3d5b12a9a5d42d4b4 Mon Sep 17 00:00:00 2001
From: Teuz
Date: Sat, 22 Nov 2025 14:28:24 +0100
Subject: [PATCH 07/18] Fix theme switcher to correctly handle assembly-loaded
css
---
.../Program.cs | 28 +--
.../Sample.AspNetCore.SwaggerUI.NSwag.csproj | 1 +
.../SwaggerThemes/Custom/mybrand.css | 174 ++++++++++++++++++
.../Program.cs | 29 +--
...le.AspNetCore.SwaggerUI.Swashbuckle.csproj | 1 +
.../SwaggerThemes/Custom/mybrand.css | 174 ++++++++++++++++++
.../AspNetCore/Swagger/Themes/DynamicTheme.cs | 19 ++
.../Swagger/Themes/ThemeSwitcher.cs | 5 -
.../Builder/SwaggerUIBuilderExtensions.cs | 72 ++++----
.../Builder/NSwagBuilderExtensions.cs | 40 ++--
10 files changed, 452 insertions(+), 91 deletions(-)
create mode 100644 samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/mybrand.css
create mode 100644 samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/mybrand.css
create mode 100644 src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/DynamicTheme.cs
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs
index 33623ab..2ff5e0f 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs
+++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs
@@ -1,5 +1,5 @@
-using AspNetCore.Swagger.Themes;
-using Sample.AspNetCore.SwaggerUI.NSwag;
+using Sample.AspNetCore.SwaggerUI.NSwag;
+using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
@@ -30,11 +30,11 @@
//});
// 3. Custom theme from embedded resource
- //app.UseSwaggerUi(Assembly.GetExecutingAssembly(), "custom.css", c =>
- //{
- // c.DocumentTitle = "Sample API - Custom Theme from Assembly";
- // c.EnableAllAdvancedOptions(); // Enables theme switcher + all UI features
- //});
+ app.UseSwaggerUi(Assembly.GetExecutingAssembly(), "mybrand.css", c =>
+ {
+ c.DocumentTitle = "Sample API - Custom Theme from Assembly";
+ c.EnableAllAdvancedOptions(); // Enables theme switcher + all UI features
+ });
// 4. Standalone theme (no dependencies on common.css or ui.js)
//app.UseSwaggerUi(Assembly.GetExecutingAssembly(), "standalone.custom.css", c =>
@@ -183,13 +183,13 @@
//});
// 18. Minimal configuration (just 2 themes)
- app.UseSwaggerUi(Theme.Dark, c =>
- {
- c.DocumentTitle = "Sample API - Minimal";
- c.EnableThemeSwitcher(new ThemeSwitcherOptions()
- .WithThemes(Theme.Dark, Theme.Light)
- .WithCustomThemes(CustomThemeMode.None));
- });
+ //app.UseSwaggerUi(Theme.Dark, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Minimal";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithThemes(Theme.Dark, Theme.Light)
+ // .WithCustomThemes(CustomThemeMode.None));
+ //});
}
app.UseHttpsRedirection();
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Sample.AspNetCore.SwaggerUI.NSwag.csproj b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Sample.AspNetCore.SwaggerUI.NSwag.csproj
index 035ed8f..ef4ed4a 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Sample.AspNetCore.SwaggerUI.NSwag.csproj
+++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Sample.AspNetCore.SwaggerUI.NSwag.csproj
@@ -21,6 +21,7 @@
+
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/mybrand.css b/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/mybrand.css
new file mode 100644
index 0000000..ead96b4
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/mybrand.css
@@ -0,0 +1,174 @@
+/*
+ Custom Style
+
+ https://github.com/teociaps/SwaggerUI.Themes
+*/
+
+:root {
+ --body-background-color: #120e1f;
+ --swagger-main-color: white;
+ /* Loading */
+ --loading-container-border-color: rgba(200, 200, 200, .1);
+ --loading-container-border-top-color: rgba(255, 255, 255, .6);
+ /* Errors container */
+ --errors-wrapper-background-color: rgb(255, 0, 0, .1);
+ --errors-wrapper-border-color: #f93e3e;
+ --errors-wrapper-errors-color: #606060;
+ /* Top-bar */
+ --topbar-background-color: #161629;
+ --topbar-select-border-color: #4990e2;
+ --topbar-select-label-color: #bfbfbf;
+ --topbar-download-url-button-background-color: #5b5b5b;
+ --topbar-download-url-button-color: #dfd9d9;
+ /* Info-box */
+ --swagger-info-link: #4990e2;
+ --swagger-info-link-hover: #1b78e5;
+ --api-version-background-color: #616161;
+ --api-version-stamp-background-color: #23891f;
+ --api-version-color: #cdcdcd;
+ /* Authorize Section/Modal */
+ --scheme-container-background-color: #12173b;
+ --scheme-container-box-shadow-color: rgba(67, 67, 67, 0.3);
+ --auth-container-border-bottom-color: #ebebeb;
+ --auth-container-background-color: #ddd;
+ --auth-container-errors-color: red;
+ --btn-authorize-background-color: transparent;
+ --btn-authorize-border-color: #ecf0f1;
+ --btn-authorize-font-color: #ecf0f1;
+ --btn-authorize-svg-fill-color: #ecf0f1;
+ /* Operations */
+ --opblock-tag-background-color-hover: rgba(0, 0, 0, .1);
+ --opblock-tag-border-bottom-color: rgba(59, 65, 81, .3);
+ --opblock-border-color: black;
+ --opblock-shadow-color: rgba(59, 59, 59, .19);
+ --opblock-tabheader-underline-color: rgba(255, 255, 255, .6);
+ --opblock-summary-svg-icons-color: white;
+ --opblock-summary-border-bottom-color: black;
+ --opblock-section-header-background-color: #0000004a;
+ --opblock-section-header-shadow-color: rgba(0, 0, 0, .1);
+ --opblock-summary-method-background-color: black;
+ --opblock-summary-method-color: white;
+ --opblock-summary-method-shadow-color: rgba(0, 0, 0, .1);
+ /*POST*/
+ --opblock-post-background-color: rgba(56, 203, 82, .1);
+ --opblock-post-method-color: #49cc90;
+ --opblock-post-border-color: #49cc90;
+ /*PUT*/
+ --opblock-put-background-color: rgba(255, 189, 63, .1);
+ --opblock-put-method-color: #eb9532;
+ --opblock-put-border-color: #eb9532;
+ /*DELETE*/
+ --opblock-delete-background-color: rgba(255, 4, 4, .1);
+ --opblock-delete-method-color: #c0392b;
+ --opblock-delete-border-color: #c0392b;
+ /*GET*/
+ --opblock-get-background-color: rgb(33, 161, 255, .1);
+ --opblock-get-method-color: #61affe;
+ --opblock-get-border-color: #61affe;
+ /*PATCH*/
+ --opblock-patch-background-color: rgba(255, 0, 165, .1);
+ --opblock-patch-method-color: #9b59b6;
+ --opblock-patch-border-color: #9b59b6;
+ /*HEAD*/
+ --opblock-head-background-color: rgba(62, 73, 114, .1);
+ --opblock-head-method-color: #3e4972;
+ --opblock-head-border-color: #3e4972;
+ /*OPTIONS*/
+ --opblock-options-background-color: rgba(37, 64, 211, .1);
+ --opblock-options-method-color: #263795;
+ --opblock-options-border-color: #263795;
+ /*Deprecated*/
+ --opblock-deprecated-background-color: hsla(0, 0%, 87%, .1);
+ --opblock-deprecated-method-color: #a59595;
+ --opblock-deprecated-border-color: #a59595;
+ /* Tabs */
+ --swagger-tabs-divider-color: gray;
+ /* Other of operations */
+ --response-undocumented-color: #999;
+ --response-control-media-type-color: green;
+ --opblock-pre-microlight-background-color: #333;
+ --opblock-pre-microlight-color: #ebe3e3;
+ --download-contents-background-color: #7d8293;
+ --download-contents-color: white;
+ --copy-to-clipboard-background-color: #7d8293;
+ --copy-to-clipboard-icon: url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;
+ --filter-operation-filter-input-border-color: #d8dde7;
+ --filter-download-failed-color: red;
+ --filter-download-loading-color: #aaa;
+ --table-headers-example-color: #999;
+ --table-thead-border-bottom-color: rgba(59, 65, 81, .2);
+ --parameter-name-required-symbol-color: red;
+ --parameter-name-required-color: rgba(255, 0, 0, .8);
+ --parameter-in-extension-color: #878585;
+ --parameter-deprecated-color: red;
+ /* Buttons */
+ --button-background-color: transparent;
+ --button-border-color: #a1a1a1;
+ --button-shadow-color: rgba(0, 0, 0, .1);
+ --button-shadow-hover-color: rgba(0, 0, 0, .3);
+ --button-cancel-background-color: transparent;
+ --button-cancel-border-color: #ff6060;
+ --button-cancel-color: #ff6060;
+ --button-execute-background-color: #4990e2;
+ --button-execute-border-color: #5fa9ff;
+ --button-execute-color: white;
+ --button-invalid-background-color: #feebeb;
+ --button-invalid-border-color: #f93e3e;
+ /* Expand operation icon */
+ --expand-operation-svg-arrow-color: #aaa;
+ --expand-operation-svg-arrow-hover-color: white;
+ /* Form elements */
+ --select-background-icon: #0c0c0c url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat;
+ --select-border-color: #41444e;
+ --select-shadow-color: rgba(0, 0, 0, .25);
+ --input-background-color: #0c0c0c;
+ --input-border-color: #6d6d6d;
+ --input-invalid-background-color: #210101;
+ --input-invalid-border-color: #f93e3e;
+ --input-invalid-color: #f3ebeb;
+ --input-disabled-background-color: #333333;
+ --input-disabled-color: #757575;
+ --select-disabled-border-color: #888;
+ --textarea-disabled-background-color: #2b2b2b;
+ --textarea-disabled-color: #b3b3b3;
+ --textarea-background-color: hsla(0, 0%, 0%, .8);
+ --textarea-border-focus-color: #a5a5a5;
+ --textarea-curl-background-color: #41444e;
+ --textarea-curl-color: #fff;
+ --checkbox-color: #303030;
+ --checkbox-label-item-background-color: #e8e8e8;
+ --checkbox-label-item-shadow-color: #e8e8e8;
+ --checkbox-label-item-icon: #e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;
+ /* Dialog */
+ --dialog-backdrop-color: rgba(0, 0, 0, .8);
+ --dialog-background-color: #161616;
+ --dialog-border-color: #333436;
+ --dialog-shadow-color: rgba(0, 0, 0, .2);
+ --dialog-close-button-icon-color: #fff;
+ /* Models */
+ --model-deprecated-color: #a0a0a0;
+ --model-arrow-icon: url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;
+ --model-hint-background-color: rgba(0, 0, 0, .7);
+ --model-hint-color: #ebebeb;
+ --model-property-color: #999;
+ --model-property-primitive-color: #676767;
+ --model-property-required-symbol-color: red;
+ --model-property-description: #b9b9b9;
+ --model-property-extension: #8d8a8a;
+ --model-section-border-color: rgba(96, 96, 96, .3);
+ --model-section-header-color: white;
+ --model-section-header-hover-background-color: rgba(0, 0, 0, .02);
+ --expand-model-svg-arrow-color: white;
+ --model-section-little-header-color: #707070;
+ --model-container-background-color: rgba(20, 20, 20, .1);
+ --model-container-hover-background-color: rgba(0, 0, 0, .1);
+ --model-box-background-color: rgba(0, 0, 0, .15);
+ --model-title-color: #bec6cf;
+ --model-deprecated-warning-color: #f93e3e;
+ --model-prop-type-color: #6464db;
+ --model-prop-format-color: #b7b7b7;
+ /* Rendered Markdown */
+ --rendered-markdown-pre-color: #000;
+ --rendered-markdown-code-background-color: rgba(0, 0, 0, .05);
+ --rendered-markdown-code-color: #9012fe;
+}
\ No newline at end of file
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs
index 9010f7e..78f9acf 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs
@@ -1,5 +1,6 @@
using AspNetCore.Swagger.Themes;
using Sample.AspNetCore.SwaggerUI.Swashbuckle;
+using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
@@ -30,11 +31,11 @@
//});
// 3. Custom theme from embedded resource
- //app.UseSwaggerUI(Assembly.GetExecutingAssembly(), "custom.css", c =>
- //{
- // c.DocumentTitle = "Sample API - Custom Theme from Assembly";
- // c.EnableAllAdvancedOptions(); // Enables theme switcher + all UI features
- //});
+ app.UseSwaggerUI(Assembly.GetExecutingAssembly(), "mybrand.css", c =>
+ {
+ c.DocumentTitle = "Sample API - Custom Theme from Assembly";
+ c.EnableAllAdvancedOptions(ThemeSwitcherOptions.CustomOnly()); // Enables theme switcher + all UI features
+ });
// 4. Standalone theme (no dependencies on common.css or ui.js)
//app.UseSwaggerUI(Assembly.GetExecutingAssembly(), "standalone.custom.css", c =>
@@ -173,15 +174,15 @@
//});
// 17. Complex filtering
- app.UseSwaggerUI(Theme.Forest, c =>
- {
- c.DocumentTitle = "Sample API - Complex Filtering";
- c.EnableAllAdvancedOptions();
- c.EnableThemeSwitcher(new ThemeSwitcherOptions()
- .WithAllPredefinedThemes(true) // Include all 6 predefined
- .ExcludeThemes(Theme.Futuristic) // But exclude Futuristic
- .WithCustomThemes(CustomThemeMode.AutoDiscover)); // Auto-discover custom
- });
+ //app.UseSwaggerUI(Theme.Forest, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Complex Filtering";
+ // c.EnableAllAdvancedOptions();
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithAllPredefinedThemes(true) // Include all 6 predefined
+ // .ExcludeThemes(Theme.Futuristic) // But exclude Futuristic
+ // .WithCustomThemes(CustomThemeMode.AutoDiscover)); // Auto-discover custom
+ //});
// 18. Minimal configuration (just 2 themes)
//app.UseSwaggerUI(Theme.Dark, c =>
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj
index b051984..c0f96d3 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj
@@ -23,6 +23,7 @@
+
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/mybrand.css b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/mybrand.css
new file mode 100644
index 0000000..ead96b4
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/mybrand.css
@@ -0,0 +1,174 @@
+/*
+ Custom Style
+
+ https://github.com/teociaps/SwaggerUI.Themes
+*/
+
+:root {
+ --body-background-color: #120e1f;
+ --swagger-main-color: white;
+ /* Loading */
+ --loading-container-border-color: rgba(200, 200, 200, .1);
+ --loading-container-border-top-color: rgba(255, 255, 255, .6);
+ /* Errors container */
+ --errors-wrapper-background-color: rgb(255, 0, 0, .1);
+ --errors-wrapper-border-color: #f93e3e;
+ --errors-wrapper-errors-color: #606060;
+ /* Top-bar */
+ --topbar-background-color: #161629;
+ --topbar-select-border-color: #4990e2;
+ --topbar-select-label-color: #bfbfbf;
+ --topbar-download-url-button-background-color: #5b5b5b;
+ --topbar-download-url-button-color: #dfd9d9;
+ /* Info-box */
+ --swagger-info-link: #4990e2;
+ --swagger-info-link-hover: #1b78e5;
+ --api-version-background-color: #616161;
+ --api-version-stamp-background-color: #23891f;
+ --api-version-color: #cdcdcd;
+ /* Authorize Section/Modal */
+ --scheme-container-background-color: #12173b;
+ --scheme-container-box-shadow-color: rgba(67, 67, 67, 0.3);
+ --auth-container-border-bottom-color: #ebebeb;
+ --auth-container-background-color: #ddd;
+ --auth-container-errors-color: red;
+ --btn-authorize-background-color: transparent;
+ --btn-authorize-border-color: #ecf0f1;
+ --btn-authorize-font-color: #ecf0f1;
+ --btn-authorize-svg-fill-color: #ecf0f1;
+ /* Operations */
+ --opblock-tag-background-color-hover: rgba(0, 0, 0, .1);
+ --opblock-tag-border-bottom-color: rgba(59, 65, 81, .3);
+ --opblock-border-color: black;
+ --opblock-shadow-color: rgba(59, 59, 59, .19);
+ --opblock-tabheader-underline-color: rgba(255, 255, 255, .6);
+ --opblock-summary-svg-icons-color: white;
+ --opblock-summary-border-bottom-color: black;
+ --opblock-section-header-background-color: #0000004a;
+ --opblock-section-header-shadow-color: rgba(0, 0, 0, .1);
+ --opblock-summary-method-background-color: black;
+ --opblock-summary-method-color: white;
+ --opblock-summary-method-shadow-color: rgba(0, 0, 0, .1);
+ /*POST*/
+ --opblock-post-background-color: rgba(56, 203, 82, .1);
+ --opblock-post-method-color: #49cc90;
+ --opblock-post-border-color: #49cc90;
+ /*PUT*/
+ --opblock-put-background-color: rgba(255, 189, 63, .1);
+ --opblock-put-method-color: #eb9532;
+ --opblock-put-border-color: #eb9532;
+ /*DELETE*/
+ --opblock-delete-background-color: rgba(255, 4, 4, .1);
+ --opblock-delete-method-color: #c0392b;
+ --opblock-delete-border-color: #c0392b;
+ /*GET*/
+ --opblock-get-background-color: rgb(33, 161, 255, .1);
+ --opblock-get-method-color: #61affe;
+ --opblock-get-border-color: #61affe;
+ /*PATCH*/
+ --opblock-patch-background-color: rgba(255, 0, 165, .1);
+ --opblock-patch-method-color: #9b59b6;
+ --opblock-patch-border-color: #9b59b6;
+ /*HEAD*/
+ --opblock-head-background-color: rgba(62, 73, 114, .1);
+ --opblock-head-method-color: #3e4972;
+ --opblock-head-border-color: #3e4972;
+ /*OPTIONS*/
+ --opblock-options-background-color: rgba(37, 64, 211, .1);
+ --opblock-options-method-color: #263795;
+ --opblock-options-border-color: #263795;
+ /*Deprecated*/
+ --opblock-deprecated-background-color: hsla(0, 0%, 87%, .1);
+ --opblock-deprecated-method-color: #a59595;
+ --opblock-deprecated-border-color: #a59595;
+ /* Tabs */
+ --swagger-tabs-divider-color: gray;
+ /* Other of operations */
+ --response-undocumented-color: #999;
+ --response-control-media-type-color: green;
+ --opblock-pre-microlight-background-color: #333;
+ --opblock-pre-microlight-color: #ebe3e3;
+ --download-contents-background-color: #7d8293;
+ --download-contents-color: white;
+ --copy-to-clipboard-background-color: #7d8293;
+ --copy-to-clipboard-icon: url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;
+ --filter-operation-filter-input-border-color: #d8dde7;
+ --filter-download-failed-color: red;
+ --filter-download-loading-color: #aaa;
+ --table-headers-example-color: #999;
+ --table-thead-border-bottom-color: rgba(59, 65, 81, .2);
+ --parameter-name-required-symbol-color: red;
+ --parameter-name-required-color: rgba(255, 0, 0, .8);
+ --parameter-in-extension-color: #878585;
+ --parameter-deprecated-color: red;
+ /* Buttons */
+ --button-background-color: transparent;
+ --button-border-color: #a1a1a1;
+ --button-shadow-color: rgba(0, 0, 0, .1);
+ --button-shadow-hover-color: rgba(0, 0, 0, .3);
+ --button-cancel-background-color: transparent;
+ --button-cancel-border-color: #ff6060;
+ --button-cancel-color: #ff6060;
+ --button-execute-background-color: #4990e2;
+ --button-execute-border-color: #5fa9ff;
+ --button-execute-color: white;
+ --button-invalid-background-color: #feebeb;
+ --button-invalid-border-color: #f93e3e;
+ /* Expand operation icon */
+ --expand-operation-svg-arrow-color: #aaa;
+ --expand-operation-svg-arrow-hover-color: white;
+ /* Form elements */
+ --select-background-icon: #0c0c0c url('data:image/svg+xml;charset=utf-8,') right 10px center no-repeat;
+ --select-border-color: #41444e;
+ --select-shadow-color: rgba(0, 0, 0, .25);
+ --input-background-color: #0c0c0c;
+ --input-border-color: #6d6d6d;
+ --input-invalid-background-color: #210101;
+ --input-invalid-border-color: #f93e3e;
+ --input-invalid-color: #f3ebeb;
+ --input-disabled-background-color: #333333;
+ --input-disabled-color: #757575;
+ --select-disabled-border-color: #888;
+ --textarea-disabled-background-color: #2b2b2b;
+ --textarea-disabled-color: #b3b3b3;
+ --textarea-background-color: hsla(0, 0%, 0%, .8);
+ --textarea-border-focus-color: #a5a5a5;
+ --textarea-curl-background-color: #41444e;
+ --textarea-curl-color: #fff;
+ --checkbox-color: #303030;
+ --checkbox-label-item-background-color: #e8e8e8;
+ --checkbox-label-item-shadow-color: #e8e8e8;
+ --checkbox-label-item-icon: #e8e8e8 url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;
+ /* Dialog */
+ --dialog-backdrop-color: rgba(0, 0, 0, .8);
+ --dialog-background-color: #161616;
+ --dialog-border-color: #333436;
+ --dialog-shadow-color: rgba(0, 0, 0, .2);
+ --dialog-close-button-icon-color: #fff;
+ /* Models */
+ --model-deprecated-color: #a0a0a0;
+ --model-arrow-icon: url('data:image/svg+xml;charset=utf-8,') 50% no-repeat;
+ --model-hint-background-color: rgba(0, 0, 0, .7);
+ --model-hint-color: #ebebeb;
+ --model-property-color: #999;
+ --model-property-primitive-color: #676767;
+ --model-property-required-symbol-color: red;
+ --model-property-description: #b9b9b9;
+ --model-property-extension: #8d8a8a;
+ --model-section-border-color: rgba(96, 96, 96, .3);
+ --model-section-header-color: white;
+ --model-section-header-hover-background-color: rgba(0, 0, 0, .02);
+ --expand-model-svg-arrow-color: white;
+ --model-section-little-header-color: #707070;
+ --model-container-background-color: rgba(20, 20, 20, .1);
+ --model-container-hover-background-color: rgba(0, 0, 0, .1);
+ --model-box-background-color: rgba(0, 0, 0, .15);
+ --model-title-color: #bec6cf;
+ --model-deprecated-warning-color: #f93e3e;
+ --model-prop-type-color: #6464db;
+ --model-prop-format-color: #b7b7b7;
+ /* Rendered Markdown */
+ --rendered-markdown-pre-color: #000;
+ --rendered-markdown-code-background-color: rgba(0, 0, 0, .05);
+ --rendered-markdown-code-color: #9012fe;
+}
\ No newline at end of file
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/DynamicTheme.cs b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/DynamicTheme.cs
new file mode 100644
index 0000000..ec7825c
--- /dev/null
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/DynamicTheme.cs
@@ -0,0 +1,19 @@
+namespace AspNetCore.Swagger.Themes;
+
+///
+/// Represents a dynamically created theme from a CSS file loaded at runtime.
+/// Used when loading themes via Assembly.GetExecutingAssembly() to enable theme switcher functionality.
+///
+///
+/// Creates a new dynamic theme instance.
+///
+/// The CSS filename.
+/// Whether this is a standalone theme.
+internal sealed class DynamicTheme(string fileName, bool isStandalone, bool useMinified = false) : BaseTheme(fileName, useMinified)
+{
+ private readonly bool _isStandalone = isStandalone;
+
+ internal override BaseTheme Common => _isStandalone ? this : new("common.css", false, true);
+
+ internal override bool LoadAdditionalJs => !_isStandalone;
+}
\ No newline at end of file
diff --git a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeSwitcher.cs b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeSwitcher.cs
index ca6358d..8bfa66c 100644
--- a/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeSwitcher.cs
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeSwitcher.cs
@@ -220,11 +220,6 @@ private static void ScanTypeForThemes(Type type)
private static bool IsStandaloneTheme(BaseTheme theme) =>
theme.FileName.Contains("standalone", StringComparison.OrdinalIgnoreCase);
- ///
- /// Gets all predefined themes.
- ///
- internal static IEnumerable GetAllPredefinedThemes() => s_predefinedThemes;
-
///
/// Gets available themes (excluding current theme).
///
diff --git a/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIBuilderExtensions.cs b/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIBuilderExtensions.cs
index af1269c..20b5ef7 100644
--- a/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIBuilderExtensions.cs
+++ b/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIBuilderExtensions.cs
@@ -26,7 +26,7 @@ public static IApplicationBuilder UseSwaggerUI(
ArgumentNullException.ThrowIfNull(theme);
options ??= new SwaggerUIOptions();
- ConfigureTheme(application, options, theme).Invoke(options);
+ ConfigureTheme(application, theme).Invoke(options);
return application.UseSwaggerUI(options);
}
@@ -67,11 +67,11 @@ public static IApplicationBuilder UseSwaggerUI(
{
ArgumentNullException.ThrowIfNull(cssThemeContent);
- const string customPath = $"{FileProvider.StylesPath}custom.css";
- FileProvider.AddGetEndpoint(application, customPath, cssThemeContent);
+ const string CustomPath = $"{FileProvider.StylesPath}custom.css";
+ FileProvider.AddGetEndpoint(application, CustomPath, cssThemeContent);
ThemeSwitcher.RegisterCustomTheme("custom.css", isStandalone: true);
- setupAction += options => options.InjectStylesheet(customPath);
+ setupAction += options => options.InjectStylesheet(CustomPath);
return application.UseSwaggerUI(setupAction);
}
@@ -93,35 +93,38 @@ public static IApplicationBuilder UseSwaggerUI(
ArgumentNullException.ThrowIfNull(assembly);
ArgumentNullException.ThrowIfNull(cssFilename);
- var options = new SwaggerUIOptions();
- setupAction?.Invoke(options);
+ return application.UseSwaggerUI(opt =>
+ {
+ setupAction?.Invoke(opt);
- var (themeContent, commonContent, loadJs, isStandalone, themeName) =
- ThemeBuilderHelpers.LoadAssemblyTheme(assembly, cssFilename);
+ var (themeContent, commonContent, loadJs, isStandalone, themeName) = ThemeBuilderHelpers.LoadAssemblyTheme(assembly, cssFilename);
- var customPath = FileProvider.StylesPath + cssFilename;
- ThemeSwitcher.RegisterCustomTheme(cssFilename, isStandalone);
+ // Create a dynamic BaseTheme instance for this CSS file
+ // This allows it to be discovered and included in the theme switcher
+ var dynamicTheme = new DynamicTheme(cssFilename, isStandalone, cssFilename.EndsWith(".min.css"));
- if (!isStandalone)
- {
- commonContent = AdvancedOptions.Apply(commonContent, options.ConfigObject.AdditionalItems, MimeTypes.Text.Css);
+ if (!isStandalone)
+ {
+ // Register the theme for auto-discovery
+ ThemeSwitcher.RegisterTheme(dynamicTheme, cssFilename, isStandalone);
- const string commonPath = $"{FileProvider.StylesPath}common.css";
- FileProvider.AddGetEndpoint(application, commonPath, commonContent);
- setupAction += opt => opt.InjectStylesheet(commonPath);
+ commonContent = AdvancedOptions.Apply(commonContent, opt.ConfigObject.AdditionalItems, MimeTypes.Text.Css);
- if (loadJs && AdvancedOptions.AnyJsFeatureEnabled(options.ConfigObject.AdditionalItems))
- {
- setupAction += InjectJavascript(application, options);
- setupAction += opt => ThemeBuilderHelpers.ConfigureCustomThemeWithSwitcher(
- application, themeName, isStandalone, options.ConfigObject.AdditionalItems, opt.GetThemeSwitcherOptions());
- }
- }
+ const string CommonPath = $"{FileProvider.StylesPath}common.css";
+ FileProvider.AddGetEndpoint(application, CommonPath, commonContent);
+ opt.InjectStylesheet(CommonPath);
- FileProvider.AddGetEndpoint(application, customPath, themeContent);
- setupAction += opt => opt.InjectStylesheet(customPath);
+ if (loadJs && AdvancedOptions.AnyJsFeatureEnabled(opt.ConfigObject.AdditionalItems))
+ {
+ InjectJavascriptInternal(application, opt);
+ ConfigureThemeSwitcher(application, opt, dynamicTheme);
+ }
+ }
- return application.UseSwaggerUI(setupAction);
+ var customPath = FileProvider.StylesPath + cssFilename;
+ FileProvider.AddGetEndpoint(application, customPath, themeContent);
+ opt.InjectStylesheet(customPath);
+ });
}
private static void ConfigureThemeInternal(
@@ -133,10 +136,8 @@ private static void ConfigureThemeInternal(
ThemeSwitcher.RegisterThemeEndpoints(application, theme, options.ConfigObject.AdditionalItems);
// Inject stylesheets
- var commonPath = FileProvider.StylesPath + theme.Common.FileName;
- var themePath = FileProvider.StylesPath + theme.FileName;
- options.InjectStylesheet(commonPath);
- options.InjectStylesheet(themePath);
+ options.InjectStylesheet(FileProvider.StylesPath + theme.Common.FileName);
+ options.InjectStylesheet(FileProvider.StylesPath + theme.FileName);
// Configure JS features if enabled
if (theme.LoadAdditionalJs && AdvancedOptions.AnyJsFeatureEnabled(options.ConfigObject.AdditionalItems))
@@ -148,7 +149,6 @@ private static void ConfigureThemeInternal(
private static Action ConfigureTheme(
IApplicationBuilder application,
- SwaggerUIOptions options,
BaseTheme theme)
{
// This is only used by the first overload
@@ -162,15 +162,7 @@ private static void InjectJavascriptInternal(
var javascript = ThemeBuilderHelpers.GetConfiguredJavaScript(options.ConfigObject.AdditionalItems);
ThemeBuilderHelpers.RegisterJavaScriptEndpoint(application, javascript);
- const string jsPath = FileProvider.ScriptsPath + FileProvider.JsFilename;
- options.InjectJavascript(jsPath);
- }
-
- private static Action InjectJavascript(
- IApplicationBuilder application,
- SwaggerUIOptions options)
- {
- return opt => InjectJavascriptInternal(application, opt);
+ options.InjectJavascript(FileProvider.ScriptsPath + FileProvider.JsFilename);
}
private static void ConfigureThemeSwitcher(
diff --git a/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/NSwagBuilderExtensions.cs b/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/NSwagBuilderExtensions.cs
index afc3b44..0d15046 100644
--- a/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/NSwagBuilderExtensions.cs
+++ b/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/NSwagBuilderExtensions.cs
@@ -78,30 +78,34 @@ public static IApplicationBuilder UseSwaggerUi(
ArgumentNullException.ThrowIfNull(assembly);
ArgumentNullException.ThrowIfNull(cssFilename);
- var settings = new SwaggerUiSettings();
- configureSettings?.Invoke(settings);
-
- var (themeContent, commonContent, loadJs, isStandalone, themeName) =
- ThemeBuilderHelpers.LoadAssemblyTheme(assembly, cssFilename);
+ return application.UseSwaggerUi(settings =>
+ {
+ configureSettings?.Invoke(settings);
- ThemeSwitcher.RegisterCustomTheme(cssFilename, isStandalone);
+ var (themeContent, commonContent, loadJs, isStandalone, themeName) =
+ ThemeBuilderHelpers.LoadAssemblyTheme(assembly, cssFilename);
- if (!isStandalone)
- {
- commonContent = AdvancedOptions.Apply(commonContent, settings.AdditionalSettings, MimeTypes.Text.Css);
- themeContent = commonContent + Environment.NewLine + themeContent;
+ // Create a dynamic BaseTheme instance for this CSS file
+ // This allows it to be discovered and included in the theme switcher
+ var dynamicTheme = new DynamicTheme(cssFilename, isStandalone, cssFilename.EndsWith(".min.css"));
- if (loadJs && AdvancedOptions.AnyJsFeatureEnabled(settings.AdditionalSettings))
+ if (!isStandalone)
{
- configureSettings += s => InjectJavascript(application, s);
- configureSettings += s => ThemeBuilderHelpers.ConfigureCustomThemeWithSwitcher(
- application, themeName, isStandalone, s.AdditionalSettings, s.GetThemeSwitcherOptions());
- }
- }
+ // Register the theme for auto-discovery
+ ThemeSwitcher.RegisterTheme(dynamicTheme, cssFilename, isStandalone);
+
+ commonContent = AdvancedOptions.Apply(commonContent, settings.AdditionalSettings, MimeTypes.Text.Css);
+ themeContent = commonContent + Environment.NewLine + themeContent;
- configureSettings += opt => opt.CustomInlineStyles = themeContent;
+ if (loadJs && AdvancedOptions.AnyJsFeatureEnabled(settings.AdditionalSettings))
+ {
+ InjectJavascript(application, settings);
+ ConfigureThemeSwitcher(application, settings, dynamicTheme);
+ }
+ }
- return application.UseSwaggerUi(configureSettings);
+ settings.CustomInlineStyles = themeContent;
+ });
}
private static void InjectJavascript(IApplicationBuilder application, SwaggerUiSettings settings)
From d4dc1ab69f87b940287d41f072f9e92a3f418eae Mon Sep 17 00:00:00 2001
From: Teuz
Date: Sat, 22 Nov 2025 16:59:13 +0100
Subject: [PATCH 08/18] Refactor NSwag themes registration and samples
---
.../Program.cs | 41 +++++------
.../Program.cs | 38 +++++-----
.../Builder/SwaggerUIBuilderExtensions.cs | 14 +---
.../Builder/NSwagBuilderExtensions.cs | 71 +++++++++++++------
4 files changed, 90 insertions(+), 74 deletions(-)
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs
index 2ff5e0f..e631f48 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs
+++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs
@@ -1,5 +1,5 @@
-using Sample.AspNetCore.SwaggerUI.NSwag;
-using System.Reflection;
+using AspNetCore.Swagger.Themes;
+using Sample.AspNetCore.SwaggerUI.NSwag;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
@@ -18,10 +18,7 @@
// ========================================
// 1. Simple predefined theme (no theme switcher)
- //app.UseSwaggerUi(Theme.Dark, c =>
- //{
- // c.DocumentTitle = "Sample API - Dark Theme";
- //});
+ app.UseSwaggerUi(Theme.Dark, c => c.DocumentTitle = "Sample API - Dark Theme");
// 2. Inline CSS theme
//app.UseSwaggerUi("body { background-color: #1a1a2e; color: #eee; }", c =>
@@ -30,11 +27,11 @@
//});
// 3. Custom theme from embedded resource
- app.UseSwaggerUi(Assembly.GetExecutingAssembly(), "mybrand.css", c =>
- {
- c.DocumentTitle = "Sample API - Custom Theme from Assembly";
- c.EnableAllAdvancedOptions(); // Enables theme switcher + all UI features
- });
+ //app.UseSwaggerUi(Assembly.GetExecutingAssembly(), "mybrand.css", c =>
+ //{
+ // c.DocumentTitle = "Sample API - Custom Theme from Assembly";
+ // c.EnableAllAdvancedOptions(); // Enables theme switcher + all UI features
+ //});
// 4. Standalone theme (no dependencies on common.css or ui.js)
//app.UseSwaggerUi(Assembly.GetExecutingAssembly(), "standalone.custom.css", c =>
@@ -69,16 +66,16 @@
//});
// 8. Specific themes selection (mixed predefined + custom)
- //app.UseSwaggerUi(CompanyThemes.CorporateBlue, c =>
+ //app.UseSwaggerUi(CompanyThemes.CompanyThemes.CorporateBlue, c =>
//{
// c.DocumentTitle = "Sample API - Business Themes";
// c.EnableThemeSwitcher(new ThemeSwitcherOptions()
// .WithThemes(
// Theme.Dark,
// Theme.Light,
- // CompanyThemes.CorporateBlue,
- // CompanyThemes.TechGreen,
- // CompanyThemes.StartupPurple
+ // CompanyThemes.CompanyThemes.CorporateBlue,
+ // CompanyThemes.CompanyThemes.TechGreen,
+ // CompanyThemes.CompanyThemes.StartupPurple
// ));
//});
@@ -112,20 +109,20 @@
//});
// 12. Professional company branding
- //app.UseSwaggerUi(CompanyThemes.TechGreen, c =>
+ //app.UseSwaggerUi(CompanyThemes.CompanyThemes.TechGreen, c =>
//{
// c.DocumentTitle = "Company API Documentation";
// c.EnableThemeSwitcher(new ThemeSwitcherOptions()
// .WithThemes(
- // CompanyThemes.CorporateBlue,
- // CompanyThemes.TechGreen,
- // CompanyThemes.StartupPurple
+ // CompanyThemes.CompanyThemes.CorporateBlue,
+ // CompanyThemes.CompanyThemes.TechGreen,
+ // CompanyThemes.CompanyThemes.StartupPurple
// )
// .WithDisplayFormat("Company Theme: {name}"));
//});
// ========================================
- // ⚙️ ADVANCED UI FEATURES (without theme switcher)
+ // ⚙️ ADVANCED UI FEATURES
// ========================================
// 13. Enable all advanced UI features
@@ -160,14 +157,14 @@
// ========================================
// 16. ExplicitOnly mode (no auto-discovery)
- //app.UseSwaggerUi(CompanyThemes.CorporateBlue, c =>
+ //app.UseSwaggerUi(CompanyThemes.CompanyThemes.CorporateBlue, c =>
//{
// c.DocumentTitle = "Sample API - Explicit Themes";
// c.EnableThemeSwitcher(new ThemeSwitcherOptions()
// .WithThemes(
// Theme.Dark,
// Theme.Light,
- // CompanyThemes.CorporateBlue
+ // CompanyThemes.CompanyThemes.CorporateBlue
// )
// .WithCustomThemes(CustomThemeMode.ExplicitOnly));
//});
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs
index 78f9acf..037bf54 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Program.cs
@@ -1,6 +1,5 @@
using AspNetCore.Swagger.Themes;
using Sample.AspNetCore.SwaggerUI.Swashbuckle;
-using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
@@ -19,10 +18,7 @@
// ========================================
// 1. Simple predefined theme (no theme switcher)
- //app.UseSwaggerUI(Theme.Dark, c =>
- //{
- // c.DocumentTitle = "Sample API - Dark Theme";
- //});
+ app.UseSwaggerUI(Theme.Dark, c => c.DocumentTitle = "Sample API - Dark Theme");
// 2. Inline CSS theme
//app.UseSwaggerUI("body { background-color: #1a1a2e; color: #eee; }", c =>
@@ -31,11 +27,11 @@
//});
// 3. Custom theme from embedded resource
- app.UseSwaggerUI(Assembly.GetExecutingAssembly(), "mybrand.css", c =>
- {
- c.DocumentTitle = "Sample API - Custom Theme from Assembly";
- c.EnableAllAdvancedOptions(ThemeSwitcherOptions.CustomOnly()); // Enables theme switcher + all UI features
- });
+ //app.UseSwaggerUI(Assembly.GetExecutingAssembly(), "mybrand.css", c =>
+ //{
+ // c.DocumentTitle = "Sample API - Custom Theme from Assembly";
+ // c.EnableAllAdvancedOptions(); // Enables theme switcher + all UI features
+ //});
// 4. Standalone theme (no dependencies on common.css or ui.js)
//app.UseSwaggerUI(Assembly.GetExecutingAssembly(), "standalone.custom.css", c =>
@@ -70,16 +66,16 @@
//});
// 8. Specific themes selection (mixed predefined + custom)
- //app.UseSwaggerUI(CompanyThemes.CorporateBlue, c =>
+ //app.UseSwaggerUI(CompanyThemes.CompanyThemes.CorporateBlue, c =>
//{
// c.DocumentTitle = "Sample API - Business Themes";
// c.EnableThemeSwitcher(new ThemeSwitcherOptions()
// .WithThemes(
// Theme.Dark,
// Theme.Light,
- // CompanyThemes.CorporateBlue,
- // CompanyThemes.TechGreen,
- // CompanyThemes.StartupPurple
+ // CompanyThemes.CompanyThemes.CorporateBlue,
+ // CompanyThemes.CompanyThemes.TechGreen,
+ // CompanyThemes.CompanyThemes.StartupPurple
// ));
//});
@@ -113,20 +109,20 @@
//});
// 12. Professional company branding
- //app.UseSwaggerUI(CompanyThemes.TechGreen, c =>
+ //app.UseSwaggerUI(CompanyThemes.CompanyThemes.TechGreen, c =>
//{
// c.DocumentTitle = "Company API Documentation";
// c.EnableThemeSwitcher(new ThemeSwitcherOptions()
// .WithThemes(
- // CompanyThemes.CorporateBlue,
- // CompanyThemes.TechGreen,
- // CompanyThemes.StartupPurple
+ // CompanyThemes.CompanyThemes.CorporateBlue,
+ // CompanyThemes.CompanyThemes.TechGreen,
+ // CompanyThemes.CompanyThemes.StartupPurple
// )
// .WithDisplayFormat("Company Theme: {name}"));
//});
// ========================================
- // ⚙️ ADVANCED UI FEATURES (without theme switcher)
+ // ⚙️ ADVANCED UI FEATURES
// ========================================
// 13. Enable all advanced UI features
@@ -161,14 +157,14 @@
// ========================================
// 16. ExplicitOnly mode (no auto-discovery)
- //app.UseSwaggerUI(CompanyThemes.CorporateBlue, c =>
+ //app.UseSwaggerUI(CompanyThemes.CompanyThemes.CorporateBlue, c =>
//{
// c.DocumentTitle = "Sample API - Explicit Themes";
// c.EnableThemeSwitcher(new ThemeSwitcherOptions()
// .WithThemes(
// Theme.Dark,
// Theme.Light,
- // CompanyThemes.CorporateBlue
+ // CompanyThemes.CompanyThemes.CorporateBlue
// )
// .WithCustomThemes(CustomThemeMode.ExplicitOnly));
//});
diff --git a/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIBuilderExtensions.cs b/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIBuilderExtensions.cs
index 20b5ef7..1efae42 100644
--- a/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIBuilderExtensions.cs
+++ b/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIBuilderExtensions.cs
@@ -76,7 +76,7 @@ public static IApplicationBuilder UseSwaggerUI(
}
///
- /// Registers the Swagger UI middleware applying a CSS theme from an assembly.
+ /// Registers the Swagger UI middleware applying a CSS theme from an assembly with optional setup action.
///
/// The application builder instance.
/// The assembly where the embedded CSS file is situated.
@@ -132,7 +132,6 @@ private static void ConfigureThemeInternal(
SwaggerUIOptions options,
BaseTheme theme)
{
- // Register theme endpoints
ThemeSwitcher.RegisterThemeEndpoints(application, theme, options.ConfigObject.AdditionalItems);
// Inject stylesheets
@@ -147,13 +146,8 @@ private static void ConfigureThemeInternal(
}
}
- private static Action ConfigureTheme(
- IApplicationBuilder application,
- BaseTheme theme)
- {
- // This is only used by the first overload
- return opt => ConfigureThemeInternal(application, opt, theme);
- }
+ private static Action ConfigureTheme(IApplicationBuilder application, BaseTheme theme) =>
+ opt => ConfigureThemeInternal(application, opt, theme); // This is only used by the first overload
private static void InjectJavascriptInternal(
IApplicationBuilder application,
@@ -171,8 +165,6 @@ private static void ConfigureThemeSwitcher(
BaseTheme theme)
{
var headContent = new StringBuilder();
-
- // Get switcher options from cache
var switcherOptions = options.GetThemeSwitcherOptions();
ThemeBuilderHelpers.ConfigureThemeWithSwitcher(
diff --git a/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/NSwagBuilderExtensions.cs b/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/NSwagBuilderExtensions.cs
index 0d15046..bc5602c 100644
--- a/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/NSwagBuilderExtensions.cs
+++ b/src/NSwag.AspNetCore.Themes/Microsoft/AspNetCore/Builder/NSwagBuilderExtensions.cs
@@ -28,14 +28,7 @@ public static IApplicationBuilder UseSwaggerUi(
return application.UseSwaggerUi(settings =>
{
configureSettings?.Invoke(settings);
-
- settings.CustomInlineStyles = ThemeSwitcher.LoadThemeContent(theme, settings.AdditionalSettings);
-
- if (theme.LoadAdditionalJs && AdvancedOptions.AnyJsFeatureEnabled(settings.AdditionalSettings))
- {
- InjectJavascript(application, settings);
- ConfigureThemeSwitcher(application, settings, theme);
- }
+ ConfigureThemeInternal(application, settings, theme);
});
}
@@ -44,7 +37,7 @@ public static IApplicationBuilder UseSwaggerUi(
///
/// The application builder instance.
/// The CSS theme to apply.
- /// An optional action to configure Swagger UI options.
+ /// An optional action to configure Swagger UI settings.
/// The for chaining.
/// Thrown when is null.
public static IApplicationBuilder UseSwaggerUi(
@@ -54,9 +47,11 @@ public static IApplicationBuilder UseSwaggerUi(
{
ArgumentNullException.ThrowIfNull(cssThemeContent);
+ const string CustomPath = $"{FileProvider.StylesPath}custom.css";
+ FileProvider.AddGetEndpoint(application, CustomPath, cssThemeContent);
ThemeSwitcher.RegisterCustomTheme("custom.css", isStandalone: true);
- setupAction += opt => opt.CustomInlineStyles = cssThemeContent;
+ setupAction += opt => opt.CustomStylesheetPath = CustomPath;
return application.UseSwaggerUi(setupAction);
}
@@ -66,7 +61,7 @@ public static IApplicationBuilder UseSwaggerUi(
/// The application builder instance.
/// The assembly where the embedded CSS file is situated.
/// The CSS theme filename (e.g. "myCustomTheme.css").
- /// An optional action to configure Swagger UI options.
+ /// An optional action to configure Swagger UI settings.
/// The for chaining.
/// Thrown when or is null.
public static IApplicationBuilder UseSwaggerUi(
@@ -82,8 +77,7 @@ public static IApplicationBuilder UseSwaggerUi(
{
configureSettings?.Invoke(settings);
- var (themeContent, commonContent, loadJs, isStandalone, themeName) =
- ThemeBuilderHelpers.LoadAssemblyTheme(assembly, cssFilename);
+ var (themeContent, commonContent, loadJs, isStandalone, themeName) = ThemeBuilderHelpers.LoadAssemblyTheme(assembly, cssFilename);
// Create a dynamic BaseTheme instance for this CSS file
// This allows it to be discovered and included in the theme switcher
@@ -95,26 +89,59 @@ public static IApplicationBuilder UseSwaggerUi(
ThemeSwitcher.RegisterTheme(dynamicTheme, cssFilename, isStandalone);
commonContent = AdvancedOptions.Apply(commonContent, settings.AdditionalSettings, MimeTypes.Text.Css);
- themeContent = commonContent + Environment.NewLine + themeContent;
+
+ // NSwag limitation: can only set ONE CustomStylesheetPath
+ // So we combine common + theme CSS into a single file
+ var combinedContent = commonContent + Environment.NewLine + themeContent;
+
+ var themePath = $"{FileProvider.StylesPath}{cssFilename}";
+ FileProvider.AddGetEndpoint(application, themePath, combinedContent);
+ settings.CustomStylesheetPath = themePath;
if (loadJs && AdvancedOptions.AnyJsFeatureEnabled(settings.AdditionalSettings))
{
- InjectJavascript(application, settings);
+ InjectJavascriptInternal(application, settings);
ConfigureThemeSwitcher(application, settings, dynamicTheme);
}
}
-
- settings.CustomInlineStyles = themeContent;
+ else
+ {
+ // Standalone theme - just the theme CSS
+ var themePath = $"{FileProvider.StylesPath}{cssFilename}";
+ FileProvider.AddGetEndpoint(application, themePath, themeContent);
+ settings.CustomStylesheetPath = themePath;
+ }
});
}
- private static void InjectJavascript(IApplicationBuilder application, SwaggerUiSettings settings)
+ private static void ConfigureThemeInternal(
+ IApplicationBuilder application,
+ SwaggerUiSettings settings,
+ BaseTheme theme)
+ {
+ // Load and register combined theme content (common + theme CSS)
+ var themeContent = ThemeSwitcher.LoadThemeContent(theme, settings.AdditionalSettings);
+ var themePath = $"{FileProvider.StylesPath}{theme.FileName}";
+ FileProvider.AddGetEndpoint(application, themePath, themeContent);
+
+ settings.CustomStylesheetPath = themePath;
+
+ // Configure JS features if enabled
+ if (theme.LoadAdditionalJs && AdvancedOptions.AnyJsFeatureEnabled(settings.AdditionalSettings))
+ {
+ InjectJavascriptInternal(application, settings);
+ ConfigureThemeSwitcher(application, settings, theme);
+ }
+ }
+
+ private static void InjectJavascriptInternal(
+ IApplicationBuilder application,
+ SwaggerUiSettings settings)
{
var javascript = ThemeBuilderHelpers.GetConfiguredJavaScript(settings.AdditionalSettings);
ThemeBuilderHelpers.RegisterJavaScriptEndpoint(application, javascript);
- const string jsPath = FileProvider.ScriptsPath + FileProvider.JsFilename;
- settings.CustomJavaScriptPath = jsPath;
+ settings.CustomJavaScriptPath = FileProvider.ScriptsPath + FileProvider.JsFilename;
}
private static void ConfigureThemeSwitcher(
@@ -139,6 +166,10 @@ private static void ConfigureThemeSwitcher(
headContent.AppendLine(ThemeSwitcher.CreateThemeLink(availableTheme, disabled: true));
});
+ // Mark current theme
+ headContent.AppendLine(ThemeSwitcher.CreateCurrentThemeMarkerScript(theme));
+
+ // Append to settings
settings.CustomHeadContent += headContent.ToString();
}
}
\ No newline at end of file
From 370612d8adcd6a9641c1af7ea6e06e5a3ef5e78d Mon Sep 17 00:00:00 2001
From: Teuz
Date: Sun, 23 Nov 2025 01:04:32 +0100
Subject: [PATCH 09/18] Lil refactor sample projects
---
.../OpenApiDocGenConfigurer.cs | 8 ++++----
.../SwaggerThemes/Custom/mybrand.css | 2 +-
.../SwaggerGenConfigurer.cs | 2 +-
.../SwaggerThemes/Custom/mybrand.css | 2 +-
4 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/OpenApiDocGenConfigurer.cs b/samples/Sample.AspNetCore.SwaggerUI.NSwag/OpenApiDocGenConfigurer.cs
index cdb8d8e..2823ece 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.NSwag/OpenApiDocGenConfigurer.cs
+++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/OpenApiDocGenConfigurer.cs
@@ -38,10 +38,10 @@ internal static void Configure(this AspNetCoreOpenApiDocumentGeneratorSettings o
Implicit = new OpenApiOAuthFlow()
{
Scopes = new Dictionary
- {
- { "read", "Read access to protected resources" },
- { "write", "Write access to protected resources" }
- },
+ {
+ { "read", "Read access to protected resources" },
+ { "write", "Write access to protected resources" }
+ },
AuthorizationUrl = "https://localhost:44333/core/connect/authorize",
TokenUrl = "https://localhost:44333/core/connect/token"
},
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/mybrand.css b/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/mybrand.css
index ead96b4..531af5e 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/mybrand.css
+++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/mybrand.css
@@ -32,7 +32,7 @@
--auth-container-border-bottom-color: #ebebeb;
--auth-container-background-color: #ddd;
--auth-container-errors-color: red;
- --btn-authorize-background-color: transparent;
+ --auth-wrapper-background-color: #071f4b;
--btn-authorize-border-color: #ecf0f1;
--btn-authorize-font-color: #ecf0f1;
--btn-authorize-svg-fill-color: #ecf0f1;
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerGenConfigurer.cs b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerGenConfigurer.cs
index 82860b6..33618c9 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerGenConfigurer.cs
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerGenConfigurer.cs
@@ -13,7 +13,7 @@ internal static void Configure(this SwaggerGenOptions options)
{
Version = "v1",
Title = "ToDo API",
- Description = "An ASP.NET Core Web API for testing",
+ Description = "An ASP.NET Core Web API for managing ToDo items",
TermsOfService = new Uri("https://example.com/terms"),
Contact = new OpenApiContact
{
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/mybrand.css b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/mybrand.css
index ead96b4..531af5e 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/mybrand.css
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/mybrand.css
@@ -32,7 +32,7 @@
--auth-container-border-bottom-color: #ebebeb;
--auth-container-background-color: #ddd;
--auth-container-errors-color: red;
- --btn-authorize-background-color: transparent;
+ --auth-wrapper-background-color: #071f4b;
--btn-authorize-border-color: #ecf0f1;
--btn-authorize-font-color: #ecf0f1;
--btn-authorize-svg-fill-color: #ecf0f1;
From 7f599404120ca5fb5212bb85a6fabf5c4060f8fd Mon Sep 17 00:00:00 2001
From: Teuz
Date: Sun, 23 Nov 2025 01:05:31 +0100
Subject: [PATCH 10/18] Improve CONTRIBUTING.md for clarity and structure
Updated CONTRIBUTING.md to enhance readability and organization:
- Reworded "How to Contribute" steps for better emphasis.
- Added a "Project Structure" section to explain the codebase layout.
- Enhanced "Working with Themes" with clearer minification steps.
- Introduced a "Testing Your Changes" section with detailed instructions.
- Improved "Pull Request Guidelines" with best practices for PRs.
- Expanded "Reporting Bugs" and "Suggesting Features" sections.
- Added a "Development Tips" section for rapid iteration and theme organization.
- Clarified licensing terms and added a friendly closing note.
- Updated contributor credit with a link to @teociaps' GitHub profile.
---
CONTRIBUTING.md | 257 +++++++++++++++++++++++++++++++++---------------
1 file changed, 177 insertions(+), 80 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 74b826f..0380fa7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,21 +8,21 @@ This project and everyone participating in it are governed by our [Code of Condu
## How to Contribute
-1. **Fork the repository to your GitHub account.**
-2. Clone the forked repository to your local machine.
+1. **Fork the repository** to your GitHub account.
+2. **Clone** the forked repository to your local machine.
3. Create a **new branch** for your contribution:
```bash
git checkout -b feature/my-feature
```
-4. Make your changes and commit them:
+4. Make your changes and **commit** them:
```bash
git commit -m "Add new feature"
```
-5. Push the changes to your fork:
+5. **Push** the changes to your fork:
```bash
git push origin feature/my-feature
@@ -30,116 +30,213 @@ This project and everyone participating in it are governed by our [Code of Condu
6. Open a **pull request (PR)** against the **main** branch of the original repository.
-## Working with Styles and Scripts
+## Project Structure
-When contributing changes to CSS or JavaScript files for themes, please follow these guidelines to ensure both original and minified versions are properly maintained.
+Understanding the project layout helps you navigate the codebase:
+
+- **`src/AspNetCore.Swagger.Themes.Common/`** - Core theme system shared between packages
+- **`src/AspNetCore.SwaggerUI.Themes/`** - Swashbuckle.AspNetCore integration
+- **`src/NSwag.AspNetCore.Themes/`** - NSwag.AspNetCore integration
+- **`samples/`** - Example applications demonstrating usage
+- **`tests/`** - Unit and integration tests
+
+## Working with Themes
+
+### Adding or Modifying Themes
+
+Themes are defined as CSS files with optional JavaScript for advanced features. When working with themes:
+
+1. **Original files** - Make your changes in the non-minified `.css` or `.js` files
+2. **Minified versions** - Must be manually updated (see Minification Process below)
+3. **Headers** - Minified files require specific comment headers for testing
### Minification Process
-The project includes both original and minified versions of assets. The minified versions are used at runtime for optimal performance, while original files serve as fallbacks.
+The project uses minified versions at runtime for performance. After editing CSS or JavaScript:
-#### CSS Files
+#### For CSS Files
1. Navigate to [CSS Minifier](https://www.toptal.com/developers/cssminifier)
-2. For each modified CSS file (e.g., `common.css`, `dark.css`, etc.):
- - Open the original CSS file and copy its entire content
- - Paste the content into the online minifier
- - Click **"Minify"** to generate the compressed version
- - Copy the minified output
- - **Add the compact header** at the beginning of the minified content (see format below)
- - Paste the result into the corresponding `.min.css` file (e.g., `common.min.css`)
- - **Important**: Verify that placeholders are preserved:
- - For `common.css` and `modern.common.css`, ensure `#STICKY_OPERATIONS` is still present in the minified output
-
-#### JavaScript Files
+2. Copy the **entire content** of your modified CSS file
+3. Paste into the minifier and click **"Minify"**
+4. Copy the minified output
+5. Add the **compact header** at the very beginning (format below)
+6. Paste into the corresponding `.min.css` file
+7. **Verify placeholders** are preserved (e.g., `#STICKY_OPERATIONS` in `common.css`)
+
+#### For JavaScript Files
1. Navigate to [JavaScript Minifier](https://www.toptal.com/developers/javascript-minifier)
-2. For each modified JavaScript file (e.g., `modern.js`):
- - Open the original JavaScript file and copy its entire content
- - Paste the content into the online minifier
- - Click **"Minify"** to generate the compressed version
- - Copy the minified output
- - **Add the compact header** at the beginning of the minified content (see format below)
- - Paste the result into the corresponding `.min.js` file (e.g., `modern.min.js`)
- - **Important**: Verify that all placeholders are preserved in the minified output:
- - `{$PINNABLE_TOPBAR}`
- - `{$BACK_TO_TOP}`
- - `{$EXPAND_COLLAPSE_ALL_OPERATIONS}`
-
-#### Minified File Header Format
-
-All minified files must start with a compact comment header for test compatibility. The format is:
+2. Copy the **entire content** of your modified JavaScript file
+3. Paste into the minifier and click **"Minify"**
+4. Copy the minified output
+5. Add the **compact header** at the very beginning (format below)
+6. Paste into the corresponding `.min.js` file
+7. **Verify all placeholders** are preserved:
+ - `{$PINNABLE_TOPBAR}`
+ - `{$BACK_TO_TOP}`
+ - `{$EXPAND_COLLAPSE_ALL_OPERATIONS}`
+ - `{$THEME_SWITCHER}`
+
+#### Minified Header Format
+
+All minified files must start with this compact comment:
```
-/*StyleName*/[minified content here...]
+/*Name*/[minified content]
```
-**Examples**:
-- `dark.min.css` → `/*Dark*/body{color:...`
-- `modern.dark.min.css` → `/*Modern Dark*/body{color:...`
-- `common.min.css` → `/*Common Style*/body{color:...`
-- `modern.min.js` → `/*Modern UI*/const rootElement=...`
+**Examples:**
+- `dark.min.css` → `/*Dark Theme*/body{...`
+- `common.min.css` → `/*Common*/body{...`
+- `ui.min.js` → `/*Swagger UI*/const ...`
+
+> **Why?** Tests verify headers are present and correctly formatted. This ensures minification was done properly.
-#### Committing Changes
+#### Placeholder Preservation
-When committing style or script changes, always include both the original and minified versions:
+Placeholders like `{$THEME_SWITCHER}` are replaced at runtime based on user settings. Minification **must preserve** these exact strings, or features will break.
+
+### Committing Theme Changes
+
+Always commit both original **and** minified versions:
```bash
git add src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Styles/*.css
git add src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/Scripts/*.js
-git commit -m "Update theme styles: [description of changes]"
+git commit -m "Add new ocean theme"
+```
+
+## Testing Your Changes
+
+Before submitting a pull request:
+
+### 1. Build the Solution
+
+```bash
+dotnet build -c Release
+```
+
+Ensure there are no compilation errors.
+
+### 2. Run Tests
+
+```bash
+dotnet test
```
-### Why Placeholders Matter
+All tests must pass, including:
+- Minified header format tests
+- Theme discovery tests
+- Integration tests
-The placeholders (`#STICKY_OPERATIONS`, `{$PINNABLE_TOPBAR}`, etc.) are dynamically replaced at runtime based on user configuration. It is critical that minification preserves these exact strings to maintain functionality.
+### 3. Test with Sample Applications
-### Testing Your Changes
+Run both sample projects to verify functionality:
-Before submitting your pull request:
+```bash
+# Swashbuckle sample
+dotnet run --project samples/Sample.AspNetCore.SwaggerUI.Swashbuckle
-1. Build the project:
- ```bash
- dotnet build -c Release
- ```
+# NSwag sample
+dotnet run --project samples/Sample.AspNetCore.SwaggerUI.NSwag
+```
-2. Run the tests to ensure minified headers are correct:
- ```bash
- dotnet test
- ```
+### 4. Manual Verification
-3. Test with the sample applications:
- ```bash
- dotnet run --project samples/Sample.AspNetCore.SwaggerUI.Swashbuckle
- ```
+Open the sample app in your browser and verify:
-4. Verify that:
- - Themes render correctly
- - Advanced features work as expected (pinnable topbar, back-to-top button, etc.)
- - No console errors appear in the browser's developer tools
- - Tests pass
+- ✅ Themes render correctly
+- ✅ Theme switcher dropdown appears and works (if enabled)
+- ✅ Custom themes are discovered automatically
+- ✅ Advanced features work (pinnable topbar, back-to-top, etc.)
+- ✅ No console errors in browser developer tools
+- ✅ Standalone themes load without extra dependencies
## Pull Request Guidelines
-- Ensure your code adheres to the existing coding standards.
-- Include relevant documentation and test cases for your changes.
-- For style/script changes, include both original and minified versions with proper headers.
-- Keep your pull request focused. If you are addressing multiple issues, submit separate pull requests for each.
-- Be responsive to comments and feedback on your pull request.
-## Reporting Bugs and Issues
-If you find a bug or have a question, please open an issue on GitHub. When reporting a bug, provide as much detail as possible, including:
+- **Follow existing code style** - Match the patterns you see in the codebase
+- **Include tests** - Add or update tests for your changes
+- **Update documentation** - If adding features, update relevant docs
+- **Keep PRs focused** - One feature or fix per PR (submit separate PRs for multiple changes)
+- **Provide context** - Explain *why* the change is needed, not just *what* changed
+- **Be responsive** - Address feedback and questions promptly
-- A clear and concise description of the bug.
-- Steps to reproduce the issue.
-- Expected behavior and actual behavior.
-- Screenshots or code snippets, if applicable.
+### Good PR Practices
-## Feature Requests
-We welcome suggestions for new features or improvements. Please open an issue to propose your ideas.
+- Use descriptive titles: `feat: add sunset theme` not `update css`
+- Reference issues: `Fixes #123` or `Closes #456`
+- Include screenshots for visual changes
+- Test across .NET 8, 9, and 10 if possible
+
+## Reporting Bugs
+
+Found a bug? Please open an issue with:
+
+- **Clear description** of the problem
+- **Steps to reproduce** the issue
+- **Expected vs. actual behavior**
+- **Environment details:**
+ - Which package (Swashbuckle or NSwag)
+ - .NET version
+ - Browser (if UI-related)
+- **Screenshots or code snippets** if applicable
+
+## Suggesting Features
+
+We welcome feature suggestions! When proposing a feature:
+
+- **Describe the use case** - What problem does it solve?
+- **Explain the benefit** - How does it improve the project?
+- **Consider backward compatibility** - Will it break existing code?
+- **Offer to help** - Can you contribute the implementation?
+
+## Development Tips
+
+### Quick Testing Loop
+
+For rapid iteration during development:
+
+1. Make your changes
+2. Build: `dotnet build`
+3. Run sample: `dotnet run --project samples/Sample.AspNetCore.SwaggerUI.Swashbuckle`
+4. Refresh browser (F5) (clear the browser cache if necessary) to see changes
+
+### Organizing Custom Themes
+
+When adding example custom themes, use subfolders for clarity:
+
+```
+samples/YourSample/SwaggerThemes/
+├── CompanyThemes/
+│ └── corporate-blue.css
+└── SeasonalThemes/
+ └── holiday-red.css
+```
+
+This demonstrates best practices for users organizing their own themes.
+
+### Standalone vs. Regular Themes
+
+- **Regular themes** - Depend on `common.css` and may use JavaScript features
+- **Standalone themes** - Prefix with `standalone.` (e.g., `standalone.custom.css`) for zero dependencies
+
+Choose based on whether you need shared styles or prefer total independence.
+
+## Questions?
+
+Not sure about something? Feel free to:
+
+- Open an issue for clarification
+- Ask in your pull request
+- Reference existing code patterns
## License
-By contributing to this project, you agree that your contributions will be licensed under the [License](LICENSE) file.
-Thank you for your contributions!
+By contributing, you agree that your contributions will be licensed under the same [MIT License](LICENSE) that covers the project.
+
+---
+
+Thank you for making SwaggerUI.Themes better! 🎨
-**_@teociaps_**
\ No newline at end of file
+**[@teociaps](https://github.com/teociaps)**
\ No newline at end of file
From b2e0c9433c3f6c67cc5973d56eaa86f12c7f07f6 Mon Sep 17 00:00:00 2001
From: Teuz
Date: Sun, 23 Nov 2025 01:08:28 +0100
Subject: [PATCH 11/18] Enhance project and packages documentation and add demo
GIF
---
README.md | 64 +++++++++++++-----
assets/swaggerui-themes-demo.gif | Bin 0 -> 14018487 bytes
.../package-readme.md | 41 ++++++++---
src/NSwag.AspNetCore.Themes/package-readme.md | 41 ++++++++---
4 files changed, 106 insertions(+), 40 deletions(-)
create mode 100644 assets/swaggerui-themes-demo.gif
diff --git a/README.md b/README.md
index e45d67b..b8e2b32 100644
--- a/README.md
+++ b/README.md
@@ -15,11 +15,17 @@
-### Beautiful, modern themes for Swagger/OpenAPI documentation in ASP.NET Core
+### Give your ASP.NET Core API documentation the look it deserves!
-Make your API documentation look great with themes that fit your style.
+**Switch themes at runtime** • **Unlock new capabilities** • **Create and choose your custom style**
+
+_**...and more!**_
-**[Get Started](https://github.com/teociaps/SwaggerUI.Themes/wiki/Getting-Started)** • **[View Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Predefined-Themes)** • **[Full Documentation](https://github.com/teociaps/SwaggerUI.Themes/wiki)**
+**[Get Started](https://github.com/teociaps/SwaggerUI.Themes/wiki/Getting-Started)** • **[View Built-in Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Predefined-Themes)** • **[Full Documentation](https://github.com/teociaps/SwaggerUI.Themes/wiki)**
+
+
+
+_Note: GIF framerate is limited. Actual transitions are buttery smooth!_
---
@@ -52,19 +58,24 @@ dotnet add package NSwag.AspNetCore.Themes
app.UseSwaggerUI(Theme.Dark); // Swashbuckle
// or
app.UseSwaggerUi(Theme.Dark); // NSwag
+
+// Enable runtime theme switcher!
+app.UseSwaggerUI(Theme.Dark, c => c.EnableThemeSwitcher());
```
## ✨ Features
+- 🔥 **[Theme Switcher](https://github.com/teociaps/SwaggerUI.Themes/wiki/Feature-Theme-Switcher)** - Switch built-in and custom themes dynamically without page reload
+
- **[Built-in Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Predefined-Themes)** - Choose from predefined themes ready to use
- **[Custom Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Custom-Themes)** - Build your own themes with full control, or create standalone themes with zero dependencies
-- **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Enhance your documentation with new capabilities
+- **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Enhance your documentation with powerful UI capabilities
-**Discover more in the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki)!**
+- _...discover more in the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki/Features)!_
-## 💡 Basic Usage Examples
+## 📚 Basic Usage Examples
### Swashbuckle
@@ -73,10 +84,16 @@ using AspNetCore.Swagger.Themes;
...
-// Simple
+// Simple theme
app.UseSwaggerUI(Theme.Dark);
-// Or with advanced options
+// With runtime theme switcher
+app.UseSwaggerUI(Theme.Dark, c =>
+{
+ c.EnableThemeSwitcher(); // Auto-discovers all custom themes!
+});
+
+// All advanced features
app.UseSwaggerUI(Theme.Dark, c =>
{
c.EnableAllAdvancedOptions();
@@ -92,10 +109,16 @@ using AspNetCore.Swagger.Themes;
...
-// Simple
+// Simple theme
app.UseSwaggerUi(Theme.Dark);
-// Or with advanced options
+// With runtime theme switcher
+app.UseSwaggerUi(Theme.Dark, c =>
+{
+ c.EnableThemeSwitcher(); // Auto-discovers all custom themes!
+});
+
+// All advanced features
app.UseSwaggerUi(Theme.Dark, c =>
{
c.EnableAllAdvancedOptions();
@@ -104,22 +127,27 @@ app.UseSwaggerUi(Theme.Dark, c =>
...
```
-### Custom Theme
+### 💡 Build your own Custom Theme
```csharp
-public class MyTheme : Theme
+// Organize themes in folders
+// SwaggerThemes/Brands/mybrand.css
+
+public class MyBrandTheme : Theme
{
- protected MyTheme() : base("my-theme.css") { }
- public static MyTheme Custom => new();
+ protected MyBrandTheme(string fileName) : base(fileName) { }
+ public static MyBrandTheme Custom => new("mybrand.css");
+ //... and others!
}
// Usage
-app.UseSwaggerUI(MyTheme.Custom); // Swashbuckle
-
-app.UseSwaggerUi(MyTheme.Custom); // NSwag
+app.UseSwaggerUI(MyBrandTheme.Custom, c =>
+{
+ c.EnableThemeSwitcher(); // Works with custom themes too!
+});
```
-**Learn more in the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki)!**
+_Learn advanced usages in the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki)!_
## 🤝 Contributing
diff --git a/assets/swaggerui-themes-demo.gif b/assets/swaggerui-themes-demo.gif
new file mode 100644
index 0000000000000000000000000000000000000000..7621bb0e0534377c48e12c0a1a2a7bcc2198ca05
GIT binary patch
literal 14018487
zcmeF2B#QA**rb@4_?VcSm{=tLYuFT6I8<0T
z)Y#aB*f>Pk__WvnRvcV%9AZu!Dq&na04^N|9#9-W%?_a91F&fm5RwoO(h-nx6M&=$
z$#@BA1&QcDL`-}{Orpd%yu`#rB!sLaO!B0xiewa`WIzTo1~M`MdNN^SatuLoGHUXF
zqNFBgDk#CdBBW{woGI+OvWb6iaIQee5`m3tlT`Tsyu9@Y#jVb
z-0wg<_>4SJ_Pk7NAT|L$E>k`MK|WnS0TW3C2?+^FaZxE=c_{@cDSdk>pV!Z&rDf!0Wt5cVq^#xSU&vVo$;+$C%j?N2D=4Zc
zy-@m}l!27W+KMX5FH|(ZD%K9Fnz~>Obq(hC8X8(!QfgY#qS{(7bzGu!vQ6~f80!UB
z8j8gkf~}3*{EW7ejJFp}jqJ?Sl`PCGEi5fGkz}7vx)W;$L7NAoeQY
zUF7@uoInTjpcVI!FzA2&zW>E$Lb~!HSMMM0
zO&Tg9vuP^xw_CQ8Z%%$uVNpS8aBTTha%JuE`?v1}QrAl@Hy=Chj+gK5?tUH~-|rvZ@1NZN{Q2wK(ZfGIKK{Pk
ze7fkwc*0<-&@%Q(6Co3Z280_roB<$erDe~U+t)sV)zqio81q{Le0rfw&BL6r3-(>xNto}b%
z|Ic9c^z;OH!uWnlrRh^R00oe-43Cks_C|g?0mC)Dx4J*jO4wVCH92Smu&E+-HJVE&
zQn=@exajNOPWcNBP2}=X@#tntzY8YnXyG;RR)P^S6B=1Al&mN7TL;%weX1}l(azJR
z$$nk)xmJBD*t5pip4>C*W-JtW)C4Y@tldovxt&>0G5wjm=E9nds+w
zSZ}&sce|%eoA1=nDTlJOF6Em}}<)IK`C4ZZ&c
zPE!8)%`?3665H_8fR%)Iz1*c%s`Y6rNsa-yCMG)hBc;h!LCZ9zZ+sn((u^Es(w{_I2)Cs^SEmObzjQ9wqBfLiTRYbC8GW0Q%a>QWsC|l(Ox6g!JLN5)19P2#DHU!K
zl^%$P6IBSLCQnLmxvR+(rpAw_Wn*Nk`AVY~56IO{)P{NUOZ&LKREd?H9aq;9j(jrk
zpnNhu&T@?AE6+ef>1i@7^bu)kW}c2mCd2yX7fsq&zZ{yC7t1^n)f5FhTf+9t>JpY&
zOz3RaIm+u|V#J;up!Y}bi_u9GCRtsGEa@zXQV36jTLDCR-}QcWYTbjy56zCmnB!^m
zuK)f@bfAYhmQjrS8B>uDxg5wBGH)EZJ3@oM${O*M_kk#g^(WnRIA_A9^*C=q=#J2S
zSr5^a$Pk_Qr0|g7yP5m6=^cO0gAR!ZY|8uG5g=c&3>1reIj+K)Yl!z5
zH6Z18rOL>~*r)cXD6~jOjoXhA`xc#r!{B2jES`aZ3#`YW^@~VaGl?^~>0miRXapq@
z*%V80-DVjG8Th^0TisV^^1n9AwW2*>itH6Ut>$~vOmplZ)q9#X5j1U?uWMf=pB-E8
z&uy+DEB=b&^R52NKB96_M)Ab3(gW$i&e4nXHNNQD5{`2v4F!Gi6f-<
zV?|#-S1AWb2x=qIlBx`W-xuP4M!Q4}t1oD~Ua8u-mmuw_MAwZ_?m#SkxCv;KhGoVD
zbX!c+(DF*qjX3fpbrz|^N;36jIu#@NTIR%@8R
zXsm-!+j_?yfPV(G%PbNVbG+I>F|D@`K2tBk6QV<(yf++WF!KKGaJYq$%{g!b&i^Wx
zn@FxPG0Wo`R^X^3OY>#+s;Gr8F~y2c?^MqxnjpOyDs)Ec!CNKJ$2gdIJPpAR~
z&d2nHWe-E!r^Ui$+55KYxJEaidWq(;^e8&sU&hPRkZyT>QDAZ@T3$zNzBVwQKc4r6
zL>aw)g609k#{H=DY*YMcidyPGV68azTF^s_7{^d*v}hY?a#|1bG5mhQQL-p<2E3)8REgMHRgP5zmfSX1X0iv`VsoxeyUZR$JOQ3hfZjhvpl;H)Zbrww?54uku65e^
zL+bj~D8nHF$=n6Y=GW1tJ?>g7yfg~y>IGD7t5Qct<#MNAob>fXCd$CUzq;N~W^_b9
zcxnX|=^FmLW1rK#lszk&^0$Hks{mk-sMxv1r-zlC#@pKG5CWTT4=b5~_D0y(bDM+U
z)p7l2%_0y2CxPzI8M|#w&Ym7N-q5urmiE>6o0&=-=+85k
z@4Fedj-7B?4#J`W3w~4F>HkEhczUbns_bC!e(1vuzkChY+&xuCf4uEQ`Tx$LAR~J=
zXu$5N`)IkpSdRYq{QI{^#>WG)2DL!}3ctjh%>&TW3z=~(FQ0UY!6Sjl`!Pn$AL_u|
z1B$uMNya3foZlPAO3&}-EJ=NnO`*pk6`hL-UN^;Zy(cj~!Ao!SZ?!@Dr!RSBmP_()
ztG)BiY(EFDh!vr#i2hJJ6?Cn97QSr|+5hSjDYx1^3+UPiyR>~CxP+L!>!RA)3CZ}i
zJVg3^Q10=jTCd|t_WOa?_?x~?lFWl~(+~av4PRnQSFx!lsKLabb
zb?$lR4j)2Rs@RavbnTN4^h-Yt-?;YPT6SZI4RS}c9*`Gb>vk|15z3Wtqd>UJ72G-y
z@6A2pD;5T5G^(ls#BwG!!;8EMxa88{vp0*rJF
zL`F#C1*36cC=|3ZGFCG(`U#Ee3W!S7jI_syvSW+-ARUzrib?_D8O=wgouC|cqwGba
z3pH^|PEeMj(MA|il_ycP=;+3iD9ib12PItlL|miBn4Z;`K2!{PJUR^>-KH7cuNgD8
z8r`26J8BlwhK}t$iR!J4`6wONrx|Ay5!Z!|(=Lp8l^ADKh_fplzhoBQgO1yEi9gfC
z-O0q=&W!t38Fz?|IdO?U2#mkEkGTWHcbdgbqGJ{TadT!-b)dLevsk>Uf15`ynnf>E
z#*om*V#p*BbjFMX#?V#84zEUA&L@?uCNV|Dl!BtG0a2`LNg(s6j1yGQYI3kqac(n5Lz?*kVgD!43aPNlr<9aoiuXg
zD_O+K5xu?<)L$iDed%|{UUiDre9LmE5r!xI6`d^u59*&V;Kn@+P+)^hsH
zbNcBa{l9XC&2vXOb4JZ`lV!4|)^ewR8o%7)5^twBS~VSzFk5I)%RRXlgwf_GX462OdKYX!Gi8P7EfM9d02
zC(_+q3uVj-FxTXd)iA0qnAs{kv@y*-DBVOF!5ECNmMP|vEjE-v
z^130p2-9B4L`I;IF)k@G=Bc9EB{1pKclXJ1GLhois0>h47$6x$kUWHnQROeyJd0%y
zD&-GCs&thaMwjXYml~}n4F#5p2bHO=mrC(RN(7fk%BDT%FMqw3_R2LCq+R}O5-Ir$
z3DzoioGj;NKv+v741mQ3fMPA_BF}kz(ued=x1!L=LU?ciiFRe=Lm?!(;O6PH!mShj
zNMCp@Q}}+Z;)yQ&+Clo9M%UV*tlYtgoCdy}af_S|^PI8C+#K7i
z%n6FlBfgT(y1}=)n(rd=CqN{2pb<{0&xM437&)z{Ip>qLJ)QVbiTGnOwSAqnH`=+g
z=DBT?HGNunb$nS*51E)mjceu*R_XjY^NjDms_Q2+cTO7-o%nP!u03o
zX^L_c9>K-7XDNm<k#52Fg|>SfS%Uh(&0UDV8J<@ISd0&05*m>Nd~
z8WSh_$!fc4-5Yyj8oNRU0Kcm#YkKfE@BlI0Wx-H1V`j%x!9!L-{YE8baH05AdOlH?
zd|(%kB|;W7q?8Gd=Px#Q84`;@l${OC#b(_S8j<-Im
zD)$R+Rm(!_xuak9l>f(=5~kC^_N#<iyJM9P)>L=SUkr|CYX
zDNLnl|x#P9}b-!>+Yr}Zk(>>qK<
zxs}Umb#2&roH=KJMDG$yQIHa6)@3F4<*1Mq(lhNwM>INs#>%Wt`+2(Avi5Up7uRb$
zCq8y`e*FFW<2Dmyfua9!I%lnW2A^pLx3-_0WM;m0df$9tC})7489E@CA3#6NN&;n-
zUu1sFADo^7)iq_wcJmU$@GXXjyVBdIyF8vID(fE8#bbvYWj@Jg!pnL-l|By25-)))
zTkPq(GCdHPIZLwL!w&ao!@Sl+!R2VVktC00l;yJNW{L5}viU_Sq^He9yThtx)Ny)D
zZ8DK4YHUC=dSpD-$8*%VyPeRr)a&btgJ6t#Fc!k0Qje~t$2A3DhJt(%
zUiM^o*AzD~v?Xs*@?;7WH^45m$R4&x(4E0MGXV5lpsibAy6FEHvbh*LGfuKV`e*Z}
z+s6ZshO_GWfvno_exe3@%Ury^uTQ#jtq!^X4yF^LZDLG$W=txuFVWQ2+=Vti2^JsA7A%xt&Quof`_%L+B%q2?;xJ`KF92&ILT0zAbwjOF&
zt}H((7n(-D-YoTdC_n6^>}l$gbl!T+)=*QvRwbOG`JB@$qS7&NT{^Tsi&4(M}H~a92eAlJ$-QM{+
zo1jAWVB2TgN_1?yJ);KtB&nmtPIStDUQe(ySOri{Bq)p^EKDTGBF9N%%ld9H-_*C$
zf3_23JCAj_iM_o^xpj#Xenx0>MsqR1Ds;9xvA~h{jqLA6-u5@T*{PPAsg8{*oEOas
zf}fi5_5thrymCVlmR*WrU5Z;rF^2&%&bl>pw_I7^
zi=W~b>);Vo_o2|o87a3
zyQ(2ivZe68Pa1LSt&bHvj2ZQw1;kH87^
zj{%bHQ-+z1p&tC0|E4E5zR(<{^
z+4YI8Z}9JIF>UtI*V*$Lx958#dYE75*K>Z><@l2W3g-!18Xu=WK2|0YI2C^Eh{7LX
z-6D*euTFR<`gZyGspqor>Cg4{G_ZG99K-Z7LUFzFOSAr1p*Tt5R^%Gd+g1
z%FIg3VY4;{qy0!cJJE`uNxSPtx?W7Wn|XuR*ost0MzvAj%ba`)
z>|5{o5(#X4(oar0a|LlQS>&{|qh3LZgH3uM&kx60UU2+HASKB}JeEQ|L6lF4+cHop
z+ot#BzLE^!$$i7-wdIG)4ZrtOu0L{qUTuyX4M!bpe8}1KJOefSVS4aCn>?4_Igsnl
z{km50`MdBJ*}$EHjs)KS{#A%?_gfl1h-~*ORJ}N{+wW32rLYvrwYOr^y+kcCE<`8+I=63519PZQghCXbEJr9
zm^+X8^fZYvnD@Iksmfnn4Ux%WO>kwHZ^I>KpIc50N0XW3h#4rzrTWf^|G_a*6_?{m
z>HbN2H2XCFc&&4zpw1j-)U8J4n7Vj<=R+$0r9CB8(LgTGfARdVrrzkafi>juaay={{YpA(Es65gm_?c;c##kh?87Z
zqT;i3*MOdurt5OVNhpHk_*z6q^N6kUI2=AymSy^Vs2t2sb`u?Y7^HjCzEWKCMr?i^
zO4xz**OVAi1t6+}HBe^SLTH>a1-rPmJKn@#-f0WO{WwC~R9rDnSBlL7J3)%Ha&$*2
zQ4AT5X>Zuu+Ip(
z9XxGF-HW=kSCYA@x*qr8ZJh3Q;nkZ|I4%cV7{RDeG8OK|mY#}dC`M_SWA!%K=lL6!
z@qNGVEM<={zL0*qeAtHhTmUNOWFGEDQ{~48-VJ@!{JC4wr~Vwj_1)YHd
zpz`$4jl6m4!Z7(ad9gV<)6(w@F0J3y$NZOqB3b++dc*?->xlgd{NE=fXV|`vQV;wR
zxwNPu+}pZvXSr`k5%A&+fJGzoF@uVRm-6(}J9$-&rynYEhr%y3)*8P3xjRbu@7?d6
zl-BNh^pvFEsWgdkrLfjWqM|4br65KBvL!w)M?zG}Q0PSnDk|BeJLb
z$)W+G*;*`BGamuB;K7M&GAdRn3{vOb#Ujzx6(5@2E#*Gk#|Cng)W6@Vv1Wd){qm7R
zq!MERWTm`6io&96F!He1ZPgxH5+7;VEQ>PXGc&6!OsfqAJ80?d)5W}C;mck$XY|}SivGtp{rn<1y<3t;Btb^kiK8WJPWM<~r;tUxtU?jzsTx~k
zPbt#Vk1Bho6viCLH|rDsDfj5mQFggg|9$(Xyl+-tSgu41Kd05}emT!QRZB&!gAdgA
z<|)2~t0l0J%oWa2Q7YuN5*_h*)6FZLD7W2o2lx{d+CkO%`wwL4%=ai-e5mf#&dQZm
z`P4<-wzQN@`)-1~43StAx(6MghXw!e?^V=)FDvr?-jc~;;G9+9tfF5E%~#IbE&G(0
z7@5i>nvN_O`{qCNHyh7abKGc}fB}Q6;Ha>&Cc9tmPj9)2#DlVu
zKo)2y4Mvms?E96L0$fiQu$;wW`bujB*wf9p+hV1CrL6(s>ERe+p|=`Ex)f@_R9hIe
zcDK?|&Wxbqw?oL!E9-s9VUE>JRR}Eysk;_aNEUuKV6H3q(8uYb*5=NIP~_7#6d!vZ
z#j6k;IKlY2(OOb+l0ymsAk%jNYl3CpeD}oy7h@Z}v+Sx+?v)IOV^IX+SiQ!2C;T7&
zv6aE{!D~a@#E3#!gAYso)BD}V7=2gDO5&pRI(QLUFIg_a?5aN2
z`Kj>!rsAidn{4FzV#VFNV&$SU-HzZ-4eD(*4kYe+bF#DE^~02u3yN1gkbjm}YsG1-
ztF1=Y>X7;TMJJ0x}6)=|lb$~ULgU$#*Wc(lHN)@J1fKmEu4bS(l-L_by
zjjLprt4zz--2Ao$R;KSQSo}#^Y&{rU^3-8svnC7P7t0Rc?>S`pbgg{blet)f>YtU=
zko|EB=#yTXe#!TPqGFm(#Ltoc50+ULuBBLP-d4<;NVze#q1!URC}GJx_{B+5Lf9@R
z>%%<$3!IN+12EOAM^X8GvVy=-y72Fj|Ml#17X*gmgkQ(McvO5CARV`~x&Cddp@`%C
zYgFg&b>?%0BZ3$A(;AAm_4pbu3=M+ThyGsWe!-XXX$Tqk@x*eA{xWtp21(e){_nOt
zQvOK8J9zhF{_PxD__?!p$O-GmyXB1Vnozj_|#dk+Qgfd&5X54^fQi=q)C;Qlf_<}kJb30Cw`h~Udy`@FMU`R_B7-moR6($ZZP&&~KmvKssLJXlfRPwu1
zUbo9(ftLO?dkaI<%#~P4Ru{v57DF>};VwJtWDQ%kK=5e{ca0!F6=%y-#vnsXQwgwJ
zx?4D=+u$a%>)mjI9q~WY!WAPjR4MXgA`;>UW(yh#ou?!V1%HHrgG)Lf$s>?rB@T8<
z)7%l(2{1t^m`7~XZd#aZ6KrZVYH~y2yxGlau5SHy#I#i1y=Szy1ne|C>KQuf-Ky>a
zSGQ^%@lsQFHd1#r((suEf6M`YU{d$R$q&LAbE(w`>d^@H7&E`XHw1iqAaC57gon@L
zdq|GM)HIO56raR2d0BajymSLp=XOOEd;)Q{QYwQ$I*Br1aEVM7#KP4zpN`GHgo
zMDS$fKH^GTi}O_J`a0RCk|pd@CHeQ~6cCYL!2^tqP?LKWBQq!?018pXl^bZ6x5Wwg
zS5;!ebMB9HUrkwCtN5|dFgH-4Owjm1P7!5UP!DHq*sMyiRB0-u&f71z19i3%(|}?d
z+o+iYJ!r&Jr*WRel$N?c
zx%BCip%!RYd$2&8idu$JRPHHl?Q8MT!dUg#jD%n8hLU#P0q!>HuQeI61=wPvwk*mfO`29-OQj1dV8~;Apx43S)JgiZ
zpp!LN^lFOxkuN`32W`b>k|l~~LE6*G*yb89wj{H45@&5U-FHaC&)h`5pG{3wBdaTb
zM5#va4vGAE<#DR&4vBLu5_?)iH|0*~R+n_IrPfrttF7e@^H9x*08WFaZGB#CPHonyh+ePZy&gERR
zzv!&MES+5bMQ+YEz!=oevSvVdVL-HmPwY`iQoE9IlN7y9fkV^bh|VY1qO!Tx0$4-w
zaSP~LvA#7K8fr@88YwZ#z`SctcL)xNo
z%@YxpS8);Cc=my-Pu7MA1v6}QrF=^zxYS9aku8plCa$l7a;!vSqI+sg3A#Bpf!T8n
zTh+WRR1|-MnuEDBUQU@)fuBn%P1HY4QB;+ehI9|mloP_ABezcImyRrCqjIMeMxg3s
zx)wpQ)GBcfdnkIM47L#2=_OrnbGlBB=BBi$jKWVGmTFwsGjpkG%Er+Xz@_Fy!h!Xe
zTG1A<2*Jr~qK{qCFNq~ILecCZ^0{rVm1%7MAEwITHuZ@x@HZ>`i8!UjBmHlok$MVSHpbT9Bn?-~wwCL*R(rQL
z+l(*v@eP5A7k8f2^9kHgxD^jVE`M3aK&
zkpXOYF7uILXry%_wmKj(zzm7AjqkFGkVwQ0P1NEYz>&Ga{^w)atl|d)@Dlz^axS5#
zg3L4YWY_}+bJEO5A|sST7>%^T25wY4IG6Y^Rx`T+8+nt&FAOq(aow5;x|2TPpju
z*-tPt)%*=M*-+Z5TU52)P}R%nx?gO}CFzNpaX{HmGt_jGKCiQ`re)f?I1kw8buBep
zU-K|7eHEsQCxEh*R0l>YWqZzyTAYdpc?lt4hv
zNB=m|gT~w`w&!nkZTDUkJN$cyS1YV^y0ND-{wAJ+PuHXY*kTBtBiQS?H=2dS)8~sa
z6T9+kmsAujCH)A8&E8h5;zXw5>MCVTj$@e@{yRjlKIW0pRj5K8z1sHIr>sIOn|XZu
z$_$BBEMxRPa}>|UXHUV*Qfw8M4lh
z8EtR1lVoyl>>d4(#4>>I9ass~{Wd=m=^v5opXeB!4Z2m_dN{_l2P7Z(;)S_v-?1d`
zEZ}=5Qrc@qva+bZE$uLtR}b~EJ0!plyjwo`WgNVH^|LIpY!l&6PDxLS&t`*1*0XX%
zfJZUr{AtO_4%iH?(ze4ZoX1O7l`(G*|)T7b9clkd-LnuPZBpG}?a18e&|RnXX`LR=LS)2zy=K
zT{NptH_*oZvXoBM_^>n)+dl9Jh!>mcg3TAW>s|@V!u(~&i*!r`lw)ipdt>thkYQ|i
z1Kfma{l4^$w>o{(nx;=%WweN9M6d*70_Hs|cv>@6+W-TOfA+PUH?nvYl5l)nzx%7wi^|
zJyB^l=lCNY2+|g#$1xGs!-}n5ncVmB$X-9xOn98l_lv(R&bTSgb^T-CZ=Y6Yrw`%3
zE&hbOr*+~9a>^>lB77g_QW*J66F2x%WbG`pX9wrWd-bSKB%c-Uk^N=Jn{9k!Y3%Kt
zkg!CA7#g|#6w*c)-fs#rm&SfX9=WeVOIZH&NvOLEv#>8j8WloYBK-`(p>t_pLTH^$
zfVkG(<)nNc!^!_RZATg=VvB$H;t0UKHUafxqr!d{zD(jaWbF<69Jr^7B4brR-SE7s
zby+ALjy}Kn3PXAAcfo%P>xW9e-069leRBA{g*QnjGsJ23=C$+7E}!il*Aia$IGt>%
z$l263VvAPy&pikoI`Ov?$XB;|b=tSp%hmUHh=U`kOj8%Kv1B1R6V~M>w$rkyi60D$
zj#XzD-&M!wl3#W@egBtk#7nh`2rpD=8`i3M8pv|W>fC!PvSXfI>*IGi=BQ)-8EtQ&
z>cPw*=|i#C9iYD4XXNhWr5zCSxzE};HNUC_xu9t7-vF)&!~0n$wO&7w+;+awycDu6
zWSOD~vhMP`o8OY?eYnk9>v)|Zy!CK%wKrhND>4=mB(qmmL-&p4@%OWfIhgZYe$V6G
z!_ObzzI_a1ej*ru6YkeqWE1gc!n6?LSG%wa!+%z}4I@>&w`KLLZ7M?21*7+nE)mik
zQS`}xVia38YM;Tm)^4BPvlLVu!x^o~i4tB}wU3vNujEWnS~;<2RqhK!#%s1`md1Os
z1>vz8a7^F>Us(ikr<-fjb0^!$pO&RP8EKz70*#;q$Mi1ASx(t+yVjhtJq?ox70<_u
zQNH%M%D6PHlGXiWqN2cvO$$_zc80=h>aq}v7J8h2
zm!NiX@@|H{DiAx45mnnVCp#~A5T|&eRPssu^aPtk^5!IkRPrQRYM!C{;>U&fp1)CY
zXBk^DfXCT2ghXSlIwoEP<-B9c!1oEX1V&uG}XQ~8;0zPp_)ZI7F
z(8e3QsMSpeum&AA;j@nYU5wR2%H>42(IZmcbsQFV&q}#p+5c$!EV-}IMW1vdcXcE0
zcqvsqPV`E(id^E=)rC)r7I_MQ+sH6n{`seCEZ;Is=H<_~R=UE=>^hQDTmgF0dL*oA
z=3l#?{+jGvV9|;UT-fWIJei&D7+UZw{Fs$n*`qB}!)BP(9ML^dLa8wkiMNb{$!l#9
z=7k)9Iu|fG?dt)P!a+hE4Dj1~KSF`=Qel4|E!Mk2LjOYtvZ_^lp2xxj>$jM~TqThl
zD~ZuQhX(=k<1_TcgG9Qv91N$s+gtXk!(G}e1u!e^(g8&xkR}ONtjk7bT%*Y~#w5
z&4~mL1`uE=+bvUN5^3-uf{klJvwlyBqIn)ESgNHRT&c<0{1PRqRc!yOGL^4+A#=PC=|Ism&fxh$TG|MWFJ_j&9g&R6z1ae4gjN
zvFrMnkpT*s?HGJBJ*F^DY8k?Xi*C&IQhadPm>Kfnz%*tt!|fCh3hX
zs;qLsssJ_-VN)T^K@)!k`eqpw!kRL~;u*t%IiiOiuH@h8MFtyV
zLJCP398ex63r#DIXyw*emr3V8^LYAYgCqqC*cI<7ka}#zBwz&I+n)n5Vq6t28GF>5
zi%HSXh6SPq*oO6CdlX^ggCsdoV8-J1)+G+Lz=05vP+m%0Os+4?$NLD3*?F5_V;qiM
zj1kfFt6bUDJ#L+OHJ*p2H_@ed6z;#@c#M*_PDlSM@F&8|yj$X}uh?rpyI6#(hN68j
z*@&OED?gctLLw|h6F8710Bs``{3{A>kV?`J(T?NN*=lC6)C9KQWIOe3=X3twa(;i4
zd(tYG8^XKEiIPh;KgP(>?{hO)Xy%Tg81BWx{4VR(>sX?~sLWOSQ}&WtD#zeJo1?A0
zLDhJq;&xGj4;yr(jR=9>h9vlia!_bkN{gLQCmR_mn}(c3J%4t+@a7jg!#6@(njv!H
zRxUWOLrKF@?O52wjLDMXl$u65p#NReRNil&QCi@j6#PPkaB6Bywteu#j^fnp6NZMO
z)gyTr&JYQX-iQ+q9!c~F7PDNOIh74CTA>iuA+m{pH#8w^m8PtVP?mOJr)__xSQGbu
zKMkSZzlS68HDU`MaDvBdAAv-3S$#r%qjjHC?`P?izBS@9=i45>Ps
z%08KLq~M?^vMk7fwTNxmjOrX|8+b6$ysGl~k55z>kdxTHLshmFQ_P=S(bC-@l9jIGC-Ng5tgD
z{hqj05&~jHv!TI9t(;)CTSOhG8UJjhi&VbniuW;kTxwidv=GMHV>o+7E6HW0W~;BD
zsfMjTjJ>FeqhE-vqP7TDlUBpT@QL;(hyeN!89kCJUSWemb1kAnZL>X`rSS=ZBPOlAq3`Lk;VWs93{ngDPZyv$;j>Ie*
zJn2{Cm>=Mar3XDK@kl3T96_nfF!=+`1&jt`2@oe{33{kJpbJcb9QN!&ndLA`uvXQh
zGloNLnPGa;0Hh*&@rflDQ*RuiS2pOsgk(Kb?h2gqzaNkb)J_P{D6_*aN4zX^w5ZtI
zFORZdiCY)RW)Ov11dZc|4v&T2Q$Q=%!*D1i9+Eh|X0z)Zuzj_t`??;kw%B03$Q4im
zk!M7CylReG1hs82lrBc60@`n1CD?ArKo?s(lac+X=>8|mR^yzM+;Q};@vgj_68V7Mxd=hC?|X4YiL{W#L#HK17_hPz=wM5mDVvL2M54Av7G*LlB#$
z5=$Wv#(^5R#({BQ%nPs1P6F5ztg!P>V6W;^Q+ue{19IuGR3x{~n1CMM_n7K`?ZJtUkOPyr8g)7o`
z4Vv45GxPUSSV*lBCO7qx6u!5i)p~EJvHvCtZUZJLyRMTPxyotYw&FY%>m#{0GlEglk;G(+tO)GYIzYQws^~JbWjJL`
zj>S%{ujVnxllnyyX#86&;S8M_!7>|R9}h9X>310-1oUeVV$c9`!8k5jiG>8kyShwf
z&|6z@>+aM2wC|{j^W;`ur_QmSKBuILn&C1To&mWHQ8*YPA&P~yi~ROTDpxwJ-Du9S
zi>cRW&U~1uB&hn+a4wz{aeGzJ&h>B};|M9UC)4xaD0A?r=nNY&O}qB@s&WiNw%b??FCed$xM!^yN?)(+ObXe=uj{p_mMIaNJYyzEt{GVmvMs59h^+s579ptFpofA3+(QI4
zVIG{E6gk`ri@bTSWa9w}>&bkb4*bg{=mE>_Wq`t@LjuM`((XYgJ%Q43Ne5o(3n*h(
zzJMwgm8$StRR*p;1cbWqNg1RzB)2rge>0rCO(T+e$g#QPQyVv}X-a#cMVtCoH0od0
zRP{1zW-$)}q%H%##jLo($}1g`a)Aw3LG{bAWjb{v&1glKF3@bB=Uys^qAnxFTGIbCX6v5{n~n%DQy$L+7$A`QXvtJh5TY
zfcDGn^lhF@R_A1As!ZMr+hwUr-a0|-lR6sqTBr&zfBh@8<3dHcCw-h;<<)K3&V*wO
z`$;s`T+AV{m{3AzAVCZve$qwWnmx
zDy)Zg)`w36uP^f?LNkw!AkG9hoc;++{pzw@P^O6eO=(rrU6@B=`CaamFo2R{JmyX<
zE&x3xOPz?p2W15=(_4}8z_GB5$I5*9E0AZUr^PHkI4V5XffOYoUT3T=FRNF^s=_11
zgiAO+jn%GcgfSh|cPTgQuXBCLZa5ojmrei?aiGxUAno*uTTDQ)
zPcyRgKwu`Bf6Py&-mk-PqeEFhu*Om@*-|8~MxHXN^R~HDK`Rl1tB0i}trZ1?LtWLB
z`Fm7B*(4%@Se$H$L%6i1FI)IQgV%yECO9Ss9CPrDfYl13EQrmJ2ugT7ZMhg=02qmu
zK6!xlWB3EpG3=m}(^f2MFvY7u+SEh(emvz`@*&58!&oXykHLZ26pX)CKv6775fvaD
zh3cVh2E~AfD_9>#y~~IA%{&FC2^8-ki|VGnMLK3es(k)DODYY%%Gm6?g|GFe*ZIK5
z2&gv??K-#WU$Cy=+cXO?ks;h@L7&g(xN)qaP_tSnHJXPd7Sde{oy3I|Be7UEY1&Fv
zOg?Dau9-0UZ2LiU`Pc^dOR+hwFg&O;#NyJYx!>a$
zL!~&0iKn@p`KPr-4VAChv1o<|J0-Em8uJ-*F|P@a#XSb^Fu6DWaLZEDedBmi@z5OL
zQG&VHUC^(3a(XD+eDt^B@qVF`>7(fMOBwsTi*1@sg$g~T=CA#muke7_ClvCH&;t`b
zO7|s@lwcWhG7YD383chcztzqHxs3Jor-4@3OZ8ZWMHY?@aNCpf}6sUS$|sh()7XU
z>67VwV9X#s-Fao}8%UNcIluG(B?gqN14{MS?*N~NAMEMI#$QXr+BYZsmqz{%0A4_$
zzb@>;_0$0)=u=dwI2%e-5UGNlUBfM;LU9OGDkK8A$bxHV^*)ePCkBIv^MWe4|HLk!
zVizfb9B*14H#w+kQHk&Is7|@4nkuRa@-MQgFw)|5?riCiWbhZ*kl3z|Ik^=LMcpN
zYZ#T#=7xjumMn1ITp|Le==xI0OVHNs5^9eu+$2y!%)j&B+i{9g?!s0q14R9dEbKwV
zbVSf@f_?GA?l6K3CfqLU6xHO0BB;YdEJGvU=CuTaof*|lweM#v+M#P^c5rA)iQA2
zE*L#JgghY>!Vk$p>gvN_Qyt8^W^#NzNv{H54TNY-R&0QxU#`O88pJic-6n9$N4x{T
z+=<>K1W@wxcMq`7)XjeNJ%JwRe+3xm*nu5PM{>(A{c>e0gTys(6JErPW&$8YLIuyn^=zTM@DF>|bC1oUO?)sI>h~C$
zO9>bFRU||@s9}{BKmWKeNF=xnAJCA(gda?T6@Y>R)r%$2QjLOs~)TJk{;>7B7|LIjZYx;Ekxkl+%vrEaQ
z{Tepy8n^Yt&^Giwe9HVhVUT(^hUpo#2O@?*qc
z<3fgf*)r$On=Ky}?OAhW&6fk8PCRh*nKNOg
zpEEzu-t7`2iypKyyy^>M4`R~MXBS@ZA%+S?mN|zWZ+P)Tj9{J
zOr`-RA9V0R4mou8(FY%W_SuJ?74g{zA9T>s2e}zb|D*>+|CUK{A9PHN(M5f1d=bbT
zNh}dX8R=mW8aaaT(L^9kM2_6bIoYtlgk6Ea(5l0+)GY;wjIZ6xx=6u)FN$uD2jvCI?~wJ}U3tt2#1
z5+^kgOGa;uQO7n{#Ii~&-wgGgDyclx)mLGS^~NG)J(V9&G_fL)h3wda4=XeorJpug
z{jyXvSA<5EW%OCa4R2g@RmCEytaeNtuWfam9H+##%6FozwMurl83l?&5-G@!bp1%E
zU3D>01(RKz*(4N96v;!~JrZfx-+t?mr95b^|ItR9aMJ0mw;-p5vEhduju_$35ahH<2?MG|H$oFxe*+>Ao`R3|01#F`jaw(;UR%(f*xr54Erj>xA$?unF0xBtpcyg(5!bzg4s;N$LV{yh$B1!Ve
zDVMyg%bCi`D6h6^$}6wP^1KeMva$=W(Ax-WF3kEOtaZN#bKP~n6l<)o+gbmMt-^Ni
zt9IM_It?t)E`zLhzIr#_HN-RnZ}>Kr{}-*p;-7E+Yen)6LyR@TkSO27)^#lmq7{)N
zj5VkrOOZX!5QFg0Ao4?wL{1vb{dYHae-A(MI7|*d6fqDvtnUmGDd4&YLbS(V&kqK4
z4FkpC8b2(e4qxLJ`zV8k)L8)#A<_}IB1Ix`h3ZE1*oQOF&?8u}qgEhf)jsUR3m2`b
zTdb;KtFl$Y5*iCzI24!1as*&*aWnQ;RiS10TIw}5evly#YmCxRW2M?m0U=}
zDay)+$bljaxm6-4La~cM5?2{Vm_`$6F^%}R)iA8khdtP#4tUrDut3odD~2kFGt$R3
z6wwBv1;bk=RGq7VI2*U*->KZJzgYGN=-Cf$_?I~L>?
z%Ya5Su5k@-AWUHl1L7-T$uTtTqhc821;#c8FM`RLExz!DCCK0Gi5I4A_ER-k>m0RQ%$`hmQ=sHI_oM=TUkGmY_o^ragtW+(InO*FVw->(Hg?Em*UF|AUyW_?5
zrZ(m2Pbq`E&V+__J6%jp|APmZpAN(`i}9UfRD&2XoUb)uu&QRl&^)SQ<_uJI%~x9k
z)-XWtF`02qW|&8s&76;Utg#+ezxtN0F7Gj_feae(3Oy@~!WaA~5u`{6SRG>VRQPzu
zH5Qr3NFnk>d^(i~ul2(!1}S8MUF;7n`&TRNBOTiCMvP*#j#Q$Ev9baz4;AaG(yCUm
zkXmgaUHID6rV+B1^%f3EbgNwLDi3yWLn44R3VzVahha34i-s8vVwLbjvW*oE6Fb{i
z0f`;_Fc&rb)iP|dtAVK?Mjf!}uKkH}UjcIlGzexelC^SMU)tq+B^KWprQ<*t3&t+Y
z5lbL~47K!FM=(u>|1y<1gJt|`R~~)_+)O}|!83y+y6BMI{pDd@qs%7LtO-vItAbAm
z)5b0$;)hhMCmbdNF?jVAGz}|_Y%6y0+Az{jwf!@nHI8wOJ))5uvu($3qe)9zVo;X&
zgl-WriI5?hU;QR+sJxk{IYQm~|gfSu(r%ugkS+g3l
zv!1QCUbQe?|Jyo^w;II~(&dmk>{VF$NOi2PRBKp4lG!;lQ9Ya+*%6X!lIU_-GPk?5
zGjwvh?;`Iu+a<8`7HpLX^Y35vo9qvq
zZLSNrn$VfQW!8#Q=;~#bTw1^QXK)vP`<<6O$UWO-lOjjtDiJY@k!zxXmSSu^=^>V)
z#kgs0^a;jf%*GmU#vsrEGtO;B;^gwi0{5onFzTQrO6CG*a2|>&6zV5_Z01PNZt`ZM
z|E{cZSa5Qv;-ggXDzHo{x+ZsJ
z6a%M}>Z$mysZb;B+>Wa_V|t3prt