fullstackhero/dotnet-starter-kit · error · NotFoundException
Theme for tenant not found
Error message
Theme for tenant {tenantId} not found What it means
TenantThemeService.SetAsDefaultThemeAsync looks up the TenantTheme row for the given tenantId and throws NotFoundException when no theme record exists for that tenant. Themes must be created before one can be promoted to default; the service refuses to implicitly create one. The exception maps to an HTTP 404 for the caller.
Solutions
- Run the theme seeding/migration (dotnet run --project src/Host/FSH.Starter.DbMigrator -- apply --seed) so the tenant gets a TenantTheme row.
- Create a theme for the tenant first (CreateTheme endpoint/handler), then call SetAsDefaultThemeAsync.
- Verify the tenantId is correct and matches the tenant that actually owns the theme row (watch out for cross-environment tenant IDs).
- Check the TenantThemes table for the row: SELECT * FROM "TenantThemes" WHERE "TenantId" = '<tenantId>'.
Example fix
// before
await tenantThemeService.SetAsDefaultThemeAsync(tenantId, ct); // throws if no theme exists
// after
var theme = await tenantThemeService.GetThemeAsync(tenantId, ct);
if (theme is null)
{
await tenantThemeService.CreateDefaultThemeAsync(tenantId, ct); // ensure a theme exists
}
await tenantThemeService.SetAsDefaultThemeAsync(tenantId, ct); Defensive patterns
Strategy: try-catch
Validate before calling
var theme = await tenantThemeService.GetThemeAsync(tenantId, ct);
if (theme is null) throw new InvalidOperationException($"No theme exists for tenant {tenantId}; create one before setting default."); Type guard
bool HasTheme(ThemeDto? theme) => theme is not null && !string.IsNullOrWhiteSpace(theme.TenantId);
Try / catch
try
{
await tenantThemeService.SetAsDefaultThemeAsync(tenantId, ct);
}
catch (NotFoundException)
{
logger.LogWarning("No theme found for tenant {TenantId}; creating one first.", tenantId);
await tenantThemeService.CreateDefaultThemeAsync(tenantId, ct);
await tenantThemeService.SetAsDefaultThemeAsync(tenantId, ct);
} Prevention
- Always create/seed a TenantTheme row as part of tenant provisioning.
- Fetch the theme before promoting it to default.
- Validate tenantId against the environment you are targeting.
- Catch NotFoundException at the endpoint layer and map it to a helpful 404 message.
When it happens
Trigger: Calling SetAsDefaultThemeAsync(tenantId) (or the SetDefaultTheme endpoint) when the tenant has no row in the TenantThemes table — i.e. no theme was ever created/seeded for that tenant, or the theme row was deleted.
Common situations: Running against a fresh database where the theme seeder did not run; calling the set-default endpoint before the tenant customized a theme; using a tenantId from another environment (staging vs prod) where the theme record is missing; a manual cleanup deleted the TenantTheme row.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/8e419e867e2bc8f4.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Multitenancy/Modules.Multitenancy/Services/TenantThemeService.cs:243
// Clear existing default
var existingDefault = await _dbContext.TenantThemes
.FirstOrDefaultAsync(t => t.IsDefault, ct)
.ConfigureAwait(false);
if (existingDefault is not null)
{
existingDefault.IsDefault = false;
}
// Set new default
var entity = await _dbContext.TenantThemes
.FirstOrDefaultAsync(t => t.TenantId == tenantId, ct)
.ConfigureAwait(false);
if (entity is null)
{
throw new NotFoundException($"Theme for tenant {tenantId} not found");
}
entity.IsDefault = true;
await _dbContext.SaveChangesAsync(ct).ConfigureAwait(false);
// Invalidate default theme cache
await _cache.RemoveAsync(CacheKeys.DefaultTheme, ct).ConfigureAwait(false);
if (_logger.IsEnabled(LogLevel.Information))
{
_logger.LogInformation("Set theme for tenant {TenantId} as default", tenantId);
}
}
public async Task InvalidateCacheAsync(string tenantId, CancellationToken ct = default)
{
// Purge both the tenant-specific entry and anything tagged for this tenant.
await _cache.RemoveAsync(CacheKeys.TenantTheme(tenantId), ct).ConfigureAwait(false);View on GitHub (pinned to 3f2959e683)