fullstackhero/dotnet-starter-kit · error · NotFoundException
Product not found.
Error message
Product {query.ProductId} not found. What it means
GetProductByIdQueryHandler runs an AsNoTracking FirstOrDefault for the given ProductId and throws NotFoundException when no product matches, producing HTTP 404. Because the query uses the default DbContext filters, soft-deleted products and other tenants' products are invisible.
Solutions
- Validate the id against the products list endpoint before deep-linking, or handle 404 gracefully in the UI with a 'product not available' state.
- If the product should exist but was deleted, call the restore endpoint (requires appropriate permissions).
- Check tenant context/headers — the product may exist under a different tenant.
- Verify environment: dev ids rarely exist in prod and vice versa.
Example fix
// before const product = await api.getProduct(id); // throws 404, crashes page // after const product = await api.getProduct(id).catch(e => e.status === 404 ? null : Promise.reject(e) ); if (!product) return <NotFound message="Product not available" />;
Defensive patterns
Strategy: try-catch
Validate before calling
// validate GUID format before calling
const isGuid = (v) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v);
if (!isGuid(productId)) throw new Error('Invalid product id'); Type guard
function isProductDto(x): x is ProductDto {
return !!x && typeof x === 'object' && typeof x.id === 'string' && typeof x.name === 'string';
} Try / catch
try {
const product = await api.getProduct(productId);
} catch (e) {
if (e.status === 404) return <NotFoundPage />;
throw e;
} Prevention
- Render a friendly 404 state instead of letting the page crash
- Don't bookmark raw product ids for long-lived links; use slugs where possible
- Clear product ids from client state on tenant switch
When it happens
Trigger: GET /products/{id} with a nonexistent id, a soft-deleted product's id, a wrong-tenant product id, or an id from another environment's database.
Common situations: Bookmarked/shared links to products deleted since; navigation after delete without list refresh; tenant switch on the client while old ids remain in state.
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/176e2b8de3ebe1b1.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Catalog/Modules.Catalog/Features/v1/Products/GetProductById/GetProductByIdQueryHandler.cs:21
using FSH.Modules.Catalog.Contracts.v1.Products;
using FSH.Modules.Catalog.Data;
using Mediator;
using Microsoft.EntityFrameworkCore;
namespace FSH.Modules.Catalog.Features.v1.Products.GetProductById;
public sealed class GetProductByIdQueryHandler(CatalogDbContext dbContext)
: IQueryHandler<GetProductByIdQuery, ProductDto>
{
public async ValueTask<ProductDto> Handle(GetProductByIdQuery query, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(query);
var product = await dbContext.Products
.AsNoTracking()
.FirstOrDefaultAsync(p => p.Id == query.ProductId, cancellationToken)
.ConfigureAwait(false)
?? throw new NotFoundException($"Product {query.ProductId} not found.");
return product.ToDto();
}
}
View on GitHub (pinned to 3f2959e683)