public void SetUp() { _invocation.Method.Returns(typeof(DummyController).GetMethod(nameof(DummyController.Object))); _request = UnitTestHelper.GetMessage(); var discriptor = Substitute.For <ReflectedHttpActionDescriptor>(); discriptor.MethodInfo = _invocation.Method; discriptor.ReturnType.Returns(typeof(object)); _actionContext = new HttpActionContext( new HttpControllerContext( new HttpConfiguration(), Substitute.For <IHttpRouteData>(), _request), discriptor); _store = Substitute.For <IAsyncCacheStore>(); _store.StoreId.Returns(1000); _actionExecutedContext = new HttpActionExecutedContext(_actionContext, null); _actionExecutedContext.Request.Properties[Global.__flatwhite_outputcache_store] = _store; _actionExecutedContext.Request.Properties[Global.__flatwhite_outputcache_key] = CacheKey; Global.Init(); Global.CacheStoreProvider.RegisterAsyncStore(_store); }
/// <inheritdoc cref="IAsyncCachedQuery{TCacheEntryOptions}.UpdateCachedResult"/> public virtual Task UpdateCachedResult(IAsyncCacheStore <TCacheEntryOptions> cacheStore, CancellationToken cancellationToken) { if (cacheStore == null) { throw new ArgumentNullException(nameof(cacheStore)); } return(cacheStore.SetEntryAsync(State.CacheKey, State.CachedResult.ToCacheEntry(), State.CacheEntryOptions, cancellationToken)); }
/// <inheritdoc cref="IAsyncCachedQuery{TCacheEntryOptions}.EvictCachedResult"/> public virtual Task EvictCachedResult(IAsyncCacheStore <TCacheEntryOptions> cacheStore, CancellationToken cancellationToken) { if (cacheStore == null) { throw new ArgumentNullException(nameof(cacheStore)); } return(cacheStore.RemoveEntryAsync(State.CacheKey, cancellationToken)); }
/// <summary> /// Register the <see cref="IAsyncCacheStore" /> /// </summary> /// <param name="store"></param> /// <returns></returns> public void RegisterAsyncStore(IAsyncCacheStore store) { if (store == null) { throw new ArgumentNullException(nameof(store)); } if (store.StoreId > 0 && _cacheStore.ContainsKey(store.StoreId)) { throw new InvalidOperationException($"There is a registered ICacheStore with id {store.StoreId}: {_cacheStore[store.StoreId].GetType().Name}"); } _asyncCacheStore[store.StoreId] = store; _asyncCacheStoreTypes[store.GetType()] = store; }
/// <summary> /// Try to get the cache from etag and build the response if cache is available /// </summary> /// <param name="cacheControl"></param> /// <param name="request"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public async Task <HttpResponseMessage> HandleAsync(CacheControlHeaderValue cacheControl, HttpRequestMessage request, CancellationToken cancellationToken) { if (request.Headers.IfNoneMatch != null) { // Etag format: fw-StoreId-HashedKey-Checksum var requestEtags = request.Headers.IfNoneMatch.Where(t => t.Tag != null && t.Tag.StartsWith("\"fw-")).ToList(); if (requestEtags.Count > 0) { foreach (var etag in requestEtags) { var etagString = etag.Tag.Trim('"'); var index = etagString.IndexOf("-", 4, StringComparison.Ordinal); var storeIdString = index > 0 ? etagString.Substring(3, index - 3) : "0"; index = etagString.LastIndexOf("-", StringComparison.Ordinal); int storeId; IAsyncCacheStore cacheStore = null; if (int.TryParse(storeIdString, out storeId)) { cacheStore = Global.CacheStoreProvider.GetAsyncCacheStore(storeId) ?? Global.CacheStoreProvider.GetAsyncCacheStore(); } if (cacheStore != null && index > 0) { var hashedKey = etagString.Substring(0, index); var checkSum = etagString.Substring(index + 1); var cacheItem = (await cacheStore.GetAsync(hashedKey)) as WebApiCacheItem; if (cacheItem != null && cacheItem.Checksum == checkSum) { request.Properties[WebApiExtensions.__webApi_etag_matched] = true; } return(_builder.GetResponse(cacheControl, cacheItem, request)); } } } } return(null); }
private async Task <bool> AttemptToResponseTheCacheWhenError(HttpActionExecutedContext actionExecutedContext, IAsyncCacheStore cacheStore, string storedKey) { if (actionExecutedContext.ActionContext.Response == null || !actionExecutedContext.ActionContext.Response.IsSuccessStatusCode) { var cacheItem = await cacheStore.GetAsync(storedKey).ConfigureAwait(false) as WebApiCacheItem; if (cacheItem != null && StaleIfError > 0) { var builder = actionExecutedContext.Request.GetConfiguration().GetFlatwhiteCacheConfiguration().ResponseBuilder; var response = builder.GetResponse(actionExecutedContext.Request.Headers.CacheControl, cacheItem, actionExecutedContext.Request); if (response != null) { //NOTE: Override error response actionExecutedContext.Response = response; } return(true); } } return(false); }
private async Task SaveTheResultToCache(HttpActionExecutedContext actionExecutedContext, string storedKey, IAsyncCacheStore cacheStore) { var responseContent = actionExecutedContext.Response.Content; if (responseContent != null) { var cacheItem = new WebApiCacheItem { Key = storedKey, Content = await responseContent.ReadAsByteArrayAsync().ConfigureAwait(false), ResponseHeaders = responseContent.Headers, ResponseMediaType = responseContent.Headers.ContentType.MediaType, ResponseCharSet = responseContent.Headers.ContentType.CharSet, StoreId = cacheStore.StoreId, StaleWhileRevalidate = StaleWhileRevalidate, MaxAge = MaxAge, CreatedTime = DateTime.UtcNow, IgnoreRevalidationRequest = IgnoreRevalidationRequest, StaleIfError = StaleIfError, AutoRefresh = AutoRefresh, Path = $"{actionExecutedContext.Request.Method} {actionExecutedContext.Request.RequestUri.PathAndQuery}" }; var invocation = GetInvocation(actionExecutedContext.ActionContext); if (AutoRefresh) { // Create if not there DisposeOldPhoenixAndCreateNew(invocation, cacheItem, actionExecutedContext.Request); } if (StaleWhileRevalidate > 0) // Only auto refresh when revalidate if this value > 0, creating a Phoenix is optional { var context = GetInvocationContext(actionExecutedContext.ActionContext); var strategy = (ICacheStrategy)actionExecutedContext.Request.Properties[Global.__flatwhite_outputcache_strategy]; var changeMonitors = strategy.GetChangeMonitors(invocation, context); foreach (var mon in changeMonitors) { mon.CacheMonitorChanged += state => { TryToRefreshCacheWhileRevalidate(storedKey); }; } } actionExecutedContext.Response.Headers.ETag = new EntityTagHeaderValue($"\"{cacheItem.Key}-{cacheItem.Checksum}\""); var absoluteExpiration = DateTime.UtcNow.AddSeconds(MaxAge + Math.Max(StaleWhileRevalidate, StaleIfError)); await Task.WhenAll(new[] { //Save url base cache key that can map to the real cache key which will be used by EvaluateServerCacheHandler cacheStore.SetAsync(actionExecutedContext.Request.GetUrlBaseCacheKey(), cacheItem.Key, absoluteExpiration), //Save the actual cache cacheStore.SetAsync(cacheItem.Key, cacheItem, absoluteExpiration) }).ConfigureAwait(false); } }
/// <inheritdoc cref="IAsyncCachedQuery{TContext,TCacheEntryOptions,TResult}.Execute"/> public virtual async Task <TTransformedResult> Execute(TContext context, IAsyncCacheStore <TCacheEntryOptions> cacheStore, CacheOption cacheOption, CancellationToken cancellationToken) { var cachedResult = await GetCachedResult(context, cacheStore, cacheOption, cancellationToken).ConfigureAwait(false); return(await TransformCachedResult(cachedResult, cancellationToken).ConfigureAwait(false)); }
/// <inheritdoc cref="IAsyncCachedQuery{TContext,TCacheEntryOptions,TResult}.Execute"/> public virtual Task <TResult> Execute(TContext context, IAsyncCacheStore <TCacheEntryOptions> cacheStore, CacheOption cacheOption, CancellationToken cancellationToken) => GetCachedResult(context, cacheStore, cacheOption, cancellationToken);
/// <inheritdoc cref="IAsyncCachedQuery{TContext,TCacheEntryOptions,TResult}.Execute"/> protected virtual async Task <TCachedResult> GetCachedResult(TContext context, IAsyncCacheStore <TCacheEntryOptions> cacheStore, CacheOption cacheOption, CancellationToken cancellationToken) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (cacheStore == null) { throw new ArgumentNullException(nameof(cacheStore)); } State.SetContext(context); if (cacheOption == CacheOption.Default) { var cacheEntry = await cacheStore.GetEntryAsync <TCachedResult>(State.CacheKey, cancellationToken).ConfigureAwait(false); if (cacheEntry != null) { return(State.SetCachedResult(cacheEntry.Value)); } } var result = await Query(context, cancellationToken).ConfigureAwait(false); await cacheStore.SetEntryAsync(State.CacheKey, result.ToCacheEntry(), State.CacheEntryOptions, cancellationToken).ConfigureAwait(false); return(State.SetCachedResult(result)); }