{"record":{"id":"c2c0be2908b3f1ab","repo":"fullstackhero/dotnet-starter-kit","slug":"image-command-imageid-not-found-on-product-command-productid-c2c0be","errorCode":null,"errorMessage":"Image {command.ImageId} not found on product {command.ProductId}.","messagePattern":"Image (.+?) not found on product (.+?)\\.","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Modules/Catalog/Modules.Catalog/Features/v1/Products/SetProductThumbnail/SetProductThumbnailCommandHandler.cs","lineNumber":25,"sourceCode":"namespace FSH.Modules.Catalog.Features.v1.Products.SetProductThumbnail;\n\npublic sealed class SetProductThumbnailCommandHandler(CatalogDbContext dbContext)\n    : ICommandHandler<SetProductThumbnailCommand, Unit>\n{\n    public async ValueTask<Unit> Handle(SetProductThumbnailCommand command, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(command);\n\n        var product = await dbContext.Products\n            .FirstOrDefaultAsync(p => p.Id == command.ProductId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException($\"Product {command.ProductId} not found.\");\n\n        // Domain throws InvalidOperationException for unknown imageId; translate to a\n        // framework-aware 404 so the API surfaces NotFound rather than a 500.\n        if (!product.Images.Any(i => i.Id == command.ImageId))\n        {\n            throw new NotFoundException($\"Image {command.ImageId} not found on product {command.ProductId}.\");\n        }\n\n        product.SetThumbnail(command.ImageId);\n        await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);\n        return Unit.Value;\n    }\n}\n","sourceCodeStart":7,"sourceCodeEnd":33,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Catalog/Modules.Catalog/Features/v1/Products/SetProductThumbnail/SetProductThumbnailCommandHandler.cs#L7-L33","documentation":"After the product is found, Handle verifies that command.ImageId is one of product.Images; if not, it throws NotFoundException. This pre-emptively translates the domain's InvalidOperationException for an unknown image into a 404 so clients get NotFound, not a 500.","triggerScenarios":"SetProductThumbnailCommand sent with an ImageId that is not present in the product's Images collection (image deleted first, image belongs to another product, or id typo).","commonSituations":"Frontend keeps a stale image list after another user removed the image; caller passes an image id from a different product; race between image deletion and thumbnail assignment.","solutions":["Fetch the product's images and confirm the ImageId belongs to this product before sending the command.","Reload product images in the UI after any delete/add so the picker only offers current ids.","If the image belongs to another product, use that product's id — images are not shared.","Retry the flow after refreshing data if the image was just uploaded."],"exampleFix":"// before\nawait mediator.Send(new SetProductThumbnailCommand(productId, imageId));\n// after\nvar product = await dbContext.Products.Include(p => p.Images).FirstAsync(p => p.Id == productId, ct);\nif (!product.Images.Any(i => i.Id == imageId))\n    throw new InvalidOperationException(\"Pick one of the product's own images\");\nawait mediator.Send(new SetProductThumbnailCommand(productId, imageId), ct);","handlingStrategy":"validation","validationCode":"bool ok = (await dbContext.Products.Include(p => p.Images)\n    .FirstAsync(p => p.Id == productId, ct)).Images.Any(i => i.Id == imageId);","typeGuard":"var image = product?.Images.FirstOrDefault(i => i.Id == imageId);\nif (image is null) return; // image not on this product","tryCatchPattern":"try { await mediator.Send(cmd, ct); }\ncatch (NotFoundException ex) when (ex.Message.StartsWith(\"Image\")) { return Results.NotFound(ex.Message); }","preventionTips":["Populate image pickers from the product's current images immediately before submit.","Never reuse image ids across products.","Refresh image data after any upload/delete operation.","Handle 404 for images distinctly so users get a 'refresh images' hint."],"tags":["catalog","not-found","image","cqrs"],"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"}