Table of Contents

Namespace Virtufin.WebSocketManager.Actors

Classes

ActorTypeNames

Actor type name advertised to daprd -- shared by the interface attribute and every Create<TActor>(ActorId, string) call site so it's defined in exactly one place.

ConnectPayload
SendPayload
SendRawPayload
SetTagPayload
TagsPayload

Wraps GetTagsAsync's response. Not just a style choice: the generated actor client mis-emits InvokeResultAsync<Dictionary<TKey,TValue>> as InvokeResultAsync<Dictionary<TKey, TValue(...) -- a missing closing > -- whenever a bare generic collection is used directly as a method's return type (confirmed: the same type works fine as a parameter, e.g. SetTagsAsync(Dictionary<string, string>, CancellationToken); only the return-type path is broken). A real Dapr.Actors.Next source-generator bug, worth reporting upstream -- this wrapper works around it, not a design preference.

WebSocketActorDaprSerializer
WebSocketActorWireSerializer

AOT-safe serialization for WebSocketConnectionActor's method call/response payloads, backed by the source-generated Virtufin.WebSocketManager.Actors.WebSocketActorJsonContext instead of the SDK's reflection-based default. See https://docs.dapr.io/developing-applications/sdks/dotnet/dotnet-actors-next/dotnet-actorsnext-serialization/ -- registering both of these is what let the Phase 0 spike (virtufin/common#9) publish clean under PublishAot=true.

WebSocketConnectionActor

One actor instance per WebSocket connection, keyed by connection id. Dapr's placement service guarantees at most one live activation of a given ActorId cluster-wide -- that guarantee, not a hand-rolled InstanceId directory + heartbeat + forward-or-reclaim pattern (Virtufin.Service.MultiInstance, removed by this migration -- see its Virtufin.Service 0.1.0 CHANGELOG entry), is what now ensures every RPC for a connection reaches the one process that actually holds its live ClientWebSocket.

A thin router, not a state owner: every method delegates straight to the pre-existing IWebSocketService, which still does the real work (open/close the socket, persist metadata via IDaprConnectionRepository) exactly as before. This mirrors the one part of the original (classic Dapr.Actors) 2026 attempt that was never the problem -- reusing existing business logic rather than duplicating it in the actor. What that attempt got wrong, and this one fixes: ConnectAsync(ConnectPayload, CancellationToken) is called immediately after the connection id is minted (see WebSocketManagerGrpcService.Connect), so the very first activation -- wherever Dapr's placement puts it -- is also where the real socket gets opened. Nothing here ever assumes "the socket is already open somewhere else."

WebSocketConnectionSnapshot

Interfaces

IWebSocketConnectionActor