private void LogMessage(CachingEntityAccessControlCheckerResult result) { // Force an indent by using two message contexts using (MessageContext outermessageContext = new MessageContext(EntityAccessControlService.MessageName)) { outermessageContext.Append(() => string.Format("Cache '{0}' results:", CacheName)); using (MessageContext innerMessageContext = new MessageContext(EntityAccessControlService.MessageName)) { if (result.CacheResult.Count > 0) { innerMessageContext.Append(() => string.Format( "Allowed: {0}", string.Join(", ", result.CacheResult.Where(kvp => kvp.Value) .Select(kvp => kvp.Key)))); innerMessageContext.Append(() => string.Format( "Denied: {0}", string.Join(", ", result.CacheResult.Where(kvp => !kvp.Value) .Select(kvp => kvp.Key)))); } else { innerMessageContext.Append(() => "No results found in the cache."); } } } }
/// <summary> /// Re-used cache checking logic that can be used for caching eithe instances lookups or type lookups. /// </summary> /// <param name="entities">The entities/types to check. This cannot be null or contain null.</param> /// <param name="permissions">The permissions or operations to check. This cannot be null or contain null.</param> /// <param name="user">The user requesting access. This cannot be null.</param> /// <param name="entityIdCallback">Callback used to determine how to get the ID of TEntity.</param> /// <param name="innerCheckAccessCallback">Callback used to perform the uncached check.</param> /// <returns>A mapping of each entity ID to whether the user has access (true) or not (false).</returns> private IDictionary <long, bool> CachedCheckImpl <TEntity>(IList <TEntity> entities, IList <EntityRef> permissions, EntityRef user, Func <TEntity, long> entityIdCallback, Func <IList <TEntity>, IList <EntityRef>, EntityRef, IDictionary <long, bool> > innerCheckAccessCallback) { TKey cacheKey; CachingEntityAccessControlCheckerResult result; IList <TEntity> toCheckEntities; long[] permissionIdArray; IDictionary <long, bool> innerResult; // If SecurityBypassContext is active, avoid the cache. Otherwise, // the cache will remember the user had access to entities that // they may not have. if (EntityAccessControlChecker.SkipCheck(user)) { innerResult = innerCheckAccessCallback(entities, permissions, user); return(innerResult); } result = new CachingEntityAccessControlCheckerResult(); toCheckEntities = null; permissionIdArray = permissions.Select(x => x.Id).ToArray(); // Determine uncached entities using (CacheContext cacheContext = CacheContext.IsSet( ) ? CacheContext.GetContext( ) : null) { foreach (TEntity entity in entities) { long entityId = entityIdCallback(entity); cacheKey = CreateKey(user.Id, entityId, permissionIdArray); bool cacheEntry; if (Cache.TryGetValue(cacheKey, out cacheEntry)) { result.CacheResult[entityId] = cacheEntry; // Add the already stored changes that should invalidate this cache // entry to any outer or containing cache contexts. if (cacheContext != null) { cacheContext.AddInvalidationsFor(_cacheInvalidator, cacheKey); } } else { if (toCheckEntities == null) { toCheckEntities = new List <TEntity>(); } toCheckEntities.Add(entity); } } } LogMessage(result); if (toCheckEntities != null) { using (CacheContext cacheContext = new CacheContext( )) { innerResult = innerCheckAccessCallback(toCheckEntities, permissions, user); foreach (KeyValuePair <long, bool> entry in innerResult) { long entityId = entry.Key; bool accessGranted = entry.Value; result.Add(entityId, accessGranted); // Cache the results cacheKey = CreateKey(user.Id, entry.Key, permissionIdArray); Cache [cacheKey] = accessGranted; // Add the cache context entries to the appropriate CacheInvalidator _cacheInvalidator.AddInvalidations(cacheContext, cacheKey); } } } // Add the results from the originally cached entities foreach (KeyValuePair <long, bool> entry in result.CacheResult) { result[entry.Key] = entry.Value; } return(result); }