AggregateException vs direct throws.
Prerequisites
- Authentication and a connected
IClient - Exceptions — the hierarchy this pattern relies on
- Familiarity with ServerResource, ProjectResource, and ModelResource
Complete Example
This mirrors the pattern Speckle’s own connectors use to decide whether a server supports connector-scale model ingestion, adapted fromSendOperation.CheckUseModelIngestionSend in speckle-sharp-connectors:
WorkspacePermissionException, keeps that distinction intact.
Step-by-Step Explanation
Server-Level Capability Checks
The simplest form: a boolean method that tells you whether a whole feature area exists on this deployment, with no exceptions involved.Permission Checks Returned Alongside Resources
Several resources return permission information with the data, so you can gate UI and code paths before the user attempts anything:client.Project.GetPermissions(projectId)→ProjectPermissionChecks(canCreateModel,canDelete,canLoad;canPublishis obsolete — useModelPermissionChecks.canCreateVersion)client.Model.GetPermissions(projectId, modelId)→ModelPermissionChecks(canUpdate,canDelete,canCreateVersion)client.ActiveUser.GetProjectsWithPermissions()→ eachProjectWithPermissions.permissions(canCreateModel,canDelete,canLoadonly)Workspace.canCreateProjecton a fetched workspace
Explicit “Can I Do This” Checks
For actions that aren’t tied to an existing permission field on a resource, some methods ask the question directly and return aPermissionCheckResult:
client.Model.CanCreateModelIngestion(projectId, modelId)client.ActiveUser.CanCreatePersonalProjects()
PermissionCheckResult exposes authorized, code, message, and a convenience method:
The property is
authorized (US spelling); the guard method is EnsureAuthorised() (UK spelling) — both match the SDK.EnsureAuthorised() as a guard clause instead of hand-rolling an if (!result.authorized) throw ... at every call site.
Telling “Not Allowed” Apart From “Server Doesn’t Support This”
This is the part that’s easy to get wrong with a blanketcatch. A server old enough to predate a permission field (like canCreateIngestion, added in server 3.0.11) doesn’t return authorized: false — its GraphQL schema doesn’t have the field at all, so the query fails at the schema-validation level. That surfaces as AggregateException wrapping a SpeckleGraphQLInvalidQueryException, which is structurally different from WorkspacePermissionException (thrown directly by EnsureAuthorised() when a capable server says no).
Catch only the narrow exception that represents “this server doesn’t know this concept,” as shown in the Complete Example above. Don’t catch SpeckleException or AggregateException broadly around the whole check — that would also swallow a genuine WorkspacePermissionException (if you don’t re-throw it) or mask unrelated failures (network errors, auth expiry) as capability gaps.
Some older, internal Speckle tooling also uses raw HTTP endpoint probing (checking for a
404 response) to detect feature availability outside GraphQL. That’s an implementation detail of specific legacy code paths, not a documented part of the Speckle.Sdk public surface — prefer the GraphQL capability and permission checks above, which are part of the supported API.Common Pitfalls
Wrapping a mutation in try/catch (Exception) instead of checking first
Wrapping a mutation in try/catch (Exception) instead of checking first
A blanket catch can’t tell a user “your server needs an upgrade” apart from “you don’t have permission” apart from “the network dropped.” Check server capability and permissions first, and let genuinely unexpected exceptions propagate — see Exceptions.
Assuming workspaces are always enabled
Assuming workspaces are always enabled
Many self-hosted and community servers run without the workspaces module. Code that only exercises
client.Workspace and workspace-scoped client.ActiveUser methods will throw SpeckleGraphQLWorkspaceNotEnabledException (wrapped in AggregateException) on those servers. Check client.Server.IsWorkspaceEnabled() and support the personal-project path too.Catching AggregateException without inspecting InnerExceptions
Catching AggregateException without inspecting InnerExceptions
An unqualified
catch (AggregateException) around a capability check treats old-server incompatibility and a real permission denial identically. Filter with an exception when clause (as in the Complete Example) or inspect InnerExceptions before deciding how to respond.Next Steps
Core concepts
Server capability patterns in context
Publish large models (connectors)
CanCreateModelIngestion in upload flow
ServerResource
IsWorkspaceEnabled API surface