Class TeamServiceBase
public abstract class TeamServiceBase
- Inheritance
-
TeamServiceBase
- Derived
- Inherited Members
Constructors
TeamServiceBase(IUserService, ILogger<TeamServiceBase>, IIconStore, ITeamCache, TeamDeleteMode, InvitationOptions)
protected TeamServiceBase(IUserService userService, ILogger<TeamServiceBase> logger = null, IIconStore iconStore = null, ITeamCache cache = null, TeamDeleteMode deleteMode = TeamDeleteMode.Soft, InvitationOptions invitationOptions = null)
Parameters
userServiceIUserServiceResolves the calling user.
loggerILogger<TeamServiceBase>Optional. Used to report ambiguous member matches.
iconStoreIIconStoreOptional. Required only for team icons; see SetTeamIconAsync(string, byte[], string).
cacheITeamCacheWhere the membership and custom-role lookups are kept. Defaults to the process-local InMemoryTeamCache, which is correct for a single instance only — forward this parameter from your own service's constructor so a shared implementation can be registered, or a multi-instance deployment will not see permission changes made through another instance. See ITeamCache.
deleteModeTeamDeleteModeinvitationOptionsInvitationOptions
Properties
DeleteMode
What deleting a team does in this store. Defaults to whatever the host configured, or Soft.
protected virtual TeamDeleteMode DeleteMode { get; }
Property Value
Remarks
An optional constructor parameter rather than a required one, and a virtual property rather than a field read, so neither a derived host that never passes it nor one that wants to decide the mode itself has to change. Both matter in a patch.
Note this is only half the answer — see SupportsSoftDelete. A store that cannot mark a team deleted deletes outright whatever this says.
SupportsSoftDelete
Whether this store can soft-delete. false by default, so a store that has not implemented it
keeps deleting exactly as before.
protected virtual bool SupportsSoftDelete { get; }
Property Value
Remarks
This is what keeps soft delete a patch rather than a break. The default delete mode is
Soft, but a host deriving from this class predates the feature and
cannot mark a team deleted. Reporting false makes the mode resolve to
Hard for that store — the behaviour it already had — instead of failing
on an operation it cannot perform.
Surfaces are expected to consult this before offering restore or purge; a control that throws when clicked is worse than no control.
Methods
AddMemberAsync(string, InviteUserModel)
Invites someone, or renews the invitation they already have.
public Task AddMemberAsync(string teamKey, InviteUserModel model)
Parameters
teamKeystringmodelInviteUserModel
Returns
Remarks
Re-inviting an address that already has an outstanding invitation keeps its code. It used to add a second member row carrying a second live code for one person, which is two working links to the same seat and one of them impossible to withdraw. Renewing instead means someone who has already mailed a link can give it more time without the recipient's link dying — the requirement behind Tharga/Team#249.
Changed details are still applied. An invitation renewed with a different access level or name takes them, because an administrator who re-invites at a different level plainly means the new one; silently keeping the old level would be the more surprising reading of the same click.
The expiry is only moved when a Lifetime is configured. Without one invitations do not expire, so there is nothing to extend and a store that has not implemented the expiry seam is not asked to.
AddTeamMemberAsync(string, InviteUserModel)
protected abstract Task AddTeamMemberAsync(string teamKey, InviteUserModel model)
Parameters
teamKeystringmodelInviteUserModel
Returns
ClearTeamIconAsync(string)
Clears the team's icon and deletes the stored bytes. Gated by team:manage.
public Task ClearTeamIconAsync(string teamKey)
Parameters
teamKeystring
Returns
CreateTeamAsync(string)
public Task<ITeam> CreateTeamAsync(string name)
Parameters
namestring
Returns
CreateTeamAsync(string, string, IUser, string)
protected abstract Task<ITeam> CreateTeamAsync(string teamKey, string name, IUser user, string displayName = null)
Parameters
Returns
DeleteTeamAsync(string)
protected abstract Task DeleteTeamAsync(string teamKey)
Parameters
teamKeystring
Returns
DeleteTeamAsync<TMember>(string)
Deletes the team — soft by default, so it can be restored, and so deleting needs no privilege to drop storage.
public Task DeleteTeamAsync<TMember>(string teamKey) where TMember : ITeamMember
Parameters
teamKeystring
Returns
Type Parameters
TMember
Remarks
Resolves to a hard delete when the host asks for one, and also when the store cannot soft-delete: see SupportsSoftDelete. Either way the team disappears from every read, which is why the change of default is invisible through this API.
ExtendInvitationAsync(string, string)
Gives an outstanding invitation a fresh lifetime, keeping its code.
public Task ExtendInvitationAsync(string teamKey, string inviteKey)
Parameters
Returns
Remarks
The point is what it does not do: mint a new code. A link that has already been mailed keeps working, so extending an invitation costs the recipient nothing and needs no second message. That is only possible because the expiry lives on the invitation record rather than being derived from its creation time — see ExpiresAt.
An operation rather than a settable expiry, so it can be authorized and audited as one fact.
The new expiry is now plus the configured Lifetime. Where no
lifetime is configured invitations do not expire, and extending clears any expiry the invitation was
carrying.
GetAllTeamsAsync()
Every team, regardless of membership. Requires the Read system scope.
public virtual IAsyncEnumerable<ITeam> GetAllTeamsAsync()
Returns
Remarks
Discovery only — the returned teams carry no implied access. Acting inside a team the caller is not a member of still depends on that team's consent. Use Tharga.Team.ITeamService.GetTeamsAsync() for the caller's own teams; this method is for oversight surfaces (support, administration).
GetAllTeamsAsync<TMember>()
Every team, regardless of membership. Requires the Read system scope.
public virtual IAsyncEnumerable<ITeam<TMember>> GetAllTeamsAsync<TMember>() where TMember : ITeamMember
Returns
- IAsyncEnumerable<ITeam<TMember>>
Type Parameters
TMember
Remarks
Discovery only — the returned teams carry no implied access. Acting inside a team the caller is not a member of still depends on that team's consent. Use Tharga.Team.ITeamService.GetTeamsAsync() for the caller's own teams; this method is for oversight surfaces (support, administration).
GetAllTeamsInternalAsync()
Backs GetAllTeamsAsync(). Virtual rather than abstract so existing derived services keep compiling; the default returns nothing, and storage-backed bases override it.
protected virtual IAsyncEnumerable<ITeam> GetAllTeamsInternalAsync()
Returns
GetConsentedTeamsAsync(string[])
public IAsyncEnumerable<ITeam> GetConsentedTeamsAsync(string[] userRoles)
Parameters
userRolesstring[]
Returns
GetConsentedTeamsInternalAsync(string[])
protected abstract IAsyncEnumerable<ITeam> GetConsentedTeamsInternalAsync(string[] userRoles)
Parameters
userRolesstring[]
Returns
GetInvitationInternalAsync(string, string)
protected virtual Task<Invitation> GetInvitationInternalAsync(string teamKey, string inviteKey)
Parameters
Returns
GetInvitedMemberNameAsync(string, string)
Look up the (admin-entered) Name of the member identified by inviteKey
inside the given team. Used to capture the invitation Name before accept clears it,
so it can be promoted to User.Name. Default implementation returns null;
derivatives that have access to the typed team document override it.
protected virtual Task<string> GetInvitedMemberNameAsync(string teamKey, string inviteKey)
Parameters
Returns
GetMembersAsync(string)
Every member on the team's roster, in any MembershipState — including invited and rejected.
public virtual IAsyncEnumerable<ITeamMember> GetMembersAsync(string teamKey)
Parameters
teamKeystring
Returns
Remarks
The counterpart to GetTeamMemberAsync(string, string), which returns only an active membership and cannot tell a pending invitee from a stranger. Reach for this one whenever the difference matters.
GetTeamAsync(string)
protected abstract Task<ITeam> GetTeamAsync(string teamKey)
Parameters
teamKeystring
Returns
GetTeamAsync<TMember>(string)
public Task<ITeam<TMember>> GetTeamAsync<TMember>(string teamKey) where TMember : ITeamMember
Parameters
teamKeystring
Returns
Type Parameters
TMember
GetTeamByKeyAsync(string)
A team by key, regardless of the caller's membership — a non-generic exact read for call sites with
no TMember to hand (e.g. the audit decorator capturing a "before" value for a consent change
made by a non-member acting through consent). Returns null when the team does not exist.
public Task<ITeam> GetTeamByKeyAsync(string teamKey)
Parameters
teamKeystring
Returns
GetTeamCustomRolesAsync(string)
The team's runtime-defined custom roles. Requires team:read on that team.
public Task<IReadOnlyList<TenantRoleDefinition>> GetTeamCustomRolesAsync(string teamKey)
Parameters
teamKeystring
Returns
Remarks
A read of team detail, so it is gated like the others — not like its write sibling
SetTeamCustomRolesAsync(string, IReadOnlyList<TenantRoleDefinition>), which needs team:manage. Seeing which roles a team
defines is part of seeing the team.
GetTeamKeyByInviteKeyAsync(string)
The key of the team holding an outstanding invitation whose code is inviteKey, or
null.
public Task<string> GetTeamKeyByInviteKeyAsync(string inviteKey)
Parameters
inviteKeystring
Returns
Remarks
Returns a key rather than a team, so the caller loads it through the reads that already exist and this adds no second way to fetch a team. It also keeps the member type out of the signature, which a generic overload would have dragged in for no gain.
Null means "no single team", covering three cases on purpose: nothing matched, the store cannot answer this at all, and — the one worth stating — more than one team matched. A token is a bearer credential, so resolving an ambiguous one by picking a team would hand someone access to a team nobody invited them to.
The default returns null, so a host with its own store keeps compiling and keeps working: invitation links carrying the team key still resolve, and only the short form is unavailable.
GetTeamKeyByInviteKeyInternalAsync(string)
The outstanding invitation matching inviteKey on teamKey, or
null. Backs expiry enforcement.
protected virtual Task<string> GetTeamKeyByInviteKeyInternalAsync(string inviteKey)
Parameters
inviteKeystring
Returns
Remarks
Virtual rather than abstract, like the other members added after the seam was first drawn, so a host with its own store keeps compiling. The default returns null, which reads as "no expiry to enforce".
That default is a hole if a lifetime is configured and this is not overridden — expiry would
silently not apply. It is a startup check rather than a silent default for exactly that reason: see
InvitationExpiryWiringCheck, which fails the boot naming this method when
Lifetime is set and the store cannot answer it. Nothing is asked of a
host that has not opted into expiry.
GetTeamMemberAsync(string, string)
One active member of a team. Requires team:read on that team.
public Task<ITeamMember> GetTeamMemberAsync(string teamKey, string userKey)
Parameters
Returns
Remarks
Whether an invited or rejected member comes back here is up to the host's store, so do not
rely on either answer. This resolves through the store's "teams this user belongs to" query.
The MongoDB store filters that query on State == MembershipState.Member, which makes a
pending invitee indistinguishable from somebody who was never in the team; a store written
differently may well return them.
So treat a non-null result as "has some membership" and null as "cannot act as a member" — never as a reliable answer to which state they are in. Anything that must tell the states apart — a message explaining a refusal, a roster count, an admin grid — has to use GetMembersAsync(string), which reads the team directly and is the only portable way to see every state.
Not hypothetical: suspending a member shipped with this bug, refusing an invitee with "is not a member of team", which was both untrue and unhelpful.
GetTeamMembersAsync(string, string)
protected abstract Task<ITeamMember> GetTeamMembersAsync(string teamKey, string userKey)
Parameters
Returns
GetTeamsAsync()
public IAsyncEnumerable<ITeam> GetTeamsAsync()
Returns
GetTeamsAsync(IUser)
protected abstract IAsyncEnumerable<ITeam> GetTeamsAsync(IUser user)
Parameters
userIUser
Returns
GetTeamsAsync<TMember>()
public IAsyncEnumerable<ITeam<TMember>> GetTeamsAsync<TMember>() where TMember : ITeamMember
Returns
- IAsyncEnumerable<ITeam<TMember>>
Type Parameters
TMember
GetTeamsForUserWithAccessLevelAsync(string, AccessLevel)
The teams where this user holds exactly accessLevel. Requires the
Manage system scope.
public Task<IReadOnlyList<ITeam>> GetTeamsForUserWithAccessLevelAsync(string userKey, AccessLevel accessLevel)
Parameters
userKeystringaccessLevelAccessLevel
Returns
Remarks
The driver is user deletion: asking for Owner answers "which teams will this delete strand?", so the admin can transfer ownership before deleting rather than being told afterwards that something is now unrecoverable.
Exact match, not minimum. "Teams they own" is the question; a caller wanting a threshold
can ask more than once. A minimum-level overload would make the common case ambiguous at the call
site, where Owner would silently also mean every level above it — of which there are none,
making the parameter read as if it did something it does not.
Gated on users:manage rather than teams:read, deliberately. The caller of this
already holds the right to remove the user from every one of these teams, so learning which they
are is strictly less than they can already do — and gating it on a scope they may not hold would
hide the warning from exactly the person about to cause the damage.
GetTeamsForUserWithAccessLevelInternalAsync(string, AccessLevel)
Backs RemoveUserFromAllTeamsAsync(string). Virtual rather than abstract so existing derived services keep compiling; the default throws rather than returning 0, since a silent no-op on a deletion path would hide the missing implementation. Storage-backed bases override it.
protected virtual Task<IReadOnlyList<ITeam>> GetTeamsForUserWithAccessLevelInternalAsync(string userKey, AccessLevel accessLevel)
Parameters
userKeystringaccessLevelAccessLevel
Returns
IsTeamKeyInUseAsync(string)
Whether a team key is already taken — including by a soft-deleted team.
protected virtual Task<bool> IsTeamKeyInUseAsync(string teamKey)
Parameters
teamKeystring
Returns
Remarks
A soft-deleted team still owns its key, and this is what enforces that. Key generation used the ordinary team read, which now excludes deleted teams — so without this a deleted team's key reads as free and gets reissued. In a deployment that derives a team's database name from its key, the new team would then be pointed at the deleted team's data: the corruption Tharga/Team#224 is about, arriving by a different route.
The default keeps the old behaviour — live teams only — so a store that cannot see deleted teams is unaffected, which is also the store that cannot soft-delete and therefore never has one.
LeaveTeamAsync(string)
Removes the calling user from the team. Refuses the Owner, who must transfer ownership first, and the last administrator of a team with no owner.
public Task LeaveTeamAsync(string teamKey)
Parameters
teamKeystring
Returns
Remarks
It takes no user key, and that is the whole of its authorization. There is nothing in the
signature to point at anybody else, so a caller can only remove themselves — which is why the
decorators pass it through unscoped where RemoveMemberAsync(string, string) requires
member:manage. That scope is registered at Administrator, so
gating leaving on it means an ordinary member cannot leave the team they are in.
A scope could not express this rule anyway: a suspended member holds none at all, and suspension is precisely the state someone most wants out of.
A default interface method for the same reason as RestoreTeamAsync<TMember>(string) — a host implementing this contract directly keeps compiling — and it throws rather than no-opping, because reporting success for a departure that did not happen leaves someone believing they are out of a team they are still in.
PurgeTeamAsync(string)
Removes the team permanently, including its storage.
protected virtual Task PurgeTeamAsync(string teamKey)
Parameters
teamKeystring
Returns
Remarks
The default is DeleteTeamAsync(string) — precisely what deleting a team meant before soft delete existed — so every store gains a working purge without implementing anything.
This is the only operation that needs whatever privilege the adapter requires to drop a team's data.
For the MongoDB adapter in a per-team-database deployment that is dropDatabase, which is why
confining it here is the point of Tharga/Team#224.
PurgeTeamAsync<TMember>(string)
Removes a team permanently, including its storage. Irreversible.
public Task PurgeTeamAsync<TMember>(string teamKey) where TMember : ITeamMember
Parameters
teamKeystring
Returns
Type Parameters
TMember
Remarks
The only operation needing the privilege to drop a team's data. A store refusal surfaces as TeamStorageException rather than as the store's own exception.
RemoveMemberAsync(string, string)
public Task RemoveMemberAsync(string teamKey, string userKey)
Parameters
Returns
RemoveTeamMemberAsync(string, string)
protected abstract Task RemoveTeamMemberAsync(string teamKey, string userKey)
Parameters
Returns
RemoveUserFromAllTeamsAsync(string)
Removes the user from every team they appear in, regardless of membership state. Backs user deletion (DeleteUserAsync(string, bool, CancellationToken)); requires the Manage system scope. Returns the number of teams the user was removed from.
public Task<int> RemoveUserFromAllTeamsAsync(string userKey)
Parameters
userKeystring
Returns
RemoveUserFromAllTeamsInternalAsync(string)
protected virtual Task<int> RemoveUserFromAllTeamsInternalAsync(string userKey)
Parameters
userKeystring
Returns
RenameTeamAsync<TMember>(string, string)
public Task RenameTeamAsync<TMember>(string teamKey, string name) where TMember : ITeamMember
Parameters
Returns
Type Parameters
TMember
ResolveDisplayName(IUser)
public static string ResolveDisplayName(IUser user)
Parameters
userIUser
Returns
RestoreTeamAsync(string)
Clears the deleted mark, returning the team to normal use.
protected virtual Task RestoreTeamAsync(string teamKey)
Parameters
teamKeystring
Returns
Remarks
Unreachable unless SupportsSoftDelete is true — there is nothing to restore in a
store that deletes outright — so the default throws rather than pretending to succeed.
RestoreTeamAsync<TMember>(string)
Restores a soft-deleted team.
public Task RestoreTeamAsync<TMember>(string teamKey) where TMember : ITeamMember
Parameters
teamKeystring
Returns
Type Parameters
TMember
SetInvitationResponseAsync(string, string, string, bool)
public Task SetInvitationResponseAsync(string teamKey, string userKey, string inviteKey, bool accept)
Parameters
Returns
SetMemberLastSeenAsync(string)
public Task SetMemberLastSeenAsync(string teamKey)
Parameters
teamKeystring
Returns
SetMemberNameAsync(string, string, string)
public Task SetMemberNameAsync(string teamKey, string userKey, string name)
Parameters
Returns
SetMemberRoleAsync(string, string, AccessLevel)
Sets a member's access level. Ownership is not settable here — it changes only through TransferOwnershipAsync<TMember>(string, string), which checks that the caller is the current owner.
public Task SetMemberRoleAsync(string teamKey, string userKey, AccessLevel accessLevel)
Parameters
teamKeystringuserKeystringaccessLevelAccessLevel
Returns
Remarks
Both directions are refused. Granting Owner would let any holder of the member-manage scope promote themselves past that check; demoting the sitting owner would leave a team nobody can transfer, because transfer requires the caller to be the owner. Transfer itself is unaffected — it calls the protected SetTeamMemberRoleAsync(string, string, AccessLevel) directly.
SetMemberScopeOverridesAsync(string, string, string[])
public Task SetMemberScopeOverridesAsync(string teamKey, string userKey, string[] scopeOverrides)
Parameters
Returns
SetMemberSuspendedAsync(string, string, bool)
public Task SetMemberSuspendedAsync(string teamKey, string userKey, bool suspended)
Parameters
Returns
Remarks
Two refusals, both mirroring guards this class already applies elsewhere. The Owner cannot be
suspended — the same reason the owner cannot leave and cannot be demoted: it would leave a team
whose ownership nobody can transfer, since transfer requires the caller to be the owner. A
member cannot suspend themselves, so an administrator who does it needs a second one to undo
it, and somebody is always left holding member:manage.
The member cache is dropped on both directions, or the claims builder keeps reading the old state and the suspension takes effect only after the entry ages out.
SetMemberTenantRolesAsync(string, string, string[])
public Task SetMemberTenantRolesAsync(string teamKey, string userKey, string[] tenantRoles)
Parameters
Returns
SetOwnerAsync<TMember>(string, string)
Makes an existing member the sole owner of the team, demoting every other owner to Administrator. Requires the SetOwner system scope. Returns the user keys of the owners demoted, empty when nothing changed.
public Task<SetOwnerResult> SetOwnerAsync<TMember>(string teamKey, string newOwnerUserKey) where TMember : ITeamMember
Parameters
Returns
Type Parameters
TMember
Remarks
Refuses only when the candidate is not already a member — see TeamOwnership for why that one condition is load-bearing and why the current owner count deliberately is not. Serves three states: a team with several owners (a legacy sync), a team whose owner cannot hand over themselves, and a team with no owner at all.
Distinct from TransferOwnershipAsync<TMember>(string, string), which requires the caller to be the owner. That is the in-team path and stays as it is; this is the operator path.
SetTeamConsentAsync(string, string[], AccessLevel?)
public Task SetTeamConsentAsync(string teamKey, string[] consentedRoles, AccessLevel? accessLevel = null)
Parameters
teamKeystringconsentedRolesstring[]accessLevelAccessLevel?
Returns
SetTeamConsentInternalAsync(string, string[], AccessLevel?)
protected abstract Task SetTeamConsentInternalAsync(string teamKey, string[] consentedRoles, AccessLevel? accessLevel)
Parameters
teamKeystringconsentedRolesstring[]accessLevelAccessLevel?
Returns
SetTeamCustomRolesAsync(string, IReadOnlyList<TenantRoleDefinition>)
public Task SetTeamCustomRolesAsync(string teamKey, IReadOnlyList<TenantRoleDefinition> customRoles)
Parameters
teamKeystringcustomRolesIReadOnlyList<TenantRoleDefinition>
Returns
SetTeamCustomRolesInternalAsync(string, IReadOnlyList<TenantRoleDefinition>)
protected abstract Task SetTeamCustomRolesInternalAsync(string teamKey, IReadOnlyList<TenantRoleDefinition> customRoles)
Parameters
teamKeystringcustomRolesIReadOnlyList<TenantRoleDefinition>
Returns
SetTeamIconAsync(string, byte[], string)
Sets the team's icon from raw image bytes: stores them via the registered IIconStore,
persists the reference on the team, and deletes any previously-stored icon. Requires a registered
icon store. Gated by team:manage.
public Task SetTeamIconAsync(string teamKey, byte[] data, string contentType)
Parameters
Returns
SetTeamIconReferenceInternalAsync(string, string)
Backs SetTeamIconAsync(string, byte[], string) / ClearTeamIconAsync(string) — persists the icon reference (or null to clear) on the team document. Virtual-throw so existing derived services keep compiling; storage-backed bases override it.
protected virtual Task SetTeamIconReferenceInternalAsync(string teamKey, string reference)
Parameters
Returns
SetTeamMemberInvitationExpiryAsync(string, string, DateTime?)
Moves the expiry of the invitation matching inviteKey, leaving its code alone.
protected virtual Task SetTeamMemberInvitationExpiryAsync(string teamKey, string inviteKey, DateTime? expiresAt)
Parameters
Returns
Remarks
Throws rather than no-opping when unimplemented, unlike GetInvitationInternalAsync(string, string). The difference is what silence would mean: a store that cannot read an expiry has nothing to enforce, but a store that silently discards an extension reports success for an invitation that stays expired, and the operator finds out from the person who could not accept it.
SetTeamMemberInvitationResponseAsync(string, string, string, bool)
protected abstract Task<ITeam> SetTeamMemberInvitationResponseAsync(string teamKey, string userKey, string inviteKey, bool accept)
Parameters
Returns
SetTeamMemberLastSeenAsync(string, string)
protected abstract Task SetTeamMemberLastSeenAsync(string teamKey, string userKey)
Parameters
Returns
SetTeamMemberNameAsync(string, string, string)
protected abstract Task SetTeamMemberNameAsync(string teamKey, string userKey, string name)
Parameters
Returns
SetTeamMemberRoleAsync(string, string, AccessLevel)
protected abstract Task SetTeamMemberRoleAsync(string teamKey, string userKey, AccessLevel accessLevel)
Parameters
teamKeystringuserKeystringaccessLevelAccessLevel
Returns
SetTeamMemberScopeOverridesAsync(string, string, string[])
protected abstract Task SetTeamMemberScopeOverridesAsync(string teamKey, string userKey, string[] scopeOverrides)
Parameters
Returns
SetTeamMemberSuspendedAsync(string, string, DateTime?, string)
Persists a member's suspended state. Override to support suspending members.
protected virtual Task SetTeamMemberSuspendedAsync(string teamKey, string userKey, DateTime? suspendedAt, string suspendedBy)
Parameters
Returns
Remarks
Virtual with a throwing body rather than abstract: adding an abstract member here would break every host that already derives from this class. Throwing rather than no-opping for the same reason the user and key equivalents do — a suspension silently skipped is a containment reported but never applied.
SetTeamMemberTenantRolesAsync(string, string, string[])
protected abstract Task SetTeamMemberTenantRolesAsync(string teamKey, string userKey, string[] tenantRoles)
Parameters
Returns
SetTeamNameAsync(string, string)
protected abstract Task SetTeamNameAsync(string teamKey, string name)
Parameters
Returns
SoftDeleteTeamAsync(string, string)
Marks the team deleted without removing it, recording when and by whom.
protected virtual Task SoftDeleteTeamAsync(string teamKey, string deletedBy)
Parameters
Returns
Remarks
Virtual, never abstract — an abstract member here breaks every derived host at compile time. The default delegates to DeleteTeamAsync(string), so a store that has not implemented soft delete performs the irreversible delete it always did rather than throwing. Paired with SupportsSoftDelete, which tells callers which of the two they will get.
TransferOwnershipAsync<TMember>(string, string)
public Task TransferOwnershipAsync<TMember>(string teamKey, string newOwnerUserKey) where TMember : ITeamMember
Parameters
Returns
Type Parameters
TMember
Events
SelectTeamEvent
public event EventHandler<SelectTeamEventArgs> SelectTeamEvent
Event Type
TeamsListChangedEvent
public event EventHandler<TeamsListChangedEventArgs> TeamsListChangedEvent