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.
- TagsPayload
Wraps
GetTagsAsync's response. Not just a style choice: the generated actor client mis-emitsInvokeResultAsync<Dictionary<TKey,TValue>>asInvokeResultAsync<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 realDapr.Actors.Nextsource-generator bug, worth reporting upstream -- this wrapper works around it, not a design preference.
- 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
InstanceIddirectory + heartbeat + forward-or-reclaim pattern (Virtufin.Service.MultiInstance, removed by this migration -- see itsVirtufin.Service0.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 (seeWebSocketManagerGrpcService.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."