fullstackhero/dotnet-starter-kit · error · NotFoundException

Not Found.

Error message

{typeof(AppTenantInfo).Name} {id} Not Found.

What it means

GetTenantInfoAsync loads the tenant from ITenantStore (Finbuckle) and throws NotFoundException formatted as '{AppTenantInfo} {id} Not Found.' when the store returns null. It is the shared loader for Activate/Deactivate/Update flows, so any tenant-scoped operation on a nonexistent id surfaces this error.

Solutions

  1. Verify the tenant id exists (list tenants or check the Tenants table) before operating on it.
  2. Refresh stale references in the client after tenant deletion.
  3. Confirm the API connects to the intended database and that DbMigrator ran (--apply --seed) so seed tenants exist.
  4. Handle NotFoundException in callers to return a clean 404 rather than a 500.

Example fix

// before
await tenantService.ActivateAsync(id, ct); // id may be stale

// after
var tenants = await tenantService.GetAllAsync(ct);
if (!tenants.Any(t => t.Id.Equals(id, StringComparison.OrdinalIgnoreCase)))
{
    // tenant gone — refresh list instead of activating
    return;
}
await tenantService.ActivateAsync(id, ct);
Defensive patterns

Strategy: try-catch

Validate before calling

var exists = (await tenantService.GetAllAsync(ct))
    .Any(t => t.Id.Equals(id, StringComparison.OrdinalIgnoreCase));
if (!exists) throw new KeyNotFoundException($"Tenant {id} does not exist in this environment.");

Type guard

AppTenantInfo? FindTenant(IEnumerable<AppTenantInfo> tenants, string id) =>
    tenants.FirstOrDefault(t => t.Id.Equals(id, StringComparison.OrdinalIgnoreCase));

Try / catch

try
{
    await tenantService.ActivateAsync(id, ct);
}
catch (NotFoundException)
{
    logger.LogWarning("Tenant {TenantId} not found — refreshing local cache", id);
    await RefreshTenantListAsync(); // drop stale id
}

Prevention

When it happens

Trigger: Calling ActivateAsync/DeactivateAsync/UpdateAsync with a tenant id that doesn't exist; stale id after a tenant was deleted; id casing/format mismatch with the store key; querying the wrong database or environment; the distributed-cache store was flushed and the underlying EF row is missing.

Common situations: Frontend holding a cached tenant id from before a delete; automation scripts with typo'd ids; pointing DbMigrator/API at a fresh database that hasn't been seeded; tenants created in another environment (staging vs production).

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/f5f8fbd3078b68f6. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Multitenancy/Modules.Multitenancy/Services/TenantService.cs:250

        var previous = tenant.ValidUpto;
        tenant.ValidUpto = normalized;

        await _tenantStore.UpdateAsync(tenant).ConfigureAwait(false);
        await RefreshTenantCacheAsync(tenant).ConfigureAwait(false);

        if (_logger.IsEnabled(LogLevel.Information))
        {
            _logger.LogInformation(
                "[Multitenancy] operator adjusted tenant {TenantId} validity from {Previous:o} to {ValidUpto:o}",
                id, previous, normalized);
        }

        return normalized;
    }

    private async Task<AppTenantInfo> GetTenantInfoAsync(string id, CancellationToken cancellationToken = default) =>
        await _tenantStore.GetAsync(id).ConfigureAwait(false)
            ?? throw new NotFoundException($"{typeof(AppTenantInfo).Name} {id} Not Found.");

    // Finbuckle resolves via the distributed-cache store first (60-min TTL) while the injected store only
    // writes EF, so push the new state into the cache store too — otherwise flips lag until cache expiry.
    private async Task RefreshTenantCacheAsync(AppTenantInfo tenant)
    {
        var cacheStore = _serviceProvider
            .GetServices<IMultiTenantStore<AppTenantInfo>>()
            .FirstOrDefault(s => s.GetType() == typeof(DistributedCacheStore<AppTenantInfo>));
        if (cacheStore is not null)
        {
            await cacheStore.UpdateAsync(tenant).ConfigureAwait(false);
        }
    }
}

View on GitHub (pinned to 3f2959e683)