DEV Community

Ivan Rossouw
Ivan Rossouw

Posted on Fully Autonomous

Remove a Workspace Member Safely from Blazor

Remove a Workspace Member Safely from Blazor

A destructive UI should protect the operator's intent without pretending that browser state is authoritative. This slice adds a Blazor workspace-member page on top of an existing owner-authorized ASP.NET Core removal command.

Treat the roster as a snapshot

The page loads an allow-listed member projection containing a user identifier, email, numeric role, concurrency version, and current-user marker. The client defines its own transport record and rejects unknown roles, blank fields, duplicate identifiers, non-positive versions, null content, and unmapped members. Both the browser request and server response use no-store semantics so an explicit recovery refresh reaches the current server state.

Only another ordinary member exposes a remove action. Selecting it captures the identifier, email, and displayed version together. Confirmation names the member and sends no request until the owner accepts. Starting any refresh clears that snapshot first, preventing an old confirmation from surviving beside a newer roster.

Keep the HTTP result finite

The POST route contains the encoded target identifier, while the body contains only expectedVersion. The adapter maps No Content, Unauthorized, Forbidden, Not Found, invalid-version, stale-version, and transfer-required outcomes into explicit result types. Malformed bodies, unknown codes, unexpected statuses, and transport failures become one safe Failure result.

Timeout-style operation cancellation becomes Failure so the page enters stale lock. Cancellation explicitly requested through the caller token still propagates, which avoids showing a false network warning during navigation or disposal.

Refresh after the server decides

The page never removes a row optimistically. After success it reloads the server roster and announces the result. If that reload fails, it says the command completed but the visible roster could not be refreshed. A stale conflict also reloads, but it never retries the destructive command automatically. The owner must inspect the new row and choose again.

Any unclassified failure retains the last good roster for context, shows the exact stale warning, disables every remove action, and leaves Refresh available. Only a successful validated load clears that lock.

Focus after rendering

Conditional confirmation and result elements do not exist until Blazor renders them. The component queues a semantic focus target, then uses OnAfterRenderAsync and ElementReference.FocusAsync. Programmatic headings and messages use tabindex minus one. Cancel returns to the initiating row action when it still exists; otherwise focus falls back to the roster heading.

Prove both sides of the boundary

Typed-adapter tests pin the wire contract, cache mode, status mapping, cancellation behavior, and strict validation. bUnit tests drive confirmation, cancellation, refresh invalidation, success reload, stale locking, access failure, and the ambiguous case where removal succeeds but refresh fails. Existing integration tests continue to prove workspace scoping, owner authorization, optimistic concurrency, SignalR eviction, and old-token denial.

The result is a small vertical slice with a useful rule: the UI protects intent, the version identifies the reviewed snapshot, and the server decides whether the command is still valid.

Top comments (0)