fullstackhero/dotnet-starter-kit · error · NotFoundException

file not found

Error message

file not found

What it means

DeleteFileCommandHandler queries FileAssets by cmd.FileAssetId and throws NotFoundException when the row is absent. Because the DbContext applies global query filters (tenant isolation + soft delete), 'not found' also covers rows that exist but are invisible to the current tenant/context.

Solutions

  1. Treat repeated 'file not found' on delete as success (idempotent delete) in the client, or pre-check existence.
  2. Verify the tenant context of the request matches the file's tenant.
  3. Check for soft-deleted rows with IgnoreQueryFilters to confirm whether the id ever existed.
  4. Refresh client caches after any delete so stale ids are not replayed.

Example fix

// before
await client.DeleteFileAsync(id);
// after (idempotent client handling)
try { await client.DeleteFileAsync(id); }
catch (ApiException e) when (e.StatusCode == 404) { /* already deleted — ignore */ }
Defensive patterns

Strategy: try-catch

Validate before calling

var exists = await db.FileAssets.AnyAsync(x => x.Id == cmd.FileAssetId, ct);
if (!exists) return; // nothing to delete — treat as idempotent success

Type guard

bool IsValidFileAssetId(Guid id) => id != Guid.Empty;

Try / catch

catch (NotFoundException) { /* idempotent delete: already gone */ return Results.NoContent(); }

Prevention

When it happens

Trigger: Delete endpoint/command invoked with a FileAssetId that does not exist, was already soft-deleted, lives in another tenant, or is a fabricated/garbage GUID from the client.

Common situations: Double-click delete: the second call hits the now soft-deleted row; client retries after network timeout though the first delete succeeded; deleting with a token from the wrong tenant; stale list data in the UI.

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

Appendix: source

Thrown at src/Modules/Files/Modules.Files/Features/v1/DeleteFile/DeleteFileCommandHandler.cs:25

using Mediator;
using Microsoft.EntityFrameworkCore;

namespace FSH.Modules.Files.Features.v1.DeleteFile;

public sealed class DeleteFileCommandHandler(
    FilesDbContext db,
    FileAccessPolicyRegistry policies,
    ICurrentUser currentUser)
    : ICommandHandler<DeleteFileCommand, Unit>
{
    public async ValueTask<Unit> Handle(DeleteFileCommand cmd, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(cmd);

        var f = await db.FileAssets
            .FirstOrDefaultAsync(x => x.Id == cmd.FileAssetId, cancellationToken)
            .ConfigureAwait(false)
            ?? throw new NotFoundException("file not found");

        var userId = currentUser.GetUserId().ToString();
        var policy = policies.Resolve(f.OwnerType)
            ?? throw new ForbiddenException("no policy");
        var ctx = new FileAccessContext(f.Id, f.OwnerType, f.OwnerId, f.CreatedByUserId, (int)f.Visibility);
        if (!await policy.CanDeleteAsync(ctx, userId, cancellationToken).ConfigureAwait(false))
        {
            throw new ForbiddenException("not allowed to delete this file");
        }

        // Soft-delete: AuditableEntitySaveChangesInterceptor sets IsDeleted/DeletedOnUtc/DeletedBy on
        // Remove() for ISoftDeletable; byte purge runs later via PurgeDeletedFilesJob post-retention.
        db.FileAssets.Remove(f);
        await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
        return Unit.Value;
    }
}

View on GitHub (pinned to 3f2959e683)