{"record":{"id":"1831a03b8c8527e3","repo":"fullstackhero/dotnet-starter-kit","slug":"file-not-found","errorCode":null,"errorMessage":"file not found","messagePattern":"file not found","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Modules/Files/Modules.Files/Features/v1/ChangeVisibility/ChangeFileVisibilityCommandHandler.cs","lineNumber":38,"sourceCode":"    IStorageService storage)\n    : ICommandHandler<ChangeFileVisibilityCommand, FileAssetDto>\n{\n    public async ValueTask<FileAssetDto> Handle(ChangeFileVisibilityCommand cmd, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(cmd);\n\n        if (cmd.Visibility is not (Visibility.Public or Visibility.Private))\n        {\n            throw new CustomException(\n                $\"Unknown visibility value '{cmd.Visibility}'.\",\n                errors: null,\n                System.Net.HttpStatusCode.BadRequest);\n        }\n\n        var f = await db.FileAssets\n            .FirstOrDefaultAsync(x => x.Id == cmd.FileAssetId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException(\"file not found\");\n\n        var userId = currentUser.GetUserId().ToString();\n        var policy = policies.Resolve(f.OwnerType)\n            ?? throw new ForbiddenException(\"no policy\");\n        var ctx = new FileAccessContext(f.Id, f.OwnerType, f.OwnerId, f.CreatedByUserId, (int)f.Visibility);\n        if (!await policy.CanChangeVisibilityAsync(ctx, userId, cancellationToken).ConfigureAwait(false))\n        {\n            throw new ForbiddenException(\"not allowed to change this file's visibility\");\n        }\n\n        f.ChangeVisibility(cmd.Visibility);\n        await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);\n\n        var publicUrl = f.Visibility == Visibility.Public\n            ? storage.BuildPublicUrl(f.StorageKey)\n            : null;\n        return FileAssetMapper.ToDto(f, publicUrl);\n    }","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Files/Modules.Files/Features/v1/ChangeVisibility/ChangeFileVisibilityCommandHandler.cs#L20-L56","documentation":"ChangeFileVisibilityCommandHandler looks up the FileAsset by cmd.FileAssetId via FirstOrDefaultAsync and throws NotFoundException when no row matches. This is a domain-level 'record not found' signal: the id supplied does not correspond to any existing file asset in the tenant-scoped query (soft-deleted or other-tenant rows are filtered out by BaseDbContext query filters).","triggerScenarios":"Calling the change-visibility endpoint/command with a FileAssetId that (a) never existed, (b) was already soft-deleted (IsDeleted filtered out), (c) belongs to a different tenant (tenant query filter), or (d) has a mistyped/stale GUID from a cached client reference.","commonSituations":"Frontend kept a stale file id after the file was deleted in another tab; test fixtures referencing seeded data from a different tenant; copying an id from logs of another environment; id truncation or casing mistakes when hand-crafting requests.","solutions":["Verify the FileAssetId exists and is not soft-deleted: query FileAssets ignoring filters, e.g. db.FileAssets.IgnoreQueryFilters().AnyAsync(x => x.Id == id).","Check the request is sent under the correct tenant (tenant header/route) so the global query filter does not hide the row.","Refresh the client-side file list; remove stale cached ids after delete operations.","If the file should exist, confirm the DbMigrator/seed ran and you are pointed at the expected database."],"exampleFix":"// before\nawait client.ChangeVisibilityAsync(new ChangeFileVisibilityCommand { FileAssetId = staleId, Visibility = Visibility.Public });\n// after\nvar exists = await db.FileAssets.AnyAsync(x => x.Id == id);\nif (!exists) { /* re-fetch fresh id from list endpoint or surface 404 to user */ }\nawait client.ChangeVisibilityAsync(new ChangeFileVisibilityCommand { FileAssetId = freshId, Visibility = Visibility.Public });","handlingStrategy":"validation","validationCode":"var exists = await db.FileAssets.AnyAsync(x => x.Id == cmd.FileAssetId, ct);\nif (!exists) throw new NotFoundException($\"FileAsset {cmd.FileAssetId} does not exist in the current tenant.\");","typeGuard":"bool IsValidFileAssetId(Guid id) => id != Guid.Empty;","tryCatchPattern":"catch (NotFoundException e) when (e.Message == \"file not found\") { return Results.NotFound(new { fileAssetId = cmd.FileAssetId }); }","preventionTips":["Always take FileAssetId from the server's create/init response, never fabricate it.","Invalidate client caches after delete operations.","Remember tenant + soft-delete filters make valid-looking ids 'not found'.","Keep ids as GUIDs end to end to avoid formatting loss."],"tags":["not-found","files","cqrs","tenancy"],"backgroundTag":"entity-not-found","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}