diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 9a52a20..d43bfb8 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -2,45 +2,51 @@
## Our Pledge
-In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+In the interest of fostering an open and welcoming community, we as contributors and maintainers pledge to make participation in this project and our community a harassment-free experience for everyone. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
## Our Standards
-Examples of behavior that contributes to creating a positive environment include:
+Examples of behavior that contributes to a positive environment:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
-- Showing empathy towards other community members
+- Showing empathy toward other community members
Examples of unacceptable behavior by participants include:
-- The use of sexualized language or imagery and unwelcome sexual attention or advances
-- Trolling, insulting/derogatory comments, and personal or political attacks
-- Public or private harassment
-- Publishing others' private information, such as a physical or electronic address, without explicit permission
-- Other conduct which could reasonably be considered inappropriate in a professional setting
+- Violence, threats of violence, or violent language
+- Discriminatory jokes and language, or derogatory comments
+- Posting sexually explicit content or sexual attention
+- Deliberate intimidation, stalking, or harassment
+- Publishing private information without permission
+- Other conduct which could reasonably be considered abusive or inappropriate in a professional setting
## Our Responsibilities
-Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any behavior that violates this Code of Conduct.
-Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+Maintainers have the right and responsibility to remove, edit, or reject contributions, and to ban or otherwise restrict participants who behave inappropriately.
-## Scope
+## Reporting and Enforcement
-This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+If you are subject to or witness unacceptable behavior, please report it. Reports will be handled with discretion and investigated promptly and fairly.
-## Enforcement
+Report incidents to: [@teociaps](mailto:teociaps.github@gmail.com) or open an issue marked as confidential on the project repository if available.
-Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project owner [@teociaps](mailto:ciapparellimatteo@gmail.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident.
+When reporting, include as much detail as possible: what happened, when, where, and any supporting evidence (e.g., screenshots, links).
-Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+## Scope
-## Attribution
+This Code of Conduct applies within project spaces and in public spaces when an individual is representing the project (e.g., using an official project e-mail address, posting via an official social media account, or acting as an appointed representative).
-This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+## Enforcement Process
-For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq
+Reports will be reviewed and investigated by the project maintainers. The maintainers will determine actions they deem appropriate, which may include asking the participant to apologize, removing content, temporary suspension, or permanent banning from project spaces.
+
+The maintainers will strive to keep the reporter's identity confidential when requested.
+
+## Attribution
+This Code of Conduct is adapted from the Contributor Covenant v3.0 (https://www.contributor-covenant.org/version/3/0/code_of_conduct/). By participating in this project, you agree to abide by this code.
\ No newline at end of file
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
diff --git a/README.md b/README.md
index e45d67b..35695f8 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
-
+
# SwaggerUI.Themes
@@ -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-Dynamic-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/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/assets/swaggerui-themes-demo.gif b/assets/swaggerui-themes-demo.gif
new file mode 100644
index 0000000..7621bb0
Binary files /dev/null and b/assets/swaggerui-themes-demo.gif differ
diff --git a/assets/theme-switcher.gif b/assets/theme-switcher.gif
new file mode 100644
index 0000000..fdc1a5c
Binary files /dev/null and b/assets/theme-switcher.gif differ
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.0
14.0
enable
-
+
+ true
+
\ No newline at end of file
diff --git a/build/Versioning.props b/build/Versioning.props
index f0a34c7..3e67247 100644
--- a/build/Versioning.props
+++ b/build/Versioning.props
@@ -10,7 +10,7 @@
$([System.Math]::Floor($(MinutesSinceMidnight)))
$([System.DateTime]::Today.ToString("yyMMdd"))
$(Today)-$(Floored)
-
2
+
3
0
0
-$(Revision)
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/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/Program.cs b/samples/Sample.AspNetCore.SwaggerUI.NSwag/Program.cs
index ab0e6aa..e631f48 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,22 +13,180 @@
{
app.UseOpenApi();
- //string inlineStyle = "body { background-color: #000; }";
- //app.UseSwaggerUi(inlineStyle, c => c.DocumentTitle = "Sample Title");
+ // ========================================
+ // 🎨 BASIC USAGE EXAMPLES
+ // ========================================
- //app.UseSwaggerUi(CustomMinifiedStyle.CustomMin, 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(), "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 =>
+ //{
+ // 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 =>
- //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 Themes Only";
+ // c.EnableThemeSwitcher(ThemeSwitcherOptions.CustomOnly());
+ //});
+
+ // 8. Specific themes selection (mixed predefined + custom)
+ //app.UseSwaggerUi(CompanyThemes.CompanyThemes.CorporateBlue, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Business Themes";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithThemes(
+ // Theme.Dark,
+ // Theme.Light,
+ // CompanyThemes.CompanyThemes.CorporateBlue,
+ // CompanyThemes.CompanyThemes.TechGreen,
+ // CompanyThemes.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.CompanyThemes.TechGreen, c =>
+ //{
+ // c.DocumentTitle = "Company API Documentation";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithThemes(
+ // CompanyThemes.CompanyThemes.CorporateBlue,
+ // CompanyThemes.CompanyThemes.TechGreen,
+ // CompanyThemes.CompanyThemes.StartupPurple
+ // )
+ // .WithDisplayFormat("Company Theme: {name}"));
+ //});
+
+ // ========================================
+ // ⚙️ ADVANCED UI FEATURES
+ // ========================================
+
+ // 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.CompanyThemes.CorporateBlue, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Explicit Themes";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithThemes(
+ // Theme.Dark,
+ // Theme.Light,
+ // CompanyThemes.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));
+ //});
}
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 50d5c02..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,9 +21,15 @@
-
-
+
+
+
+
+
+
+
+
diff --git a/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/CustomMinifiedStyle.cs b/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/CustomMinifiedStyle.cs
new file mode 100644
index 0000000..c8cd835
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/CustomMinifiedStyle.cs
@@ -0,0 +1,12 @@
+using AspNetCore.Swagger.Themes;
+
+namespace SwaggerThemes.Custom;
+
+public class CustomMinifiedStyle : Theme
+{
+ protected CustomMinifiedStyle(string fileName, bool useMinified) : base(fileName, useMinified)
+ {
+ }
+
+ public static CustomMinifiedStyle CustomMin => new("minifiedCustom.css", true);
+}
\ No newline at end of file
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/Custom/mybrand.css b/samples/Sample.AspNetCore.SwaggerUI.NSwag/SwaggerThemes/Custom/mybrand.css
new file mode 100644
index 0000000..531af5e
--- /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;
+ --auth-wrapper-background-color: #071f4b;
+ --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.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..037bf54 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,181 @@
{
app.UseSwagger();
- //string inlineStyle = "body { background-color: #000; }";
- //app.UseSwaggerUI(inlineStyle, c => c.DocumentTitle = "Sample Title");
+ // ========================================
+ // 🎨 BASIC USAGE EXAMPLES
+ // ========================================
- //app.UseSwaggerUI(CustomMinifiedStyle.CustomMin, 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(), "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 =>
+ //{
+ // 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 =>
- //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 Themes Only";
+ // c.EnableThemeSwitcher(ThemeSwitcherOptions.CustomOnly());
+ //});
+
+ // 8. Specific themes selection (mixed predefined + custom)
+ //app.UseSwaggerUI(CompanyThemes.CompanyThemes.CorporateBlue, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Business Themes";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithThemes(
+ // Theme.Dark,
+ // Theme.Light,
+ // CompanyThemes.CompanyThemes.CorporateBlue,
+ // CompanyThemes.CompanyThemes.TechGreen,
+ // CompanyThemes.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.CompanyThemes.TechGreen, c =>
+ //{
+ // c.DocumentTitle = "Company API Documentation";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithThemes(
+ // CompanyThemes.CompanyThemes.CorporateBlue,
+ // CompanyThemes.CompanyThemes.TechGreen,
+ // CompanyThemes.CompanyThemes.StartupPurple
+ // )
+ // .WithDisplayFormat("Company Theme: {name}"));
+ //});
+
+ // ========================================
+ // ⚙️ ADVANCED UI FEATURES
+ // ========================================
+
+ // 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.CompanyThemes.CorporateBlue, c =>
+ //{
+ // c.DocumentTitle = "Sample API - Explicit Themes";
+ // c.EnableThemeSwitcher(new ThemeSwitcherOptions()
+ // .WithThemes(
+ // Theme.Dark,
+ // Theme.Light,
+ // CompanyThemes.CompanyThemes.CorporateBlue
+ // )
+ // .WithCustomThemes(CustomThemeMode.ExplicitOnly));
+ //});
+
+ // 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
+ //});
+
+ // 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));
+ //});
}
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..3b05d88 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/Sample.AspNetCore.SwaggerUI.Swashbuckle.csproj
@@ -10,6 +10,7 @@
+
@@ -21,9 +22,15 @@
-
-
+
+
+
+
+
+
+
+
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerGenConfigurer.cs b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerGenConfigurer.cs
index 82860b6..58c3228 100644
--- a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerGenConfigurer.cs
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerGenConfigurer.cs
@@ -1,6 +1,8 @@
-#pragma warning disable S1075 // URIs should not be hardcoded - Done for testing purposes
-
+#if NET10_0
using Microsoft.OpenApi;
+#else
+using Microsoft.OpenApi.Models;
+#endif
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Sample.AspNetCore.SwaggerUI.Swashbuckle;
@@ -13,7 +15,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
{
@@ -26,16 +28,38 @@ internal static void Configure(this SwaggerGenOptions options)
Url = new Uri("https://example.com/license")
}
});
- options.AddSecurityDefinition("bearer", new OpenApiSecurityScheme
+ options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
+ Description = "JWT Authorization header using the Bearer scheme.",
+ Name = "Authorization",
+ In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
- Scheme = "bearer",
+ Scheme = "Bearer",
BearerFormat = "JWT",
- Description = "JWT Authorization header using the Bearer scheme."
});
+#if NET10_0
options.AddSecurityRequirement(document => new OpenApiSecurityRequirement
{
[new OpenApiSecuritySchemeReference("bearer", document)] = []
});
+#else
+ options.AddSecurityRequirement(new OpenApiSecurityRequirement()
+ {
+ {
+ new OpenApiSecurityScheme
+ {
+ Reference = new OpenApiReference
+ {
+ Type = ReferenceType.SecurityScheme,
+ Id = "Bearer"
+ },
+ Scheme = "oauth2",
+ Name = "Bearer",
+ In = ParameterLocation.Header,
+ },
+ new List()
+ }
+ });
+#endif
}
}
\ No newline at end of file
diff --git a/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/CustomMinifiedStyle.cs b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/CustomMinifiedStyle.cs
new file mode 100644
index 0000000..c8cd835
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/CustomMinifiedStyle.cs
@@ -0,0 +1,12 @@
+using AspNetCore.Swagger.Themes;
+
+namespace SwaggerThemes.Custom;
+
+public class CustomMinifiedStyle : Theme
+{
+ protected CustomMinifiedStyle(string fileName, bool useMinified) : base(fileName, useMinified)
+ {
+ }
+
+ public static CustomMinifiedStyle CustomMin => new("minifiedCustom.css", true);
+}
\ No newline at end of file
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/custom.css b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/custom.css
new file mode 100644
index 0000000..04fd75a
--- /dev/null
+++ b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/custom.css
@@ -0,0 +1,9 @@
+/*
+ Custom Style
+
+ https://github.com/teociaps/SwaggerUI.Themes
+*/
+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/Custom/mybrand.css b/samples/Sample.AspNetCore.SwaggerUI.Swashbuckle/SwaggerThemes/Custom/mybrand.css
new file mode 100644
index 0000000..531af5e
--- /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;
+ --auth-wrapper-background-color: #071f4b;
+ --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/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/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/FileProvider.cs b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/FileProvider.cs
index 06c4ef1..9f5915c 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,75 @@ 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))
- .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 one time in assembly {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}.");
- var resourceName = resourceNamespaces[0];
+ 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.");
+ }
- // Retrieve the common theme and determine if JS needs to be loaded
+ var resourceName = swaggerThemesResources[0];
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()
@@ -72,8 +134,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);
}
@@ -84,14 +145,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) ||
@@ -100,7 +214,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;
}
@@ -112,18 +226,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..7a42fe6 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);
}
@@ -993,6 +1006,7 @@ body {
padding: 0 20px;
width: 100%;
pointer-events: none;
+ z-index: 1000;
}
#scroll-to-top-btn {
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..bd03f59 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;z-index:1000}#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..8bfa66c
--- /dev/null
+++ b/src/AspNetCore.Swagger.Themes.Common/AspNetCore/Swagger/Themes/ThemeSwitcher.cs
@@ -0,0 +1,320 @@
+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 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/AspNetCore.SwaggerUI.Themes.csproj b/src/AspNetCore.SwaggerUI.Themes/AspNetCore.SwaggerUI.Themes.csproj
index 312b5c4..08f0604 100644
--- a/src/AspNetCore.SwaggerUI.Themes/AspNetCore.SwaggerUI.Themes.csproj
+++ b/src/AspNetCore.SwaggerUI.Themes/AspNetCore.SwaggerUI.Themes.csproj
@@ -5,7 +5,7 @@
- 2
+ 3
0
0
@@ -15,16 +15,16 @@
AspNetCore.SwaggerUI.Themes
- Change theme to your Swagger API documentation in ASP.NET Core applications.
+ Modernize your ASP.NET Core Swagger UI with runtime theme switching, deep customization, and new functional UI features.
- extension;aspnetcore;swagger;swagger-ui;themes;ui;dark-theme;modern
+ swagger;swagger-ui;swaggerui;swashbuckle;aspnetcore;webapi;openapi;extension;modern-ui;theme;dark-mode;dark-theme;runtime-theming;theme-switcher;theme-changer;custom-themes
true
-
+
diff --git a/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIBuilderExtensions.cs b/src/AspNetCore.SwaggerUI.Themes/Microsoft/AspNetCore/Builder/SwaggerUIBuilderExtensions.cs
index 397824d..1efae42 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, 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 with optional setup action.
///
/// The application builder instance.
/// The assembly where the embedded CSS file is situated.
@@ -96,81 +93,98 @@ public static IApplicationBuilder UseSwaggerUI(
ArgumentNullException.ThrowIfNull(assembly);
ArgumentNullException.ThrowIfNull(cssFilename);
- var options = new SwaggerUIOptions();
- setupAction?.Invoke(options);
-
- var theme = FileProvider.GetResourceText(cssFilename, assembly, out var commonTheme, out var loadJs);
-
- if (!string.IsNullOrEmpty(commonTheme))
+ return application.UseSwaggerUI(opt =>
{
- 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);
+ setupAction?.Invoke(opt);
- if (loadJs && AdvancedOptions.AnyJsFeatureEnabled(options.ConfigObject.AdditionalItems))
- setupAction += InjectJavascript(application, options);
- }
-
- FileProvider.AddGetEndpoint(application, FileProvider.StylesPath + cssFilename, theme);
- setupAction += options => options.InjectStylesheet(FileProvider.StylesPath + cssFilename);
+ var (themeContent, commonContent, loadJs, isStandalone, themeName) = ThemeBuilderHelpers.LoadAssemblyTheme(assembly, cssFilename);
- return application.UseSwaggerUI(setupAction);
- }
+ // 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"));
- #region Private
+ if (!isStandalone)
+ {
+ // Register the theme for auto-discovery
+ ThemeSwitcher.RegisterTheme(dynamicTheme, cssFilename, isStandalone);
- private static Action ConfigureSwaggerUIOptions(IApplicationBuilder application, SwaggerUIOptions options, BaseTheme theme)
- {
- ImportSwaggerTheme(application, options, theme);
+ commonContent = AdvancedOptions.Apply(commonContent, opt.ConfigObject.AdditionalItems, MimeTypes.Text.Css);
- var optionsAction = InjectCommonTheme(application, options, theme);
- optionsAction += InjectTheme(theme);
+ const string CommonPath = $"{FileProvider.StylesPath}common.css";
+ FileProvider.AddGetEndpoint(application, CommonPath, commonContent);
+ opt.InjectStylesheet(CommonPath);
- if (theme.LoadAdditionalJs && AdvancedOptions.AnyJsFeatureEnabled(options.ConfigObject.AdditionalItems))
- optionsAction += InjectJavascript(application, options);
+ if (loadJs && AdvancedOptions.AnyJsFeatureEnabled(opt.ConfigObject.AdditionalItems))
+ {
+ InjectJavascriptInternal(application, opt);
+ ConfigureThemeSwitcher(application, opt, dynamicTheme);
+ }
+ }
- return optionsAction;
+ var customPath = FileProvider.StylesPath + cssFilename;
+ FileProvider.AddGetEndpoint(application, customPath, themeContent);
+ opt.InjectStylesheet(customPath);
+ });
}
- private static void ImportSwaggerTheme(IApplicationBuilder application, SwaggerUIOptions options, BaseTheme theme, bool isCommonTheme = false)
+ private static void ConfigureThemeInternal(
+ IApplicationBuilder application,
+ SwaggerUIOptions options,
+ BaseTheme theme)
{
- var themeContent = FileProvider.GetResourceText(theme.FileName, theme.GetType());
+ ThemeSwitcher.RegisterThemeEndpoints(application, theme, options.ConfigObject.AdditionalItems);
- if (isCommonTheme)
- themeContent = AdvancedOptions.Apply(themeContent, options.ConfigObject.AdditionalItems, MimeTypes.Text.Css);
+ // Inject stylesheets
+ options.InjectStylesheet(FileProvider.StylesPath + theme.Common.FileName);
+ options.InjectStylesheet(FileProvider.StylesPath + theme.FileName);
- FileProvider.AddGetEndpoint(application, ComposeThemePath(theme), themeContent);
- }
-
- private static Action InjectTheme(BaseTheme theme)
- {
- return options => options.InjectStylesheet(ComposeThemePath(theme));
+ // Configure JS features if enabled
+ if (theme.LoadAdditionalJs && AdvancedOptions.AnyJsFeatureEnabled(options.ConfigObject.AdditionalItems))
+ {
+ InjectJavascriptInternal(application, options);
+ ConfigureThemeSwitcher(application, options, theme);
+ }
}
- private static string ComposeThemePath(BaseTheme theme)
- {
- return FileProvider.StylesPath + theme.FileName;
- }
+ private static Action ConfigureTheme(IApplicationBuilder application, BaseTheme theme) =>
+ opt => ConfigureThemeInternal(application, opt, theme); // This is only used by the first overload
- private static Action InjectCommonTheme(IApplicationBuilder application, SwaggerUIOptions options, BaseTheme theme)
+ private static void InjectJavascriptInternal(
+ IApplicationBuilder application,
+ SwaggerUIOptions options)
{
- var commonTheme = theme.Common;
- ImportSwaggerTheme(application, options, commonTheme, true);
+ var javascript = ThemeBuilderHelpers.GetConfiguredJavaScript(options.ConfigObject.AdditionalItems);
+ ThemeBuilderHelpers.RegisterJavaScriptEndpoint(application, javascript);
- return InjectTheme(commonTheme);
+ options.InjectJavascript(FileProvider.ScriptsPath + FileProvider.JsFilename);
}
- 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();
+ 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/AspNetCore.SwaggerUI.Themes/package-readme.md b/src/AspNetCore.SwaggerUI.Themes/package-readme.md
index 917be5f..8504344 100644
--- a/src/AspNetCore.SwaggerUI.Themes/package-readme.md
+++ b/src/AspNetCore.SwaggerUI.Themes/package-readme.md
@@ -6,18 +6,20 @@
# AspNetCore.SwaggerUI.Themes
-### Beautiful, modern themes for Swashbuckle Swagger UI 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)** • **[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)**
> ⚠️ **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
+## 🚀 Quick Start
```bash
dotnet add package AspNetCore.SwaggerUI.Themes
@@ -26,20 +28,28 @@ dotnet add package AspNetCore.SwaggerUI.Themes
## Quick Start
```csharp
+// Apply a theme
app.UseSwaggerUI(Theme.Dark);
+
+// Or enable runtime theme switcher!
+app.UseSwaggerUI(Theme.Dark, c => c.EnableThemeSwitcher());
```
> **Note**: The `UseSwaggerUI()` method is provided by Swashbuckle.AspNetCore. This package adds convenient overloads to apply themes seamlessly.
-## Features
+## ✨ Features
+
+- 🔥 **[Theme Switcher](https://github.com/teociaps/SwaggerUI.Themes/wiki/Feature-Dynamic-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
-- **[Built-in Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Predefined-Themes)** - 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)** - Unlock new capabilities to enhance your Swagger UI
+- **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Enhance your documentation with powerful UI capabilities
-- **[Custom Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Custom-Themes)** - Create your own themes with full control or build standalone themes
+- _...discover more in the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki/Features)!_
-## Usage
+## 📚 Basic Usage Examples
```csharp
using AspNetCore.Swagger.Themes;
@@ -49,14 +59,23 @@ using AspNetCore.Swagger.Themes;
// Use a built-in theme
app.UseSwaggerUI(Theme.Dark);
-// Or with advanced features
+// Enable theme switcher with auto-discovery
+app.UseSwaggerUI(Theme.Dark, c =>
+{
+ c.EnableThemeSwitcher();
+});
+
+// Or with all advanced features
app.UseSwaggerUI(Theme.Dark, c =>
{
c.EnableAllAdvancedOptions();
});
// Or use your custom theme from assembly
-app.UseSwaggerUI(Assembly.GetExecutingAssembly(), "my-theme.css");
+app.UseSwaggerUI(Assembly.GetExecutingAssembly(), "my-theme.css", c =>
+{
+ c.EnableThemeSwitcher(); // Works with custom themes too!
+});
...
```
diff --git a/src/AspNetCore.SwaggerUI.Themes/readme.txt b/src/AspNetCore.SwaggerUI.Themes/readme.txt
index 90c9275..7772a2c 100644
--- a/src/AspNetCore.SwaggerUI.Themes/readme.txt
+++ b/src/AspNetCore.SwaggerUI.Themes/readme.txt
@@ -1,13 +1,58 @@
- #-------------------#
-## RELEASE NOTES ##
- #-------------------#
++===================================================================================+
+| AspNetCore.SwaggerUI.Themes • RELEASE NOTES - v3.0.0 |
++===================================================================================+
-v3.0.0
+BREAKING CHANGES
+-------------------------------------------------------------------------------------
+- API redesign: Style -> Theme (all classes renamed)
+- Classic themes removed (modern themes only)
+- .NET 6 & 7 support discontinued
-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
+See migration guide: https://github.com/teociaps/SwaggerUI.Themes/wiki/Migration-v3
+
+
+NEW FEATURES
+-------------------------------------------------------------------------------------
+- 🔥 Runtime Theme Switcher: Change themes on-the-fly without refreshing the page
+- Auto-Discovery: Custom themes automatically available in the theme switcher
+- Nested Folder Support: Organize your themes in subfolders
+- Standalone Themes: Create CSS-only themes with zero dependencies
+- Smart Filename Resolution: Support standard and standalone variants simultaneously
+- Minified Stylesheet Support: Optimize load times with minified CSS
+
+
+PLATFORM & DEPENDENCIES
+-------------------------------------------------------------------------------------
+- .NET 10 support added
+- Swashbuckle.AspNetCore updated to v9.0.6
+
+
+IMPROVEMENTS
+-------------------------------------------------------------------------------------
+- Unified modern theme system with consistent defaults
+- Advanced features now available on all themes
+- Smaller package footprint (optimized assets)
+- Improved performance and clarity throughout
+
+
+QUICK START
+-------------------------------------------------------------------------------------
+Basic theme:
+ app.UseSwaggerUI(Theme.Dark);
+
+Enable theme switcher:
+ app.UseSwaggerUI(Theme.Dark, c => c.EnableThemeSwitcher());
+
+All features:
+ app.UseSwaggerUI(Theme.Dark, c => c.EnableAllAdvancedOptions());
+
+
+DOCUMENTATION
+-------------------------------------------------------------------------------------
+
+Full docs: https://github.com/teociaps/SwaggerUI.Themes/wiki
+Migration: https://github.com/teociaps/SwaggerUI.Themes/wiki/Migration-v3
+Repository: https://github.com/teociaps/SwaggerUI.Themes
Other Changes:
- .NET 10 support; .NET 6 & 7 discontinued
@@ -18,4 +63,4 @@ Other Changes:
- 50% smaller package footprint
- Enhanced performance and clarity
-For details: https://github.com/teociaps/SwaggerUI.Themes?tab=readme-ov-file#swaggeruithemes
\ No newline at end of file
+=====================================================================================
\ 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..bc5602c 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,10 @@ public static IApplicationBuilder UseSwaggerUi(
{
ArgumentNullException.ThrowIfNull(theme);
- return application.UseSwaggerUi(uiSettings =>
+ return application.UseSwaggerUi(settings =>
{
- configureSettings?.Invoke(uiSettings);
-
- uiSettings.CustomInlineStyles = GetSwaggerThemeCss(theme, uiSettings);
-
- if (theme.LoadAdditionalJs && AdvancedOptions.AnyJsFeatureEnabled(uiSettings.AdditionalSettings))
- AddCustomJavascript(application, uiSettings);
+ configureSettings?.Invoke(settings);
+ ConfigureThemeInternal(application, settings, theme);
});
}
@@ -41,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(
@@ -51,18 +47,21 @@ public static IApplicationBuilder UseSwaggerUi(
{
ArgumentNullException.ThrowIfNull(cssThemeContent);
- setupAction += options => options.CustomInlineStyles = cssThemeContent;
+ const string CustomPath = $"{FileProvider.StylesPath}custom.css";
+ FileProvider.AddGetEndpoint(application, CustomPath, cssThemeContent);
+ ThemeSwitcher.RegisterCustomTheme("custom.css", isStandalone: true);
+ setupAction += opt => opt.CustomStylesheetPath = 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 with optional setup action.
///
/// 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(
@@ -74,58 +73,103 @@ public static IApplicationBuilder UseSwaggerUi(
ArgumentNullException.ThrowIfNull(assembly);
ArgumentNullException.ThrowIfNull(cssFilename);
- var settings = new SwaggerUiSettings();
- configureSettings?.Invoke(settings);
-
- var theme = FileProvider.GetResourceText(cssFilename, assembly, out var commonTheme, out var loadJs);
-
- if (!string.IsNullOrEmpty(commonTheme))
+ return application.UseSwaggerUi(settings =>
{
- commonTheme = AdvancedOptions.Apply(commonTheme, settings.AdditionalSettings, MimeTypes.Text.Css);
- theme = commonTheme + Environment.NewLine + theme;
-
- if (loadJs && AdvancedOptions.AnyJsFeatureEnabled(settings.AdditionalSettings))
- configureSettings += settings => AddCustomJavascript(application, settings);
- }
-
- configureSettings += options => options.CustomInlineStyles = theme;
-
- return application.UseSwaggerUi(configureSettings);
+ configureSettings?.Invoke(settings);
+
+ 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
+ var dynamicTheme = new DynamicTheme(cssFilename, isStandalone, cssFilename.EndsWith(".min.css"));
+
+ if (!isStandalone)
+ {
+ // Register the theme for auto-discovery
+ ThemeSwitcher.RegisterTheme(dynamicTheme, cssFilename, isStandalone);
+
+ commonContent = AdvancedOptions.Apply(commonContent, settings.AdditionalSettings, MimeTypes.Text.Css);
+
+ // 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))
+ {
+ InjectJavascriptInternal(application, settings);
+ ConfigureThemeSwitcher(application, settings, dynamicTheme);
+ }
+ }
+ else
+ {
+ // Standalone theme - just the theme CSS
+ var themePath = $"{FileProvider.StylesPath}{cssFilename}";
+ FileProvider.AddGetEndpoint(application, themePath, themeContent);
+ settings.CustomStylesheetPath = themePath;
+ }
+ });
}
- // TODO: add other extension methods from nswag?
-
- #region Private
-
- private static string GetSwaggerThemeCss(BaseTheme theme, SwaggerUiSettings settings)
+ private static void ConfigureThemeInternal(
+ IApplicationBuilder application,
+ SwaggerUiSettings settings,
+ BaseTheme theme)
{
- var sb = new StringBuilder();
-
- string baseCss = FileProvider.GetResourceText(theme.Common.FileName);
- baseCss = AdvancedOptions.Apply(baseCss, settings.AdditionalSettings, MimeTypes.Text.Css);
+ // 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);
- string themeCss = FileProvider.GetResourceText(theme.FileName, theme.GetType());
+ settings.CustomStylesheetPath = themePath;
- sb.Append(baseCss);
- sb.Append('\n');
- sb.Append(themeCss);
-
- return sb.ToString();
+ // Configure JS features if enabled
+ if (theme.LoadAdditionalJs && AdvancedOptions.AnyJsFeatureEnabled(settings.AdditionalSettings))
+ {
+ InjectJavascriptInternal(application, settings);
+ ConfigureThemeSwitcher(application, settings, theme);
+ }
}
- private static string GetSwaggerThemeJavascriptPath(IApplicationBuilder application, SwaggerUiSettings settings)
+ private static void InjectJavascriptInternal(
+ IApplicationBuilder application,
+ SwaggerUiSettings settings)
{
- 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);
+ var javascript = ThemeBuilderHelpers.GetConfiguredJavaScript(settings.AdditionalSettings);
+ ThemeBuilderHelpers.RegisterJavaScriptEndpoint(application, javascript);
- return FullPath;
+ settings.CustomJavaScriptPath = FileProvider.ScriptsPath + FileProvider.JsFilename;
}
- private static void AddCustomJavascript(IApplicationBuilder application, SwaggerUiSettings settings)
- => settings.CustomJavaScriptPath = GetSwaggerThemeJavascriptPath(application, settings);
-
- #endregion Private
+ private static void ConfigureThemeSwitcher(
+ IApplicationBuilder application,
+ SwaggerUiSettings settings,
+ BaseTheme theme)
+ {
+ 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));
+ });
+
+ // Mark current theme
+ headContent.AppendLine(ThemeSwitcher.CreateCurrentThemeMarkerScript(theme));
+
+ // Append to settings
+ settings.CustomHeadContent += headContent.ToString();
+ }
}
\ 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/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj b/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj
index d2ff579..99789ee 100644
--- a/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj
+++ b/src/NSwag.AspNetCore.Themes/NSwag.AspNetCore.Themes.csproj
@@ -5,7 +5,7 @@
- 2
+ 3
0
0
@@ -15,16 +15,16 @@
NSwag.AspNetCore.Themes
- Change theme to your Swagger API documentation in ASP.NET Core applications.
+ Modernize your ASP.NET Core Swagger UI with runtime theme switching, deep customization, and new functional UI features.
- extension;aspnetcore;swagger;swagger-ui;nswag;themes;ui;dark-theme;modern
+ swagger;swagger-ui;swaggerui;nswag;aspnetcore;webapi;openapi;extension;modern-ui;theme;dark-mode;dark-theme;runtime-theming;theme-switcher;theme-changer;custom-themes
true
-
+
diff --git a/src/NSwag.AspNetCore.Themes/package-readme.md b/src/NSwag.AspNetCore.Themes/package-readme.md
index 761e63e..2aff06d 100644
--- a/src/NSwag.AspNetCore.Themes/package-readme.md
+++ b/src/NSwag.AspNetCore.Themes/package-readme.md
@@ -6,18 +6,20 @@
# NSwag.AspNetCore.Themes
-### Beautiful, modern themes for NSwag Swagger UI 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)** • **[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)**
> ⚠️ **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
+## 🚀 Quick Start
```bash
dotnet add package NSwag.AspNetCore.Themes
@@ -26,20 +28,28 @@ dotnet add package NSwag.AspNetCore.Themes
## Quick Start
```csharp
+// Apply a theme
app.UseSwaggerUi(Theme.Dark);
+
+// Or enable runtime theme switcher!
+app.UseSwaggerUi(Theme.Dark, c => c.EnableThemeSwitcher());
```
> **Note**: The `UseSwaggerUi()` method is provided by NSwag.AspNetCore. This package adds convenient overloads to apply themes seamlessly.
-## Features
+## ✨ Features
+
+- 🔥 **[Theme Switcher](https://github.com/teociaps/SwaggerUI.Themes/wiki/Feature-Dynamic-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
-- **[Built-in Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Predefined-Themes)** - 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)** - Unlock new capabilities to enhance your Swagger UI
+- **[Advanced Features](https://github.com/teociaps/SwaggerUI.Themes/wiki/Advanced-Options)** - Enhance your documentation with powerful UI capabilities
-- **[Custom Themes](https://github.com/teociaps/SwaggerUI.Themes/wiki/Custom-Themes)** - Create your own themes with full control or build standalone themes
+- _...discover more in the [Wiki](https://github.com/teociaps/SwaggerUI.Themes/wiki/Features)!_
-## Usage
+## 📚 Basic Usage Examples
```csharp
using AspNetCore.Swagger.Themes;
@@ -49,14 +59,23 @@ using AspNetCore.Swagger.Themes;
// Use a built-in theme
app.UseSwaggerUi(Theme.Dark);
-// Or with advanced features
+// Enable theme switcher with auto-discovery
+app.UseSwaggerUi(Theme.Dark, c =>
+{
+ c.EnableThemeSwitcher();
+});
+
+// Or with all advanced features
app.UseSwaggerUi(Theme.Dark, c =>
{
c.EnableAllAdvancedOptions();
});
// Or use your custom theme from assembly
-app.UseSwaggerUi(Assembly.GetExecutingAssembly(), "my-theme.css");
+app.UseSwaggerUi(Assembly.GetExecutingAssembly(), "my-theme.css", c =>
+{
+ c.EnableThemeSwitcher(); // Works with custom themes too!
+});
...
```
diff --git a/src/NSwag.AspNetCore.Themes/readme.txt b/src/NSwag.AspNetCore.Themes/readme.txt
index 90c9275..cf8c182 100644
--- a/src/NSwag.AspNetCore.Themes/readme.txt
+++ b/src/NSwag.AspNetCore.Themes/readme.txt
@@ -1,13 +1,58 @@
- #-------------------#
-## RELEASE NOTES ##
- #-------------------#
++===================================================================================+
+| NSwag.AspNetCore.Themes • RELEASE NOTES - v3.0.0 |
++===================================================================================+
-v3.0.0
+BREAKING CHANGES
+-------------------------------------------------------------------------------------
+- API redesign: Style -> Theme (all classes renamed)
+- Classic themes removed (modern themes only)
+- .NET 6 & 7 support discontinued
-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
+See migration guide: https://github.com/teociaps/SwaggerUI.Themes/wiki/Migration-v3
+
+
+NEW FEATURES
+-------------------------------------------------------------------------------------
+- 🔥 Runtime Theme Switcher: Change themes on-the-fly without refreshing the page
+- Auto-Discovery: Custom themes automatically available in the theme switcher
+- Nested Folder Support: Organize your themes in subfolders
+- Standalone Themes: Create CSS-only themes with zero dependencies
+- Smart Filename Resolution: Support standard and standalone variants simultaneously
+- Minified Stylesheet Support: Optimize load times with minified CSS
+
+
+PLATFORM & DEPENDENCIES
+-------------------------------------------------------------------------------------
+- .NET 10 support added
+- NSwag.AspNetCore updated to v14.6.3
+
+
+IMPROVEMENTS
+-------------------------------------------------------------------------------------
+- Unified modern theme system with consistent defaults
+- Advanced features now available on all themes
+- Smaller package footprint (optimized assets)
+- Improved performance and clarity throughout
+
+
+QUICK START
+-------------------------------------------------------------------------------------
+Basic theme:
+ app.UseSwaggerUi(Theme.Dark);
+
+Enable theme switcher:
+ app.UseSwaggerUi(Theme.Dark, c => c.EnableThemeSwitcher());
+
+All features:
+ app.UseSwaggerUi(Theme.Dark, c => c.EnableAllAdvancedOptions());
+
+
+DOCUMENTATION
+-------------------------------------------------------------------------------------
+
+Full docs: https://github.com/teociaps/SwaggerUI.Themes/wiki
+Migration: https://github.com/teociaps/SwaggerUI.Themes/wiki/Migration-v3
+Repository: https://github.com/teociaps/SwaggerUI.Themes
Other Changes:
- .NET 10 support; .NET 6 & 7 discontinued
@@ -18,4 +63,4 @@ Other Changes:
- 50% smaller package footprint
- Enhanced performance and clarity
-For details: https://github.com/teociaps/SwaggerUI.Themes?tab=readme-ov-file#swaggeruithemes
\ No newline at end of file
+=====================================================================================
\ No newline at end of file
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/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/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 bb27dec..91edd37 100644
--- a/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeProviderTests.cs
+++ b/tests/AspNetCore.SwaggerUI.Themes.Tests/ThemeProviderTests.cs
@@ -5,6 +5,10 @@
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;
@@ -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);
@@ -173,33 +177,189 @@ public async Task AddGetEndpoint_ShouldReturnStyleContent_WhenWebApplication(Bas
(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]
- [ClassData(typeof(ThemeTestData))]
- public async Task AddGetEndpoint_ShouldReturnCssContent_WhenNotWebApplication(BaseTheme Theme)
+ [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 mockAppBuilder = new MockApplicationBuilder();
- var path = StylesPath + Theme.FileName;
- var content = GetResourceText(Theme.FileName, Theme.GetType());
+ var assembly = Assembly.GetExecutingAssembly();
// Act
- AddGetEndpoint(mockAppBuilder, path, content);
- var app = mockAppBuilder.Build();
+ 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();
+ }
- // Simulate a request
- var context = MockApplicationBuilder.CreateHttpContext(path);
- await app.Invoke(context);
+ [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();
- await context.Response.Body.FlushAsync();
+ // Act
+ GetResourceText(fileName, assembly, out var commonStyle, out var loadJs);
// Assert
- Assert.Equal(200, context.Response.StatusCode);
- Assert.Equal(MimeTypes.Text.Css, context.Response.ContentType);
+ 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();
- context.Response.Body.Seek(0, SeekOrigin.Begin);
+ // 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");
- using var reader = new StreamReader(context.Response.Body);
- var responseBody = await reader.ReadToEndAsync();
- Assert.Equal(content, responseBody);
+ // 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
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