예제 #1
0
 /// <summary>
 /// Tries to read a value from local cache. If it is not found there, tries the distributed cache.
 /// If neither contains the specified key, produces value by calling a loader function and adds the
 /// value to local and distributed cache for a given expiration time. By using a group key,
 /// all items on both cache types that are members of this group can be expired at once. </summary>
 /// <remarks>
 /// To not check group generation every time an item is requested, generation number itself is also
 /// cached in local cache. Thus, when a generation number changes, local cached items might expire
 /// after about 5 seconds. This means that, if you use this strategy in a web farm setup, when a change
 /// occurs in one server, other servers might continue to use old local cached data for 5 seconds more.
 /// If this is a problem for your configuration, use DistributedCache directly.
 /// </remarks>
 /// <typeparam name="TItem">Data type</typeparam>
 /// <param name="cache">Two level cache</param>
 /// <param name="cacheKey">The item key for local and distributed cache</param>
 /// <param name="localExpiration">Local expiration</param>
 /// <param name="remoteExpiration">Distributed cache expiration (is usually same with local expiration)</param>
 /// <param name="groupKey">Group key that will hold generation (version). Can be used to expire all items
 /// that depend on it. This can be a table name. When a table changes, you change its version, and all
 /// cached data that depends on that table is expired.</param>
 /// <param name="loader">The delegate that will be called to generate value, if not found in local cache,
 /// or distributed cache, or all found items are expired.</param>
 public static TItem Get <TItem>(this ITwoLevelCache cache, string cacheKey, TimeSpan localExpiration, TimeSpan remoteExpiration,
                                 string groupKey, Func <TItem> loader)
     where TItem : class
 {
     return(GetInternal(cache, cacheKey, localExpiration, remoteExpiration,
                        groupKey, loader, localOnly: false));
 }
예제 #2
0
 /// <summary>
 /// Tries to read a value from local cache. If it is not found there produces value by calling a loader
 /// function and adds the value to local cache for a given expiration time. By using a generation
 /// (item version) key, all items on local cache that are members of this group can be expired
 /// at once. </summary>
 /// <remarks>
 /// The difference between this and Get method is that this one only caches items in local cache, but
 /// uses distributed cache for versioning. To not check group generation every time an item is requested,
 /// generation number itself is also cached in local cache. Thus, when a generation number changes, local
 /// cached items might expire after about 5 seconds. This means that, if you use this strategy in a web farm
 /// setup, when a change occurs in one server, other servers might continue to use old local cached data for
 /// 5 seconds more. If this is a problem for your configuration, use DistributedCache directly.
 /// </remarks>
 /// <typeparam name="TItem">Data type</typeparam>
 /// <param name="cache">Two level cache</param>
 /// <param name="cacheKey">The item key for local and distributed cache</param>
 /// <param name="localExpiration">Local expiration</param>
 /// <param name="groupKey">Group key that will hold generation (version). Can be used to expire all items
 /// that depend on it. This can be a table name. When a table changes, you change its version, and all
 /// cached data that depends on that table is expired.</param>
 /// <param name="loader">The delegate that will be called to generate value, if not found in local cache,
 /// or distributed cache, or all found items are expired.</param>
 public static TItem GetLocalStoreOnly <TItem>(this ITwoLevelCache cache, string cacheKey, TimeSpan localExpiration,
                                               string groupKey, Func <TItem> loader)
     where TItem : class
 {
     return(GetInternal(cache, cacheKey, localExpiration, TimeSpan.FromSeconds(0),
                        groupKey, loader, localOnly: true));
 }
예제 #3
0
        public ActionResult Index(
            [FromServices] ITwoLevelCache cache,
            [FromServices] ISqlConnections sqlConnections
            )
        {
            if (cache is null)
            {
                throw new ArgumentNullException(nameof(cache));
            }

            if (sqlConnections is null)
            {
                throw new ArgumentNullException(nameof(sqlConnections));
            }

            var cachedModel = cache.GetLocalStoreOnly("DashboardPageModel", TimeSpan.FromMinutes(5),
                                                      OrderRow.Fields.GenerationKey, () =>
            {
                var model = new DashboardPageModel();
                var o     = OrderRow.Fields;
                using (var connection = sqlConnections.NewFor <OrderRow>())
                {
                    model.OpenOrders         = connection.Count <OrderRow>(o.ShippingState == (int)OrderShippingState.NotShipped);
                    var closedOrders         = connection.Count <OrderRow>(o.ShippingState == (int)OrderShippingState.Shipped);
                    var totalOrders          = model.OpenOrders + closedOrders;
                    model.ClosedOrderPercent = (int)Math.Round(totalOrders == 0 ? 100 :
                                                               ((double)closedOrders / (double)totalOrders * 100));
                    model.CustomerCount = connection.Count <CustomerRow>();
                    model.ProductCount  = connection.Count <ProductRow>();
                }
                return(model);
            });

            return(View(MVC.Views.Common.Dashboard.DashboardIndex, cachedModel));
        }
 /// <summary>
 /// Creates or overrides a specified entry in the local cache.
 /// </summary>
 /// <typeparam name="TItem">Data type</typeparam>
 /// <param name="cache">Two level cache</param>
 /// <param name="cacheKey">The item key for local and distributed cache</param>
 /// <param name="localExpiration">Local expiration</param>
 /// <param name="groupKey">Group key that will hold generation (version). Can be used to expire all items
 /// that depend on it. This can be a table name. When a table changes, you change its version, and all
 /// cached data that depends on that table is expired.</param>
 /// <param name="value">Value to set.</param>
 public static TItem SetLocalStoreOnly <TItem>(this ITwoLevelCache cache, string cacheKey, TimeSpan localExpiration,
                                               string groupKey, TItem value)
     where TItem : class
 {
     return(GetInternal(cache, cacheKey, localExpiration, TimeSpan.FromSeconds(0),
                        groupKey, () => value, localOnly: true, forceReload: true));
 }
 /// <summary>
 /// Creates or overrides a specified entry in the local and distributed cache.
 /// </summary>
 /// <typeparam name="TItem">Data type</typeparam>
 /// <param name="cache">Two level cache</param>
 /// <param name="cacheKey">The item key for local and distributed cache</param>
 /// <param name="localExpiration">Local expiration</param>
 /// <param name="remoteExpiration">Distributed cache expiration (is usually same with local expiration)</param>
 /// <param name="groupKey">Group key that will hold generation (version). Can be used to expire all items
 /// that depend on it. This can be a table name. When a table changes, you change its version, and all
 /// cached data that depends on that table is expired.</param>
 /// <param name="value">Value to set.</param>
 public static TItem Set <TItem>(this ITwoLevelCache cache, string cacheKey, TimeSpan localExpiration, TimeSpan remoteExpiration,
                                 string groupKey, TItem value)
     where TItem : class
 {
     return(GetInternal(cache, cacheKey, localExpiration, remoteExpiration,
                        groupKey, () => value, localOnly: false, forceReload: true));
 }
예제 #6
0
        private static void ProcessTwoLevelCachedAttribute(ITwoLevelCache cache, IUnitOfWork uow, Type type)
        {
            if (type == null)
            {
                return;
            }

            var attr = type.GetCustomAttribute <TwoLevelCachedAttribute>(true);

            if (attr == null)
            {
                return;
            }

            if (attr.GenerationKeys != null)
            {
                foreach (var key in attr.GenerationKeys)
                {
                    InvalidateOnCommit(cache, uow, key);
                }
            }

            if (attr.LinkedRows != null)
            {
                foreach (var rowType in attr.LinkedRows)
                {
                    var rowInstance = (IRow)Activator.CreateInstance(rowType);
                    InvalidateOnCommit(cache, uow, rowInstance.GetFields().GenerationKey);
                }
            }
        }
 public PermissionService(ITwoLevelCache cache, ISqlConnections sqlConnections,
                          ITypeSource typeSource, IUserAccessor userAccessor)
 {
     Cache          = cache ?? throw new ArgumentNullException(nameof(cache));
     SqlConnections = sqlConnections ?? throw new ArgumentNullException(nameof(sqlConnections));
     TypeSource     = typeSource ?? throw new ArgumentNullException(nameof(typeSource));
     UserAccessor   = userAccessor ?? throw new ArgumentNullException(nameof(userAccessor));
 }
예제 #8
0
        /// <summary>
        /// Removes a key from local, distributed caches, and removes their generation version information.
        /// </summary>
        /// <param name="cache">Two level cache</param>
        /// <param name="cacheKey">Cache key</param>
        public static void Remove(this ITwoLevelCache cache, string cacheKey)
        {
            string itemGenerationKey = cacheKey + TwoLevelCache.GenerationSuffix;

            cache.Memory.Remove(cacheKey);
            cache.Memory.Remove(itemGenerationKey);
            cache.Distributed.Remove(cacheKey);
            cache.Distributed.Remove(itemGenerationKey);
        }
예제 #9
0
 public UserPasswordValidator(ITwoLevelCache cache, ISqlConnections sqlConnections, IUserRetrieveService userRetriever,
                              ILogger <UserPasswordValidator> log = null, IDirectoryService directoryService = null)
 {
     Cache            = cache ?? throw new ArgumentNullException(nameof(cache));
     SqlConnections   = sqlConnections ?? throw new ArgumentNullException(nameof(sqlConnections));
     UserRetriever    = userRetriever ?? throw new ArgumentNullException(nameof(userRetriever));
     DirectoryService = directoryService;
     Log = log;
 }
예제 #10
0
 public DefaultRequestContext(IBehaviorProvider behaviors, ITwoLevelCache cache, ITextLocalizer localizer,
                              IPermissionService permissions, IUserAccessor userAccessor)
 {
     Behaviors         = behaviors ?? throw new ArgumentNullException(nameof(behaviors));
     Cache             = cache ?? throw new ArgumentNullException(nameof(cache));
     Localizer         = localizer ?? throw new ArgumentNullException(nameof(localizer));
     Permissions       = permissions ?? throw new ArgumentNullException(nameof(permissions));
     this.userAccessor = userAccessor ?? throw new ArgumentNullException(nameof(userAccessor));
 }
예제 #11
0
 public UserDataScript(ITwoLevelCache cache, IPermissionService permissions,
                       ITypeSource typeSource, IUserAccessor userAccessor, IUserRetrieveService userRetrieveService)
 {
     Cache         = cache ?? throw new ArgumentNullException(nameof(cache));
     Permissions   = permissions ?? throw new ArgumentNullException(nameof(permissions));
     TypeSource    = typeSource ?? throw new ArgumentNullException(nameof(typeSource));
     UserAccessor  = userAccessor ?? throw new ArgumentNullException(nameof(userAccessor));
     UserRetriever = userRetrieveService ?? throw new ArgumentNullException(nameof(userRetrieveService));
 }
예제 #12
0
 public DynamicScriptManager(ITwoLevelCache cache, IPermissionService permissions, ITextLocalizer localizer)
 {
     this.cache        = cache ?? throw new ArgumentNullException(nameof(cache));
     this.permissions  = permissions ?? throw new ArgumentNullException(nameof(permissions));
     this.localizer    = localizer;
     registeredScripts = new ConcurrentDictionary <string, IDynamicScript>(StringComparer.OrdinalIgnoreCase);
     scriptLastChange  = new ConcurrentDictionary <string, DateTime>();
     Register(new RegisteredScripts(this));
 }
예제 #13
0
        public static void InvalidateOnCommit(this ITwoLevelCache cache, IUnitOfWork uow, IRow row)
        {
            if (row is null)
            {
                throw new ArgumentNullException(nameof(row));
            }

            InvalidateOnCommit(cache, uow, row.GetFields().GenerationKey);
            ProcessTwoLevelCachedAttribute(cache, uow, row.GetType());
        }
예제 #14
0
        public static void RemoveCachedUser(ITwoLevelCache cache, int?userId, string username)
        {
            if (userId != null)
            {
                cache.Remove("UserByID_" + userId);
            }

            if (username != null)
            {
                cache.Remove("UserByName_" + username.ToLowerInvariant());
            }
        }
예제 #15
0
 public NullRequestContext(IBehaviorProvider behaviors    = null,
                           ITwoLevelCache cache           = null,
                           ITextLocalizer localizer       = null,
                           IPermissionService permissions = null,
                           IUserAccessor userAccessor     = null)
 {
     Behaviors    = behaviors ?? new NullBehaviorProvider();
     Cache        = cache ?? new NullTwoLevelCache();
     Localizer    = localizer ?? NullTextLocalizer.Instance;
     Permissions  = permissions ?? new NullPermissions();
     UserAccessor = userAccessor ?? new NullUserAccessor();
 }
예제 #16
0
        public static void InvalidateOnCommit(this ITwoLevelCache cache, IUnitOfWork uow, string groupKey)
        {
            if (groupKey.IsNullOrEmpty())
            {
                throw new ArgumentNullException("generationKey");
            }

            var updater = cache.Memory.Get("BatchGenerationUpdater:UpdaterInstance", TimeSpan.Zero,
                                           () => new GenerationUpdater(cache));

            updater.Add(groupKey);

            uow.OnCommit -= updater.Update;
            uow.OnCommit += updater.Update;
        }
예제 #17
0
            private UserDefinition GetUserByName(ITwoLevelCache cache, string username, ref int loadCount)
            {
                var l      = loadCount;
                var result = cache.Get("UserByName_" + username.ToLowerInvariant(),
                                       TimeSpan.Zero, TimeSpan.FromDays(1), "UserGenerationKey", () =>
                {
                    l++;
                    return(new UserDefinition {
                        UserId = 1, Username = username
                    });
                });

                loadCount = l;
                return(result);
            }
예제 #18
0
        public static void InvalidateOnCommit(this ITwoLevelCache cache, IUnitOfWork uow, RowFieldsBase fields)
        {
            if (fields is null)
            {
                throw new ArgumentNullException(nameof(fields));
            }

            InvalidateOnCommit(cache, uow, fields.GenerationKey);

            var fieldsType = fields.GetType();

            if (fieldsType.IsNested && fieldsType.DeclaringType != null)
            {
                ProcessTwoLevelCachedAttribute(cache, uow, fieldsType.DeclaringType);
            }
        }
예제 #19
0
 public NavigationModelFactory(
     ITwoLevelCache cache,
     IHttpContextAccessor httpContextAccessor,
     ITextLocalizer localizer,
     IPermissionService permissions,
     IServiceProvider serviceProvider,
     ITypeSource typeSource,
     IUserAccessor userAccessor)
 {
     this.httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
     this.localizer           = localizer ?? throw new ArgumentNullException(nameof(localizer));
     this.cache           = cache ?? throw new ArgumentNullException(nameof(cache));
     this.permissions     = permissions ?? throw new ArgumentNullException(nameof(permissions));
     this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
     this.typeSource      = typeSource ?? throw new ArgumentNullException(nameof(typeSource));
     this.userAccessor    = userAccessor ?? throw new ArgumentNullException(nameof(userAccessor));
 }
예제 #20
0
        public NavigationModel(ITwoLevelCache cache, IPermissionService permissions,
                               ITypeSource typeSource, IServiceProvider services, ClaimsPrincipal user,
                               string requestUrl, PathString pathBase)
        {
            if (cache is null)
            {
                throw new ArgumentNullException(nameof(cache));
            }

            Items = cache.GetLocalStoreOnly("LeftNavigationModel:NavigationItems:" +
                                            (user?.GetIdentifier() ?? "-1"), TimeSpan.Zero,
                                            UserPermissionRow.Fields.GenerationKey, () =>
                                            NavigationHelper.GetNavigationItems(permissions, typeSource,
                                                                                services, x => x != null && x.StartsWith("~/") ?
                                                                                VirtualPathUtility.ToAbsolute(PathBase, x) : x));

            RequestUrl = requestUrl;
            PathBase   = pathBase;
            SetActivePath();
        }
예제 #21
0
        public ActionResult Index(
            [FromServices] ITwoLevelCache cache,
            [FromServices] ISqlConnections sqlConnections
            )
        {
            if (cache is null)
            {
                throw new ArgumentNullException(nameof(cache));
            }

            if (sqlConnections is null)
            {
                throw new ArgumentNullException(nameof(sqlConnections));
            }

            var cachedModel = new DashboardPageModel()
            {
            };

            return(View(MVC.Views.Common.Dashboard.DashboardIndex, cachedModel));
        }
예제 #22
0
 public EmployeeListDecorator(ITwoLevelCache cache, ISqlConnections sqlConnections)
 {
     Cache          = cache ?? throw new ArgumentNullException(nameof(cache));
     SqlConnections = sqlConnections ?? throw new ArgumentNullException(nameof(sqlConnections));
 }
예제 #23
0
        private static TItem GetInternal <TItem>(ITwoLevelCache cache, string cacheKey,
                                                 TimeSpan localExpiration, TimeSpan remoteExpiration,
                                                 string groupKey, Func <TItem> loader, bool localOnly)
            where TItem : class
        {
            ulong?groupGeneration      = null;
            ulong?groupGenerationCache = null;

            string itemGenerationKey = cacheKey + TwoLevelCache.GenerationSuffix;

            var localCache       = cache.Memory;
            var distributedCache = cache.Distributed;

            // retrieves distributed cache group generation number lazily
            ulong getGroupGenerationValue()
            {
                if (groupGeneration != null)
                {
                    return(groupGeneration.Value);
                }

                var bytes = distributedCache.Get(groupKey);

                groupGeneration = (bytes == null || bytes.Length != 8) ? (ulong?)null : BitConverter.ToUInt64(bytes);
                if (groupGeneration == null || groupGeneration == 0)
                {
                    groupGeneration = RandomGeneration();
                    distributedCache.Set(groupKey, BitConverter.GetBytes(groupGeneration.Value));
                }

                groupGenerationCache = groupGeneration.Value;
                // add to local cache, use 5 seconds from there
                localCache.Add(groupKey, groupGenerationCache, TwoLevelCache.GenerationCacheExpiration);

                return(groupGeneration.Value);
            }

            // retrieves local cache group generation number lazily
            ulong getGroupGenerationCacheValue()
            {
                if (groupGenerationCache != null)
                {
                    return(groupGenerationCache.Value);
                }

                // check cached local value of group key
                // it expires in 5 seconds and read from server again
                groupGenerationCache = localCache.Get <object>(groupKey) as ulong?;

                // if its in local cache, return it
                if (groupGenerationCache != null)
                {
                    return(groupGenerationCache.Value);
                }

                return(getGroupGenerationValue());
            }

            // first check local cache, if item exists and not expired (group version = item version) return it
            var cachedObj = localCache.Get <object>(cacheKey);

            if (cachedObj != null)
            {
                // check local cache, if exists, compare version with group one
                var itemGenerationCache = localCache.Get <object>(itemGenerationKey) as ulong?;
                if (itemGenerationCache != null &&
                    itemGenerationCache == getGroupGenerationCacheValue())
                {
                    // local cached item is not expired yet

                    if (cachedObj == DBNull.Value)
                    {
                        return(null);
                    }

                    return((TItem)cachedObj);
                }

                // local cached item is expired, remove all information
                if (itemGenerationCache != null)
                {
                    localCache.Remove(itemGenerationKey);
                }

                localCache.Remove(cacheKey);
            }

            if (!localOnly)
            {
                // no item in local cache or expired, now check distributed cache
                var bytes          = distributedCache.Get(itemGenerationKey);
                var itemGeneration = (bytes == null || bytes.Length != 8) ? (ulong?)null : BitConverter.ToUInt64(bytes);

                // if item has version number in distributed cache and this is equal to group version
                if (itemGeneration != null &&
                    itemGeneration.Value == getGroupGenerationValue())
                {
                    // get item from distributed cache
                    cachedObj = distributedCache.GetAutoJson <TItem>(cacheKey);
                    // if item exists in distributed cache
                    if (cachedObj != null)
                    {
                        localCache.Add(cacheKey, cachedObj, localExpiration);
                        localCache.Add(itemGenerationKey, getGroupGenerationValue(), localExpiration);
                        return((TItem)cachedObj);
                    }
                }
            }

            if (loader == null)
            {
                return(null);
            }

            // couldn't find valid item in local or distributed cache, produce value by calling loader
            var item = loader();

            // add item and its version to cache
            localCache.Add(cacheKey, (object)item ?? DBNull.Value, localExpiration);
            localCache.Add(itemGenerationKey, getGroupGenerationValue(), localExpiration);

            if (!localOnly)
            {
                // add item and generation to distributed cache
                if (remoteExpiration == TimeSpan.Zero)
                {
                    distributedCache.SetAutoJson(cacheKey, item);
                    distributedCache.Set(itemGenerationKey, BitConverter.GetBytes(getGroupGenerationValue()));
                }
                else if (remoteExpiration < TimeSpan.Zero)
                {
                    distributedCache.Remove(cacheKey);
                    distributedCache.Remove(itemGenerationKey);
                }
                else
                {
                    distributedCache.SetAutoJson(cacheKey, item, remoteExpiration);
                    distributedCache.Set(itemGenerationKey, BitConverter.GetBytes(getGroupGenerationValue()), new DistributedCacheEntryOptions
                    {
                        AbsoluteExpirationRelativeToNow = remoteExpiration
                    });
                }
            }

            return(item);
        }
예제 #24
0
 /// <summary>Changes a group generation value, so that all items that depend on it are expired.</summary>
 /// <param name="cache">Two level cache</param>
 /// <param name="groupKey">Group key</param>
 public static void ExpireGroupItems(this ITwoLevelCache cache, string groupKey)
 {
     cache.Memory.Remove(groupKey);
     cache.Distributed.Remove(groupKey);
 }
예제 #25
0
 public GenerationUpdater(ITwoLevelCache cache)
 {
     groupKeys  = new HashSet <string>();
     this.cache = cache ?? throw new ArgumentNullException(nameof(cache));
 }
예제 #26
0
 public AccountController(ITwoLevelCache cache, ITextLocalizer localizer)
 {
     Cache     = cache ?? throw new ArgumentNullException(nameof(cache));
     Localizer = localizer ?? throw new ArgumentNullException(nameof(localizer));
 }
예제 #27
0
 public UserRetrieveService(ITwoLevelCache cache, ISqlConnections sqlConnections)
 {
     Cache          = cache;
     SqlConnections = sqlConnections;
 }