fullstackhero/dotnet-starter-kit · error · NotFoundException
file not found
Error message
file not found
What it means
NotFoundException('file not found') thrown by RestoreFileCommandHandler when no FileAsset row with cmd.FileAssetId exists — the query uses IgnoreQueryFilters() so even soft-deleted rows would be found; only a truly absent (or wrong-tenant-filtered) id yields null. Restore is a soft-delete reversal, and a missing target is surfaced as a 404.
Solutions
- Verify the FileAssetId exists (list/get the file first) and belongs to the current tenant before restoring.
- Fix the client to use the id returned by the files API rather than one captured from another context/environment.
- If the file was hard-deleted, it cannot be restored — re-upload the file instead.
- Ensure restore requests are always made within the tenant that owns the file.
Example fix
// before
await restoreFile({ fileAssetId: staleIdFromOtherEnv }); // 404 file not found
// after
const file = await getFileAsset(fileAssetId); // confirms existence in this tenant
if (file?.isDeleted) await restoreFile({ fileAssetId: file.id }); Defensive patterns
Strategy: try-catch
Validate before calling
const file = await getFileAsset(fileAssetId); // 404 here means nothing to restore if (!file?.isDeleted) return; // idempotent — nothing to do
Type guard
function isRestorable(f) { return f != null && f.isDeleted === true; } Try / catch
try { await restoreFile({ fileAssetId }); } catch (e) { if (e.status === 404) { refreshList(); showToast("File no longer exists"); } else throw e; } Prevention
- Refresh file lists after mutations so ids are never stale.
- Scope all file ids to the current tenant; never reuse ids across environments.
- Treat restore as idempotent on the client: only call it when the file is known to be soft-deleted.
When it happens
Trigger: Calling restore with a file id that was never created, an id from another tenant (tenant query filter still applies), a truncated/mistyped GUID, or referencing a file permanently deleted (hard delete) so the row no longer exists at all.
Common situations: Client restoring from a stale list after the record was purged; copying ids across environments; cross-tenant id confusion in multi-tenant testing; users hitting restore twice after a hard-delete cleanup job removed the row.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/7b73e921c710487e.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Files/Modules.Files/Features/v1/RestoreFile/RestoreFileCommandHandler.cs:21
using FSH.Modules.Files.Data;
using Mediator;
using Microsoft.EntityFrameworkCore;
namespace FSH.Modules.Files.Features.v1.RestoreFile;
public sealed class RestoreFileCommandHandler(FilesDbContext db)
: ICommandHandler<RestoreFileCommand, Unit>
{
public async ValueTask<Unit> Handle(RestoreFileCommand cmd, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(cmd);
// IgnoreQueryFilters because the SoftDelete filter would otherwise hide the row.
var f = await db.FileAssets
.IgnoreQueryFilters()
.FirstOrDefaultAsync(x => x.Id == cmd.FileAssetId, cancellationToken)
.ConfigureAwait(false)
?? throw new NotFoundException("file not found");
if (!f.IsDeleted)
{
return Unit.Value; // idempotent — already live
}
f.Restore();
await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
return Unit.Value;
}
}
View on GitHub (pinned to 3f2959e683)