fullstackhero/dotnet-starter-kit · error · NotFoundException

Ticket not found.

Error message

Ticket {command.TicketId} not found.

What it means

ResolveTicket's handler looks up the ticket by the command's TicketId and, when no matching row is returned, throws the app-wide NotFoundException with the message "Ticket {id} not found.". This is the standard FSH pattern: a CQRS command referencing a nonexistent or soft-deleted aggregate is surfaced as a typed not-found error (mapped to HTTP 404 by the exception middleware) rather than a NullReferenceException deeper in the pipeline.

Solutions

  1. Verify the TicketId exists with a query including soft-deleted rows (e.g. IgnoreQueryFilters) before/instead of resolving.
  2. If the ticket is soft-deleted and you intend to restore it, call the RestoreTicket endpoint instead of ResolveTicket.
  3. Check the request is going to the correct tenant (tenant header/route) so the tenant query filter matches the ticket's tenant.
  4. Confirm the id format: it must be the exact GUID of the ticket as returned by the list endpoint, not a number or external reference.

Example fix

// before (id from stale client cache)
var id = ticketList[0].Id; // ticket already deleted server-side
await api.ResolveTicket(new ResolveTicketCommand(id, note));
// after (verify existence first, tolerate not-found)
var ticket = await api.GetTicket(id);
if (ticket is null) { /* refresh list / show user */ return; }
await api.ResolveTicket(new ResolveTicketCommand(id, note));
Defensive patterns

Strategy: try-catch

Validate before calling

const exists = await api.getTicket(id);
if (!exists) throw new SkipError('Ticket ' + id + ' not found');

Try / catch

try {
  await resolveTicket(id, note);
} catch (ApiError e) when (e.Status === 404) {
  refreshTicketList();
  notify('Ticket no longer exists');
}

Prevention

When it happens

Trigger: Calling the ResolveTicket endpoint/command with a TicketId that does not exist in the Tickets table, or that exists but is soft-deleted (the query runs with default query filters, so rows flagged with the SoftDelete filter are invisible), or that belongs to a different tenant (tenant isolation filter on BaseDbContext excludes it).

Common situations: Client caches a ticket id from a previous session and the ticket was since deleted; a caller tries to resolve a ticket from another tenant; the id was mistyped or truncated; the ticket was soft-deleted by a concurrent user between listing and resolving; test fixtures were reset so old ids no longer exist.

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/0b269355c2513a23. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Tickets/Modules.Tickets/Features/v1/Tickets/ResolveTicket/ResolveTicketCommandHandler.cs:19

using FSH.Framework.Core.Exceptions;
using FSH.Modules.Tickets.Contracts.v1.Tickets;
using FSH.Modules.Tickets.Data;
using Mediator;
using Microsoft.EntityFrameworkCore;

namespace FSH.Modules.Tickets.Features.v1.Tickets.ResolveTicket;

public sealed class ResolveTicketCommandHandler(TicketsDbContext dbContext)
    : ICommandHandler<ResolveTicketCommand, Guid>
{
    public async ValueTask<Guid> Handle(ResolveTicketCommand command, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(command);

        var ticket = await dbContext.Tickets
            .FirstOrDefaultAsync(t => t.Id == command.TicketId, cancellationToken)
            .ConfigureAwait(false)
            ?? throw new NotFoundException($"Ticket {command.TicketId} not found.");

        ticket.Resolve(command.ResolutionNote);
        await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
        return ticket.Id;
    }
}

View on GitHub (pinned to 3f2959e683)