Table of Contents

Class CacheInvalidatingUserServiceDecorator

Namespace
Tharga.Team.Service
Assembly
Tharga.Team.Service.dll

Decorator over IUserService that drops the cached copy of a user after any call that changes them — whoever implemented the write.

public sealed class CacheInvalidatingUserServiceDecorator : IUserService
Inheritance
CacheInvalidatingUserServiceDecorator
Implements
Inherited Members

Remarks

UserServiceBase caches resolved users and invalidates in the paths it owns. A host overriding one of those to supply persistence replaces the path that invalidates, so the write commits and every later read is served stale. That is the toolkit dropping a responsibility it owns, not a host concern, so the fix belongs here rather than in a note telling every host to remember.

Why a decorator and not a template method. The obvious fix is to make the mutating members non-virtual and have them call a protected abstract hook, which would make it impossible to get wrong. That is a breaking change — it stops every existing override compiling — so it is 4.0 work. A decorator invalidates regardless of who implemented the member, needs no consumer change, and can ship in a minor.

SetUserLastSeenAsync(string, DateTime) is deliberately not invalidated. It runs on every authenticated resolve (throttled), so invalidating there would empty the cache continuously and defeat the thing this class exists to keep correct. A cached LastSeen is therefore up to one resolve stale, which is the behaviour that already shipped.

Invalidation runs only after the inner call returns. A throwing write has changed nothing, and dropping a valid entry because a write failed would trade a stale read for a needless one.

Constructors

CacheInvalidatingUserServiceDecorator(IUserService)

public CacheInvalidatingUserServiceDecorator(IUserService inner)

Parameters

inner IUserService

The store to delegate to.

Remarks

A store that does not implement IUserCacheInvalidator has no cache to drop, so this decorator becomes a pass-through rather than an error.

Methods

ClearOwnIconAsync()

public Task ClearOwnIconAsync()

Returns

Task

Remarks

The self-service icon members take no key, so the caller is resolved first to learn which entry to drop. That read is a cache hit in the case that matters — the caller is signed in, which is what put them in the cache — so it costs a dictionary lookup, not a round trip.

ClearUserIconAsync(string)

Clears a specific user's icon (administrative). Requires Manage.

public Task ClearUserIconAsync(string userKey)

Parameters

userKey string

Returns

Task

DeleteUserAsync(string)

public Task DeleteUserAsync(string userKey)

Parameters

userKey string

Returns

Task

GetAsync()

All users. Cross-user enumeration — requires Manage.

public IAsyncEnumerable<IUser> GetAsync()

Returns

IAsyncEnumerable<IUser>

GetCurrentUserAsync(ClaimsPrincipal)

public Task<IUser> GetCurrentUserAsync(ClaimsPrincipal claimsPrincipal = null)

Parameters

claimsPrincipal ClaimsPrincipal

Returns

Task<IUser>

GetTeamMemberUsersAsync()

The users who share at least one team with the caller, plus the caller themselves. Self-service: an authenticated caller is required but no scope, because the result is derived entirely from the caller's own team memberships and takes no argument that could widen it.

public Task<IReadOnlyList<IUser>> GetTeamMemberUsersAsync()

Returns

Task<IReadOnlyList<IUser>>

Remarks

This is the identity source for the team member list when the caller lacks Manage. A member row's email, display name and icon live on the user record rather than on ITeamMember — accepting an invitation clears the per-team name override and promotes it to Name — so without this projection a team owner would see their own accepted members as unidentified. The default implementation returns an empty list; AuthorizationUserServiceDecorator supplies the real projection, as it holds the undecorated store.

GetUserByKeyAsync(string)

The user with the given key, or null. The default implementation scans GetAsync(); storage-backed services override it with a direct read.

public Task<IUser> GetUserByKeyAsync(string userKey)

Parameters

userKey string

Returns

Task<IUser>

SeedUserNameAsync(string, string)

Sets the user's display name only if it is currently null/empty. Used by the invitation-accept flow to promote the admin-entered invitation name into the new user's identity without clobbering an IdP-provided name.

public Task SeedUserNameAsync(string userKey, string name)

Parameters

userKey string
name string

Returns

Task

SetOwnIconAsync(byte[], string)

public Task SetOwnIconAsync(byte[] data, string contentType)

Parameters

data byte[]
contentType string

Returns

Task

Remarks

The self-service icon members take no key, so the caller is resolved first to learn which entry to drop. That read is a cache hit in the case that matters — the caller is signed in, which is what put them in the cache — so it costs a dictionary lookup, not a round trip.

SetUserDirectoryIdAsync(string, string)

Links the user to their external-directory id (DirectoryId). Called by the oid backfill (internal self-call) and by directory verification on an email-fallback match (relink). Default is a no-op — stores that track the directory id override it.

public Task SetUserDirectoryIdAsync(string userKey, string directoryId)

Parameters

userKey string
directoryId string

Returns

Task

SetUserDisabledAsync(string, DateTime?, string)

Deletes the user record from the store, with no team-membership cleanup — call through DeleteUserAsync(string, bool, CancellationToken), which removes team memberships and audits.

public Task SetUserDisabledAsync(string userKey, DateTime? disabledAt, string disabledBy)

Parameters

userKey string
disabledAt DateTime?
disabledBy string

Returns

Task

Remarks

Throws rather than no-opping when unimplemented, for the same reason DeleteUserAsync(string) does: silently skipping a requested disable would hide the missing implementation behind an apparently successful containment.

SetUserIconAsync(string, byte[], string)

Sets a specific user's icon (administrative). The mechanism is the same as SetOwnIconAsync(byte[], string) but targets userKey; requires Manage.

public Task SetUserIconAsync(string userKey, byte[] data, string contentType)

Parameters

userKey string
data byte[]
contentType string

Returns

Task

SetUserLastSeenAsync(string, DateTime)

Stamps when the user last made an authenticated request. The automatic throttled stamping is an internal self-call that bypasses the authorization decorator; calling this member from outside requires Manage. The default is a no-op — stores that track LastSeen override it.

public Task SetUserLastSeenAsync(string userKey, DateTime lastSeen)

Parameters

userKey string
lastSeen DateTime

Returns

Task

SetUserNameAsync(string, string)

Always sets the user's display name. Used by the user self-edit flow where the caller has explicitly chosen a name for themselves.

public Task SetUserNameAsync(string userKey, string name)

Parameters

userKey string
name string

Returns

Task