WebSpark.Bootswatch provides Bootswatch themes for ASP.NET Core applications. It includes custom themes and styles that can be easily integrated with ASP.NET Core MVC or Razor Pages applications. Supports .NET 8.0, 9.0, and 10.0. ⚠️ IMPORTANT: This package requires WebSpark.HttpClientUtility to be installed and registered separately. SETUP: 1. Install: dotnet add package WebSpark.HttpClientUtility 2. Register: builder.Services.AddHttpClientUtility(); (BEFORE AddBootswatchThemeSwitcher) 3. Configure appsettings.json with HttpRequestResultPollyOptions section See package README for complete setup guide.
$ dotnet add package WebSpark.BootswatchA .NET Razor Class Library that provides seamless integration of Bootswatch themes into ASP.NET Core applications. Built on Bootstrap 5, this library offers modern, responsive theming with dynamic theme switching, light/dark mode support, and comprehensive caching mechanisms.
Multi-Framework Support: Targets .NET 8.0 (LTS), .NET 9.0 (STS), and .NET 10.0 for maximum compatibility.
Latest Release: v1.34.0 - Demo site UI improvements for better theme visibility
StyleCache service<bootswatch-theme-switcher /> for easy UI integrationWebSpark.Bootswatch requires WebSpark.HttpClientUtility to be installed AND registered separately.
Before starting, ensure you complete ALL of these steps:
WebSpark.Bootswatch AND WebSpark.HttpClientUtilityProgram.csAddHttpClientUtility() BEFORE AddBootswatchThemeSwitcher()appsettings.jsonUseBootswatchAll() BEFORE UseStaticFiles() in middleware pipelineMissing any of these steps will cause runtime errors!
// ❌ WRONG - Missing HttpClientUtility registration
builder.Services.AddBootswatchThemeSwitcher();
// ✅ CORRECT - HttpClientUtility registered first
using WebSpark.Bootswatch;
using WebSpark.HttpClientUtility;
builder.Services.AddHttpClientUtility(); // Must be FIRST
builder.Services.AddBootswatchThemeSwitcher(); // Then this
The library supports multiple .NET versions:
| Framework | Status | Support Level |
|---|---|---|
| .NET 8.0 | ✅ Supported | LTS (Long Term Support) |
| .NET 9.0 | ✅ Supported | STS (Standard Term Support) |
| .NET 10.0 | ✅ Supported | Current Release |
Your project can target any of these frameworks and will receive the appropriate version of the library.
<PackageReference Include="WebSpark.Bootswatch" Version="1.34.0" />
<PackageReference Include="WebSpark.HttpClientUtility" Version="2.1.1" />
Add to your appsettings.json for dynamic theme fetching:
{
"CsvOutputFolder": "c:\\temp\\WebSpark\\CsvOutput",
"HttpRequestResultPollyOptions": {
"MaxRetryAttempts": 3,
"RetryDelaySeconds": 1,
"CircuitBreakerThreshold": 3,
"CircuitBreakerDurationSeconds": 10
}
}
Install-Package WebSpark.Bootswatch
Install-Package WebSpark.HttpClientUtility
dotnet add package WebSpark.Bootswatch
dotnet add package WebSpark.HttpClientUtility
<PackageReference Include="WebSpark.Bootswatch" Version="1.34.0" />
<PackageReference Include="WebSpark.HttpClientUtility" Version="2.1.1" />
The NuGet package automatically selects the correct assembly based on your project's target framework.
# Install WebSpark.Bootswatch
dotnet add package WebSpark.Bootswatch
# Install REQUIRED dependency (NOT automatically installed)
dotnet add package WebSpark.HttpClientUtility
Verify Installation:
Your .csproj should now include BOTH packages:
<PackageReference Include="WebSpark.Bootswatch" Version="1.34.0" />
<PackageReference Include="WebSpark.HttpClientUtility" Version="2.1.1" />
Create or update appsettings.json:
{
"CsvOutputFolder": "c:\\temp\\WebSpark\\CsvOutput",
"HttpRequestResultPollyOptions": {
"MaxRetryAttempts": 3,
"RetryDelaySeconds": 1,
"CircuitBreakerThreshold": 3,
"CircuitBreakerDurationSeconds": 10
},
"BootswatchOptions": {
"DefaultTheme": "yeti",
"EnableCaching": true,
"CacheDurationMinutes": 60
}
}
Add using statements at the top:
using WebSpark.Bootswatch;
using WebSpark.HttpClientUtility;
Register services in the correct order:
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddRazorPages();
builder.Services.AddHttpContextAccessor();
// ⚠️ CRITICAL: Register HttpClientUtility FIRST
builder.Services.AddHttpClientUtility();
// Then register Bootswatch theme switcher
builder.Services.AddBootswatchThemeSwitcher();
var app = builder.Build();
// Configure middleware pipeline
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
// ⚠️ CRITICAL: UseBootswatchAll() must come BEFORE UseStaticFiles()
app.UseBootswatchAll();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, WebSpark.Bootswatch
Add required using statements and inject StyleCache:
@using WebSpark.Bootswatch.Services
@using WebSpark.Bootswatch.Helpers
@inject StyleCache StyleCache
Update the HTML structure:
<!DOCTYPE html>
<html lang="en" data-bs-theme="@(BootswatchThemeHelper.GetCurrentColorMode(Context))">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"]</title>
@{
var themeName = BootswatchThemeHelper.GetCurrentThemeName(Context);
var themeUrl = BootswatchThemeHelper.GetThemeUrl(StyleCache, themeName);
}
<link id="bootswatch-theme-stylesheet" rel="stylesheet" href="@themeUrl" />
<script src="/_content/WebSpark.Bootswatch/js/bootswatch-theme-switcher.js"></script>
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" />
</head>
<body>
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand" href="/">My App</a>
<ul class="navbar-nav ms-auto">
<!-- Your navigation items -->
<!-- Theme Switcher Tag Helper -->
<bootswatch-theme-switcher />
</ul>
</div>
</nav>
<main>
@RenderBody()
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
Build and run your application:
dotnet build
dotnet run
Expected Results:
Full Error Message:
System.AggregateException: Some services are not able to be constructed
(Error while validating the service descriptor 'ServiceType: WebSpark.Bootswatch.Model.IStyleProvider
Lifetime: Scoped ImplementationType: WebSpark.Bootswatch.Provider.BootswatchStyleProvider':
Unable to resolve service for type 'WebSpark.HttpClientUtility.RequestResult.IHttpRequestResultService'
Cause: WebSpark.HttpClientUtility services are not registered.
Solution:
dotnet list package | findstr HttpClientUtilityusing WebSpark.HttpClientUtility;using WebSpark.Bootswatch;
using WebSpark.HttpClientUtility;
builder.Services.AddHttpClientUtility(); // ✅ Must be FIRST
builder.Services.AddBootswatchThemeSwitcher(); // ✅ Then this
Cause: Middleware is in wrong order.
Solution:
Ensure UseBootswatchAll() comes BEFORE UseStaticFiles():
// ✅ CORRECT ORDER
app.UseBootswatchAll(); // First
app.UseStaticFiles(); // Then this
// ❌ WRONG ORDER (will fail)
app.UseStaticFiles();
app.UseBootswatchAll();
Cause: Missing or incorrect appsettings.json configuration.
Solution:
Ensure ALL required sections are present in appsettings.json:
{
"CsvOutputFolder": "c:\\temp\\WebSpark\\CsvOutput",
"HttpRequestResultPollyOptions": {
"MaxRetryAttempts": 3,
"RetryDelaySeconds": 1,
"CircuitBreakerThreshold": 3,
"CircuitBreakerDurationSeconds": 10
}
}
Cause: Tag helper not registered in _ViewImports.cshtml.
Solution:
Add to _ViewImports.cshtml:
@addTagHelper *, WebSpark.Bootswatch
public class HomeController : Controller
{
private readonly StyleCache _styleCache;
public HomeController(StyleCache styleCache)
{
_styleCache = styleCache;
}
public IActionResult Index()
{
// Get all available themes
var allThemes = _styleCache.GetAllStyles();
// Get specific theme
var defaultTheme = _styleCache.GetStyle("default");
return View(allThemes);
}
}
// Get current theme information
var currentTheme = BootswatchThemeHelper.GetCurrentThemeName(Context);
var colorMode = BootswatchThemeHelper.GetCurrentColorMode(Context);
var themeUrl = BootswatchThemeHelper.GetThemeUrl(StyleCache, currentTheme);
// Generate theme switcher HTML
var switcherHtml = BootswatchThemeHelper.GetThemeSwitcherHtml(StyleCache, Context);
// Add custom themes to your StyleCache
public void ConfigureServices(IServiceCollection services)
{
services.AddBootswatchThemeSwitcher();
services.Configure<BootswatchOptions>(options =>
{
options.CustomThemes.Add(new StyleModel
{
Name = "custom-theme",
Description = "My Custom Theme",
CssPath = "/css/custom-theme.css"
});
});
}
Explore the complete implementation in our demo project:
git clone https://github.com/MarkHazleton/WebSpark.Bootswatch.git
cd WebSpark.Bootswatch
dotnet run --project WebSpark.Bootswatch.Demo
The demo showcases:
The library includes comprehensive tests that run on all supported frameworks:
# Test all frameworks
dotnet test
# Test specific framework
dotnet test --framework net8.0
dotnet test --framework net9.0
dotnet test --framework net10.0
# Use PowerShell script for detailed output
.\run-multi-framework-tests.ps1
Our CI/CD pipeline runs separate test jobs for each framework, ensuring compatibility across all supported .NET versions.
| Component | Purpose | Lifecycle |
|---|---|---|
StyleCache | Theme data caching | Singleton |
BootswatchStyleProvider | Theme management | Scoped |
BootswatchThemeHelper | Static utilities | Static |
BootswatchThemeSwitcherTagHelper | UI component | Transient |
The correct middleware order is crucial:
app.UseBootswatchStaticFiles(); // 1. Bootswatch static files
app.UseStaticFiles(); // 2. Application static files
app.UseRouting(); // 3. Routing
The NuGet package contains separate assemblies for each target framework:
WebSpark.Bootswatch.1.31.0.nupkg
├── lib/
│ ├── net8.0/
│ │ └── WebSpark.Bootswatch.dll
│ ├── net9.0/
│ │ └── WebSpark.Bootswatch.dll
│ └── net10.0/
│ └── WebSpark.Bootswatch.dll
Each assembly is compiled with framework-specific optimizations and references the appropriate version of dependencies.
// Full configuration
app.UseBootswatchAll();
// Or individual components
app.UseBootswatchStaticFiles();
app.UseBootswatchThemeRoutes();
services.AddBootswatchThemeSwitcher(options =>
{
options.DefaultTheme = "bootstrap";
options.EnableCaching = true;
options.CacheDurationMinutes = 60;
});
StyleCache singletonEach target framework receives optimized builds:
For detailed troubleshooting, see the Common Errors & Solutions section above.
| Issue | Solution |
|---|---|
| Service resolution error | Register AddHttpClientUtility() before AddBootswatchThemeSwitcher() |
| Themes not loading | Check middleware order: UseBootswatchAll() before UseStaticFiles() |
| Theme switcher not visible | Ensure @addTagHelper *, WebSpark.Bootswatch in _ViewImports.cshtml |
| Missing dependencies | Install WebSpark.HttpClientUtility package |
| Configuration errors | Add required appsettings.json configuration |
| Wrong framework version | NuGet automatically selects correct version based on your target framework |
Enable detailed logging:
builder.Services.AddLogging(config =>
{
config.AddConsole();
config.SetMinimumLevel(LogLevel.Debug);
});
| Browser | Version | Status |
|---|---|---|
| Chrome | 90+ | ✅ Fully Supported |
| Firefox | 88+ | ✅ Fully Supported |
| Safari | 14+ | ✅ Fully Supported |
| Edge | 90+ | ✅ Fully Supported |
| IE | 11 | ❌ Not Supported |
We welcome contributions! Please see CONTRIBUTING.md for guidelines.
# Clone repository
git clone https://github.com/MarkHazleton/WebSpark.Bootswatch.git
cd WebSpark.Bootswatch
# Restore dependencies
dotnet restore
# Build solution (builds for all target frameworks)
dotnet build
# Run tests (tests all frameworks)
dotnet test
# Run demo
dotnet run --project WebSpark.Bootswatch.Demo
When contributing, ensure your changes work across all target frameworks:
# Run comprehensive multi-framework tests
.\run-multi-framework-tests.ps1 -Configuration Release
This project is licensed under the MIT License - see the LICENSE file for details.
See NOTICE.txt for complete attribution.