fullstackhero/dotnet-starter-kit · error · NotFoundException
Product not found.
Error message
Product {command.ProductId} not found. What it means
RemoveProductImageCommandHandler first loads the owning product and throws NotFoundException if command.ProductId does not match any product row (HTTP 404). The image lookup itself is handled by error [77]; this one fires before any image check when the parent product cannot be found.
Solutions
- Re-fetch the product to confirm the id, then re-read its current image ids before removing.
- Treat 404 on product delete flows as idempotent success where appropriate.
- Ensure the image id and product id come from the same product object in the client state.
- Check tenant context if the product exists but is filtered out.
Example fix
// before
await api.removeProductImage(staleProductId, imageId);
// after
const product = await api.getProduct(staleProductId).catch(() => null);
if (!product) { refreshList(); return; }
await api.removeProductImage(product.id, imageId); Defensive patterns
Strategy: try-catch
Validate before calling
const product = await api.getProduct(productId).catch(() => null);
if (!product) throw new Error(`Product ${productId} does not exist`);
if (!product.images.some(i => i.id === imageId)) throw new Error('Image not on this product'); Try / catch
try {
await api.removeProductImage(productId, imageId);
} catch (e) {
if (e.status === 404) { refreshProduct(); return; }
throw e;
} Prevention
- Keep image id and product id sourced from the same fetched product object
- Refresh product detail after every mutation
- Re-fetch before mutating data loaded more than a few seconds ago
When it happens
Trigger: DELETE (remove-image) with a ProductId that doesn't exist, was soft-deleted, belongs to another tenant, or an image payload carrying the wrong/zero product id.
Common situations: Client holds product ids from a stale list after the product was deleted; wrong entity's image id submitted (image id from product A with product id of product B).
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/d3c0766686b0e401.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Catalog/Modules.Catalog/Features/v1/Products/RemoveProductImage/RemoveProductImageCommandHandler.cs:19
using FSH.Framework.Core.Exceptions;
using FSH.Modules.Catalog.Contracts.v1.Products.RemoveProductImage;
using FSH.Modules.Catalog.Data;
using Mediator;
using Microsoft.EntityFrameworkCore;
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)