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

  1. Re-fetch the product to confirm the id, then re-read its current image ids before removing.
  2. Treat 404 on product delete flows as idempotent success where appropriate.
  3. Ensure the image id and product id come from the same product object in the client state.
  4. 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

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)