/// <summary> /// Execute statement with MemoryCache /// </summary> /// <typeparam name="T"></typeparam> /// <param name="uid">The uid.</param> /// <param name="userId">The user identifier.</param> /// <param name="cacheLifeKey">The cache life key.</param> /// <param name="customHeaders">The custom headers.</param> /// <param name="action">The action.</param> /// <returns></returns> /// <exception cref="InvalidOperationException"> /// This method requires a cache; use the correct constructor /// or /// Incorrect expiration time parameter /// </exception> protected async Task<T> WithMemoryCacheExecute<T>(string uid, string userId, object cacheLifeKey, IHeaderDictionary customHeaders, Func<Task<T>> action, string uniqueIdCacheKey = null) where T : class, IMasterDataModel { if (_dataCache == null) throw new InvalidOperationException("This method requires a cache; use the correct constructor"); if (string.IsNullOrEmpty(uid) && string.IsNullOrEmpty(userId)) { log.LogWarning("Attempting to execute method with cache, but cannot generate a cache key - not caching the result."); var noCacheResult = await action.Invoke(); if (noCacheResult != null) return noCacheResult; } else { var opts = new MemoryCacheEntryOptions(); switch (cacheLifeKey) { case string s: opts.GetCacheOptions(s, configurationStore, log); break; case TimeSpan t: opts.SlidingExpiration = t; break; default: throw new InvalidOperationException("Incorrect expiration time parameter"); } var keyPrefix = typeof(T).Name; var cacheKey = string.IsNullOrEmpty(userId) ? $"{keyPrefix} {uid}" : $"{keyPrefix} {uid} {userId}"; // Allow for extra cache keys, eg page 1 or 2 would need a separate key if (!string.IsNullOrEmpty(uniqueIdCacheKey)) cacheKey = $"{cacheKey} {uniqueIdCacheKey}"; T result = default; using (await _memCacheLock.LockAsync(cacheKey)) { if (!IfCacheNeedsToBeInvalidated(customHeaders)) { return await _dataCache.GetOrCreate(cacheKey, async entry => { log.LogDebug($"{nameof(WithMemoryCacheExecute)}: Item for key {cacheKey} not found in cache, getting from web api"); result = await action.Invoke(); if (result != null) { entry.SetOptions(opts); // We need to support clearing cache by the user - the model doesn't know about the user info var identifiers = result.GetIdentifiers() ?? new List<string>(); if (!string.IsNullOrEmpty(uid)) identifiers.Add(uid); if (!string.IsNullOrEmpty(userId)) identifiers.Add(userId); return new CacheItem<T>(result, identifiers); } throw new ServiceException(HttpStatusCode.BadRequest, new ContractExecutionResult(ContractExecutionStatesEnum.FailedToGetResults, "Unable to request data from a webapi")); }); } log.LogDebug($"{nameof(WithMemoryCacheExecute)}: Item for key {cacheKey} is requested to be invalidated, getting from web api"); result = await action.Invoke(); if (result != null) { // We need to support clearing cache by the user - the model doesn't know about the user info var identifiers = result.GetIdentifiers() ?? new List<string>(); if (!string.IsNullOrEmpty(uid)) identifiers.Add(uid); if (!string.IsNullOrEmpty(userId)) identifiers.Add(userId); return _dataCache.Set(cacheKey, result, identifiers, opts); } } } throw new ServiceException(HttpStatusCode.BadRequest, new ContractExecutionResult(ContractExecutionStatesEnum.FailedToGetResults, "Unable to request data from a webapi")); }