Class CacheInvalidatingUserServiceDecorator
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
innerIUserServiceThe 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
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
userKeystring
Returns
DeleteUserAsync(string)
public Task DeleteUserAsync(string userKey)
Parameters
userKeystring
Returns
GetAsync()
All users. Cross-user enumeration — requires Manage.
public IAsyncEnumerable<IUser> GetAsync()
Returns
GetCurrentUserAsync(ClaimsPrincipal)
public Task<IUser> GetCurrentUserAsync(ClaimsPrincipal claimsPrincipal = null)
Parameters
claimsPrincipalClaimsPrincipal
Returns
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
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
userKeystring
Returns
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
Returns
SetOwnIconAsync(byte[], string)
public Task SetOwnIconAsync(byte[] data, string contentType)
Parameters
Returns
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
Returns
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
Returns
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
Returns
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
Returns
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)