{"record":{"id":"fea28c60e94c580d","repo":"fullstackhero/dotnet-starter-kit","slug":"message-not-found-editmessagecommandhandler","errorCode":null,"errorMessage":"Message not found.","messagePattern":"Message not found\\.","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Modules/Chat/Modules.Chat/Features/v1/Messages/EditMessage/EditMessageCommandHandler.cs","lineNumber":28,"sourceCode":"\nnamespace FSH.Modules.Chat.Features.v1.Messages.EditMessage;\n\npublic sealed class EditMessageCommandHandler(\n    ChatDbContext db,\n    ICurrentUser currentUser,\n    IHubContext<AppHub> hub)\n    : ICommandHandler<EditMessageCommand, Unit>\n{\n    public async ValueTask<Unit> Handle(EditMessageCommand cmd, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(cmd);\n        var userId = currentUser.GetUserId();\n        if (userId == Guid.Empty) throw new UnauthorizedException(\"no current user\");\n        var currentUserId = userId.ToString();\n\n        var message = await db.Messages.FirstOrDefaultAsync(m => m.Id == cmd.MessageId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException(\"Message not found.\");\n\n        // Verify membership through the parent channel (don't leak existence to non-members).\n        var channel = await db.Channels.FirstOrDefaultAsync(c => c.Id == message.ChannelId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException(\"Message not found.\");\n        channel.RequireMember(currentUserId);\n\n        message.Edit(cmd.Body, currentUserId); // domain enforces author-only\n        await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);\n\n        await hub.Clients.Group($\"channel:{channel.Id}\")\n            .SendAsync(\"ChatMessageEdited\", message.ToDto(), cancellationToken)\n            .ConfigureAwait(false);\n        return Unit.Value;\n    }\n}\n","sourceCodeStart":10,"sourceCodeEnd":45,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Chat/Modules.Chat/Features/v1/Messages/EditMessage/EditMessageCommandHandler.cs#L10-L45","documentation":"EditMessageCommandHandler throws NotFoundException(\"Message not found.\") when db.Messages has no row matching cmd.MessageId. It is the module's standard 404, and tenant filters mean a message from another tenant is indistinguishable from a missing one. Thrown before any membership or author check.","triggerScenarios":"Editing a message id that was deleted (possibly by an earlier edit/delete race), never existed, or is outside the caller's tenant scope.","commonSituations":"Two tabs open on the same message — one already deleted it; optimistic UI applied the edit locally against a stale id; cross-tenant fixture data in integration tests; message removed by retention policy.","solutions":["Confirm the message id exists in the caller's tenant before editing.","Handle 404 in the client by refreshing the channel messages and discarding the local optimistic edit.","Check tenant context/headers match the message's tenant.","Guard against concurrent delete/edit races with a reload before submit."],"exampleFix":"// before\n// optimistic edit applied against stale message\neditLocal(messageId, body);\nawait api.editMessage(messageId, body); // 404\n\n// after\nconst fresh = await api.getMessage(messageId).catch(() => null);\nif (fresh) { editLocal(messageId, body); await api.editMessage(messageId, body); }\nelse refreshMessages();","handlingStrategy":"validation","validationCode":"const msg = messagesQuery.data?.find(m => m.id === messageId);\nif (!msg) { refreshMessages(); return; } // stale id — don't edit","typeGuard":null,"tryCatchPattern":"try {\n  await api.editMessage(messageId, body);\n} catch (e) {\n  if (e.status === 404) { rollbackOptimisticEdit(messageId); refreshMessages(); }\n  else throw e;\n}","preventionTips":["Reload the message before submitting edits in long-lived views.","Roll back optimistic updates on 404.","Invalidate message caches on delete events from other sessions.","Keep test tenants consistent between message and channel seeds."],"tags":["chat","not-found","message","ef-core"],"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"}