// //==================================================================================================== /// <summary> /// save cacheDocument to memory cache /// </summary> /// <param name="serverKey">key converted to serverKey with app name and code version</param> /// <param name="cacheDocument"></param> public void storeCacheDocument_MemoryCache(string serverKey, CacheDocumentClass cacheDocument) { ObjectCache cache = MemoryCache.Default; CacheItemPolicy policy = new CacheItemPolicy { AbsoluteExpiration = cacheDocument.invalidationDate // core.dateTimeMockable.AddMinutes(100); }; cache.Set(serverKey, cacheDocument, policy); }
// //==================================================================================================== /// <summary> /// save object directly to cache. /// </summary> /// <param name="key"></param> /// <param name="cacheDocument">Either a string, a date, or a serializable object</param> /// <param name="invalidationDate"></param> /// <remarks></remarks> private void storeCacheDocument(string key, CacheDocumentClass cacheDocument) { try { // if (string.IsNullOrEmpty(key)) { throw new ArgumentException("cache key cannot be blank"); } string typeMessage = ""; string serverKey = createServerKey(key); if (core.serverConfig.enableLocalMemoryCache) { // // -- save local memory cache typeMessage = "local-memory"; storeCacheDocument_MemoryCache(serverKey, cacheDocument); } if (core.serverConfig.enableLocalFileCache) { // // -- save local file cache typeMessage = "local-file"; string serializedData = SerializeObject(cacheDocument); using (System.Threading.Mutex mutex = new System.Threading.Mutex(false, serverKey)) { mutex.WaitOne(); core.privateFiles.saveFile("appCache\\" + FileController.encodeDosFilename(serverKey + ".txt"), serializedData); mutex.ReleaseMutex(); } } if (core.serverConfig.enableRemoteCache) { typeMessage = "remote"; if (remoteCacheInitialized) { // // -- save remote cache if (!cacheClient.Store(Enyim.Caching.Memcached.StoreMode.Set, serverKey, cacheDocument, cacheDocument.invalidationDate)) { // // -- store failed LogController.logError(core, "Enyim cacheClient.Store failed, no details available."); } } } // LogController.logTrace(core, "cacheType [" + typeMessage + "], key [" + key + "], expires [" + cacheDocument.invalidationDate + "], depends on [" + string.Join(",", cacheDocument.dependentKeyList) + "], points to [" + string.Join(",", cacheDocument.keyPtr) + "]"); // } catch (Exception ex) { LogController.logError(core, ex); } }
// //==================================================================================================== /// <summary> /// set a key ptr. A ptr points to a normal key, creating an altername way to get/invalidate a cache. /// ex - image with id=10, guid={999}. The normal key="image/id/10", the alias Key="image/ccguid/{9999}" /// </summary> /// <param name="CP"></param> /// <param name="keyPtr"></param> /// <param name="data"></param> /// <remarks></remarks> public void storePtr(string keyPtr, string key) { try { keyPtr = Regex.Replace(keyPtr, "0x[a-fA-F\\d]{2}", "_").ToLowerInvariant().Replace(" ", "_"); key = Regex.Replace(key, "0x[a-fA-F\\d]{2}", "_").ToLowerInvariant().Replace(" ", "_"); CacheDocumentClass cacheDocument = new CacheDocumentClass(core.dateTimeNowMockable) { saveDate = core.dateTimeNowMockable, invalidationDate = core.dateTimeNowMockable.AddDays(invalidationDaysDefault), keyPtr = key }; storeCacheDocument(keyPtr, cacheDocument); } catch (Exception ex) { LogController.logError(core, ex); } }
// //==================================================================================================== /// <summary> /// save an object to cache, with invalidation date and dependentKeyList /// /// </summary> /// <param name="key"></param> /// <param name="content"></param> /// <param name="invalidationDate"></param> /// <param name="dependentKeyList">Each tag should represent the source of data, and should be invalidated when that source changes.</param> /// <remarks></remarks> public void storeObject(string key, object content, DateTime invalidationDate, List <string> dependentKeyList) { try { key = Regex.Replace(key, "0x[a-fA-F\\d]{2}", "_").ToLowerInvariant().Replace(" ", "_"); var cacheDocument = new CacheDocumentClass(core.dateTimeNowMockable) { content = content, saveDate = core.dateTimeNowMockable, invalidationDate = invalidationDate, dependentKeyList = dependentKeyList }; storeCacheDocument(key, cacheDocument); } catch (Exception ex) { LogController.logError(core, ex); } }
// //==================================================================================================== // <summary> // invalidates a tag // </summary> // <param name="tag"></param> // <remarks></remarks> public void invalidate(string key, int recursionLimit = 5) { try { Controllers.LogController.logTrace(core, "invalidate, key [" + key + "], recursionLimit [" + recursionLimit + "]"); if ((recursionLimit > 0) && (!string.IsNullOrWhiteSpace(key.Trim()))) { key = Regex.Replace(key, "0x[a-fA-F\\d]{2}", "_").ToLowerInvariant().Replace(" ", "_"); // if key is a ptr, we need to invalidate the real key CacheDocumentClass cacheDocument = getCacheDocument(key); if (cacheDocument == null) { // no cache for this key, if this is a dependency for another key, save invalidated storeCacheDocument(key, new CacheDocumentClass(core.dateTimeNowMockable) { saveDate = core.dateTimeNowMockable }); } else { if (!string.IsNullOrWhiteSpace(cacheDocument.keyPtr)) { // this key is an alias, invalidate it's parent key invalidate(cacheDocument.keyPtr, --recursionLimit); } else { // key is a valid cache, invalidate it storeCacheDocument(key, new CacheDocumentClass(core.dateTimeNowMockable) { saveDate = core.dateTimeNowMockable }); } } } } catch (Exception ex) { LogController.logError(core, ex); throw; } }
// //==================================================================================================== /// <summary> /// get a cache object from the cache. returns the cacheObject that wraps the object /// </summary> /// <typeparam name="returnType"></typeparam> /// <param name="key"></param> /// <returns></returns> private CacheDocumentClass getCacheDocument(string key) { CacheDocumentClass result = null; try { // - verified in createServerKey() -- key = Regex.Replace(key, "0x[a-fA-F\\d]{2}", "_").ToLowerInvariant().Replace(" ", "_"); if (string.IsNullOrEmpty(key)) { throw new ArgumentException("cache key cannot be blank"); } string serverKey = createServerKey(key); string typeMessage = ""; if (remoteCacheInitialized) { // // -- use remote cache typeMessage = "remote"; try { result = cacheClient.Get <CacheDocumentClass>(serverKey); } catch (Exception ex) { // // --client does not throw its own errors, so try to differentiate by message if (ex.Message.ToLowerInvariant().IndexOf("unable to load type") >= 0) { // // -- trying to deserialize an object and this code does not have a matching class, clear cache and return empty LogController.logWarn(core, ex); cacheClient.Remove(serverKey); result = null; } else { // // -- some other error LogController.logError(core, ex); throw; } } } if ((result == null) && core.serverConfig.enableLocalMemoryCache) { // // -- local memory cache typeMessage = "local-memory"; result = (CacheDocumentClass)MemoryCache.Default[serverKey]; } if ((result == null) && core.serverConfig.enableLocalFileCache) { // // -- local file cache typeMessage = "local-file"; string serializedDataObject = null; using (System.Threading.Mutex mutex = new System.Threading.Mutex(false, serverKey)) { mutex.WaitOne(); serializedDataObject = core.privateFiles.readFileText("appCache\\" + FileController.encodeDosFilename(serverKey + ".txt")); mutex.ReleaseMutex(); } if (!string.IsNullOrEmpty(serializedDataObject)) { result = DeserializeObject <CacheDocumentClass>(serializedDataObject); storeCacheDocument_MemoryCache(serverKey, result); } } string returnContentSegment = SerializeObject(result); returnContentSegment = (returnContentSegment.Length > 50) ? returnContentSegment.Substring(0, 50) : returnContentSegment; // // -- log result if (result == null) { LogController.logTrace(core, "miss, cacheType [" + typeMessage + "], key [" + key + "]"); } else { if (result.content == null) { LogController.logTrace(core, "hit, cacheType [" + typeMessage + "], key [" + key + "], saveDate [" + result.saveDate + "], content [null]"); } else { string content = result.content.ToString(); content = (content.Length > 50) ? (content.left(50) + "...") : content; LogController.logTrace(core, "hit, cacheType [" + typeMessage + "], key [" + key + "], saveDate [" + result.saveDate + "], content [" + content + "]"); } } // // if dependentKeyList is null, return an empty list, not null if (result != null) { // // -- empty objects return nothing, empty lists return count=0 if (result.dependentKeyList == null) { result.dependentKeyList = new List <string>(); } } } catch (Exception ex) { LogController.logError(core, ex); throw; } return(result); }
// //======================================================================== /// <summary> /// get an object of type TData from cache. If the cache misses or is invalidated, null object is returned /// </summary> /// <typeparam name="TData"></typeparam> /// <param name="key"></param> /// <returns></returns> public TData getObject <TData>(string key) { try { key = Regex.Replace(key, "0x[a-fA-F\\d]{2}", "_").ToLowerInvariant().Replace(" ", "_"); if (string.IsNullOrEmpty(key)) { return(default(TData)); } // // -- read cacheDocument (the object that holds the data object plus control fields) CacheDocumentClass cacheDocument = getCacheDocument(key); if (cacheDocument == null) { return(default(TData)); } // // -- test for global invalidation int dateCompare = globalInvalidationDate.CompareTo(cacheDocument.saveDate); if (dateCompare >= 0) { // // -- global invalidation LogController.logTrace(core, "key [" + key + "], invalidated because cacheObject saveDate [" + cacheDocument.saveDate + "] is before the globalInvalidationDate [" + globalInvalidationDate + "]"); return(default(TData)); } // // -- test all dependent objects for invalidation (if they have changed since this object changed, it is invalid) bool cacheMiss = false; foreach (string dependentKey in cacheDocument.dependentKeyList) { CacheDocumentClass dependantCacheDocument = getCacheDocument(dependentKey); if (dependantCacheDocument == null) { // create dummy cache to validate future cache requests, fake saveDate as last globalinvalidationdate storeCacheDocument(dependentKey, new CacheDocumentClass(core.dateTimeNowMockable) { keyPtr = null, content = "", saveDate = globalInvalidationDate }); } else { dateCompare = dependantCacheDocument.saveDate.CompareTo(cacheDocument.saveDate); if (dateCompare >= 0) { // // -- invalidate because a dependent document was changed after the cacheDocument was saved cacheMiss = true; LogController.logTrace(core, "[" + key + "], invalidated because the dependantKey [" + dependentKey + "] was modified [" + dependantCacheDocument.saveDate + "] after the cacheDocument's saveDate [" + cacheDocument.saveDate + "]"); break; } } } TData result = default(TData); if (!cacheMiss) { if (!string.IsNullOrEmpty(cacheDocument.keyPtr)) { // // -- this is a pointer key, load the primary result = getObject <TData>(cacheDocument.keyPtr); } else if (cacheDocument.content is Newtonsoft.Json.Linq.JObject dataJObject) { // // -- newtonsoft types result = dataJObject.ToObject <TData>(); } else if (cacheDocument.content is Newtonsoft.Json.Linq.JArray dataJArray) { // // -- newtonsoft types result = dataJArray.ToObject <TData>(); } else if (cacheDocument.content == null) { // // -- if cache data was left as a string (might be empty), and return object is not string, there was an error result = default(TData); } else { // // -- all worked, but if the class is unavailable let it return default like a miss try { result = (TData)cacheDocument.content; } catch (Exception ex) { // // -- object value did not match. return as miss LogController.logWarn(core, "cache getObject failed to cast value as type, key [" + key + "], type requested [" + typeof(TData).FullName + "], ex [" + ex + "]"); result = default(TData); } } } return(result); } catch (Exception ex) { LogController.logError(core, ex); return(default(TData)); } }