fullstackhero/dotnet-starter-kit · error · NotFoundException
Image not found on product .
Error message
Image {command.ImageId} not found on product {command.ProductId}. What it means
RemoveProductImageCommandHandler verifies the requested ImageId exists in the product's Images collection before calling the domain; if not, it throws NotFoundException (HTTP 404). The comment in source notes the domain would otherwise throw an untranslated InvalidOperationException, so this check maps the miss to a proper 404.
Solutions
- Re-fetch the product's images and confirm the image id is still present before calling remove.
- Treat 404 'image not found' as idempotent success in the UI — the goal state (image gone) already holds.
- Refresh the image gallery after every removal so stale ids aren't reused.
- Verify the image id belongs to the same product you are targeting.
Example fix
// before
await api.removeProductImage(productId, imageId); // may 404
// after
try {
await api.removeProductImage(productId, imageId);
} catch (e) {
if (e.status === 404) return; // already removed — idempotent
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
const product = await api.getProduct(productId); if (!product.images.some(i => i.id === imageId)) return; // already gone
Try / catch
try {
await api.removeProductImage(productId, imageId);
} catch (e) {
if (e.status === 404) return; // idempotent: image already removed
throw e;
} Prevention
- Treat image removal as idempotent — 404 means goal already achieved
- Update the gallery optimistically and refetch to reconcile
- Avoid queuing duplicate remove calls for the same image id
When it happens
Trigger: Removing an image id that is not part of the given product's image collection — already-removed image, image id belonging to a different product, or a client-side stale image list.
Common situations: Two operators removing the same image concurrently (second gets 404); UI cache of images not refreshed after a prior removal; copying image ids between products.
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/8ef3742e10c24b4c.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Catalog/Modules.Catalog/Features/v1/Products/RemoveProductImage/RemoveProductImageCommandHandler.cs:24
namespace FSH.Modules.Catalog.Features.v1.Products.RemoveProductImage;
public sealed class RemoveProductImageCommandHandler(CatalogDbContext dbContext)
: ICommandHandler<RemoveProductImageCommand, Unit>
{
public async ValueTask<Unit> Handle(RemoveProductImageCommand command, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(command);
var product = await dbContext.Products
.FirstOrDefaultAsync(p => p.Id == command.ProductId, cancellationToken)
.ConfigureAwait(false)
?? throw new NotFoundException($"Product {command.ProductId} not found.");
// Domain throws InvalidOperationException for unknown imageId; translate to 404.
if (!product.Images.Any(i => i.Id == command.ImageId))
{
throw new NotFoundException($"Image {command.ImageId} not found on product {command.ProductId}.");
}
product.RemoveImage(command.ImageId);
await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
return Unit.Value;
}
}
View on GitHub (pinned to 3f2959e683)