public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) { if (actionExecutedContext.Response != null && !actionExecutedContext.Response.IsSuccessStatusCode) { return; } if (actionExecutedContext.ActionContext.Request.Method != HttpMethod.Post && actionExecutedContext.ActionContext.Request.Method != HttpMethod.Put && actionExecutedContext.ActionContext.Request.Method != HttpMethod.Delete) { return; } var controller = actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor; var actions = FindAllGetMethods(controller.ControllerType, TryMatchType ? actionExecutedContext.ActionContext.ActionDescriptor.GetParameters() : null); var config = actionExecutedContext.ActionContext.Request.GetConfiguration(); EnsureCache(config, actionExecutedContext.ActionContext.Request); foreach (var action in actions) { var key = config.CacheOutputConfiguration().MakeBaseCachekey(controller.ControllerName, action); if (WebApiCache.Contains(key)) { WebApiCache.RemoveStartsWith(key); } } }
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) { if (actionExecutedContext.ActionContext.Response == null || !actionExecutedContext.ActionContext.Response.IsSuccessStatusCode) { return; } if (!_isCachingAllowed(actionExecutedContext.ActionContext, AnonymousOnly)) { return; } var cacheTime = CacheTimeQuery.Execute(DateTime.Now); if (cacheTime.AbsoluteExpiration > DateTime.Now) { var cachekey = MakeCachekey(actionExecutedContext.ActionContext, _responseMediaType, ExcludeQueryStringFromCacheKey); if (!string.IsNullOrWhiteSpace(cachekey) && !(WebApiCache.Contains(cachekey))) { var eTag = CreateEtag(actionExecutedContext, cachekey, cacheTime); SetEtag(actionExecutedContext.Response, eTag); if (actionExecutedContext.Response.Content != null) { var realResponse = actionExecutedContext.Response; realResponse.Content.ReadAsByteArrayAsync().ContinueWith(t => { var baseKey = actionExecutedContext.Request.GetConfiguration().CacheOutputConfiguration().MakeBaseCachekey(actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerName, actionExecutedContext.ActionContext.ActionDescriptor.ActionName); WebApiCache.Add(baseKey, string.Empty, cacheTime.AbsoluteExpiration); WebApiCache.Add(cachekey, t.Result, cacheTime.AbsoluteExpiration, baseKey); WebApiCache.Add(cachekey + Constants.ContentTypeKey, realResponse.Content.Headers.ContentType, cacheTime.AbsoluteExpiration, baseKey); WebApiCache.Add(cachekey + Constants.EtagKey, realResponse.Headers.ETag, cacheTime.AbsoluteExpiration, baseKey); }); } var quotedEtag = "\"" + eTag + "\""; if (actionExecutedContext.ActionContext.Request.Headers.IfNoneMatch.Any(x => x.Tag == quotedEtag)) { var time = CacheTimeQuery.Execute(DateTime.Now); var quickResponse = actionExecutedContext.ActionContext.Request.CreateResponse(HttpStatusCode.NotModified); ApplyCacheHeaders(quickResponse, time); actionExecutedContext.ActionContext.Response = quickResponse; return; } } } ApplyCacheHeaders(actionExecutedContext.ActionContext.Response, cacheTime); }
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) { if (actionExecutedContext.ActionContext.Response == null || !actionExecutedContext.ActionContext.Response.IsSuccessStatusCode) { return; } if (!_isCachingAllowed(actionExecutedContext.ActionContext, AnonymousOnly)) { return; } var cacheTime = CacheTimeQuery.Execute(DateTime.Now); if (cacheTime.AbsoluteExpiration > DateTime.Now) { var config = actionExecutedContext.Request.GetConfiguration().CacheOutputConfiguration(); var cacheKeyGenerator = config.GetCacheKeyGenerator(actionExecutedContext.Request, CacheKeyGenerator); var cachekey = cacheKeyGenerator.MakeCacheKey(actionExecutedContext.ActionContext, _responseMediaType, ExcludeQueryStringFromCacheKey); if (!string.IsNullOrWhiteSpace(cachekey) && !(WebApiCache.Contains(cachekey))) { SetEtag(actionExecutedContext.Response, Guid.NewGuid().ToString()); if (actionExecutedContext.Response.Content != null) { actionExecutedContext.Response.Content.ReadAsByteArrayAsync().ContinueWith(t => { var baseKey = config.MakeBaseCachekey(actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerName, actionExecutedContext.ActionContext.ActionDescriptor.ActionName); WebApiCache.Add(baseKey, string.Empty, cacheTime.AbsoluteExpiration); WebApiCache.Add(cachekey, t.Result, cacheTime.AbsoluteExpiration, baseKey); WebApiCache.Add(cachekey + Constants.ContentTypeKey, actionExecutedContext.Response.Content.Headers.ContentType.MediaType, cacheTime.AbsoluteExpiration, baseKey); WebApiCache.Add(cachekey + Constants.EtagKey, actionExecutedContext.Response.Headers.ETag.Tag, cacheTime.AbsoluteExpiration, baseKey); WebApiCache.Add(cachekey + Constants.ContentRangeKey, actionExecutedContext.Response.Content.Headers.ContentRange, cacheTime.AbsoluteExpiration, baseKey); }); } } } ApplyCacheHeaders(actionExecutedContext.ActionContext.Response, cacheTime); }
/// <summary> /// If allowed for this request, cache the rendered bytes, content type, and ETag. /// </summary> /// <param name="actionExecutedContext"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken) { // If the request failed, there is nothing to cache. if (actionExecutedContext.ActionContext.Response == null || !actionExecutedContext.ActionContext.Response.IsSuccessStatusCode) { return; } // Don't try to cache if we shouldn't. if (!IsCachingAllowed(actionExecutedContext.ActionContext, AnonymousOnly)) { return; } var cacheTime = CacheTimeQuery.Execute(DateTime.Now); if (cacheTime.AbsoluteExpiration > DateTime.Now) { var httpConfig = actionExecutedContext.Request.GetConfiguration(); var config = httpConfig.GetOutputCacheConfiguration(); var responseMediaType = actionExecutedContext.Request.Properties[CurrentRequestMediaType] as MediaTypeHeaderValue ?? GetExpectedMediaType(httpConfig, actionExecutedContext.ActionContext); var fullCacheKey = actionExecutedContext.Request.Properties[FullCacheKey] as string; if (!string.IsNullOrWhiteSpace(fullCacheKey) && !(await WebApiCache.ContainsAsync(fullCacheKey))) { // Add an ETag to the response. SetEtag(actionExecutedContext.Response, CreateEtag()); var responseContent = actionExecutedContext.Response.Content; if (responseContent != null) { var contentType = responseContent.Headers.ContentType.ToString(); var etag = actionExecutedContext.Response.Headers.ETag.Tag; var contentBytes = await responseContent.ReadAsByteArrayAsync().ConfigureAwait(false); responseContent.Headers.Remove("Content-Length"); // Cache the content bytes, content type, and ETag. await WebApiCache.AddAsync(fullCacheKey, contentBytes, cacheTime.AbsoluteExpiration); await WebApiCache.AddAsync(fullCacheKey + Constants.ContentTypeKey, contentType, cacheTime.AbsoluteExpiration); await WebApiCache.AddAsync(fullCacheKey + Constants.EtagKey, etag, cacheTime.AbsoluteExpiration); } } } ApplyCacheHeaders(actionExecutedContext.ActionContext.Response, cacheTime); }
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) { if (actionExecutedContext.Response != null && !actionExecutedContext.Response.IsSuccessStatusCode) { return; } _controller = _controller ?? actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerName; // compare: ...ControllerDescriptor.ControllerType.FullName; EnsureCache(actionExecutedContext.Request.GetConfiguration(), actionExecutedContext.Request); //axctxt.Request.GetConfiguration().CacheOutputConfiguration() var basekey = BaseCacheKeyGenerator.GetKey(_controller, _methodName, actionExecutedContext.ActionContext.ActionArguments, BaseKeyCacheArgs); if (WebApiCache.Contains(basekey)) // is this a waste? pry not needed, so long as remove gracefully handles a non-existent key { WebApiCache.RemoveStartsWith(basekey); } }
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) { if (actionExecutedContext.Response != null && !actionExecutedContext.Response.IsSuccessStatusCode) { return; } _controller = _controller ?? actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerName; var config = actionExecutedContext.Request.GetConfiguration(); EnsureCache(config, actionExecutedContext.Request); var key = actionExecutedContext.Request.GetConfiguration().CacheOutputConfiguration().MakeBaseCachekey(_controller, _methodName); if (WebApiCache.Contains(key)) { WebApiCache.RemoveStartsWith(key); } }
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) { if (actionExecutedContext.Response != null && !actionExecutedContext.Response.IsSuccessStatusCode) { return; } _controller = _controller ?? actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerType.FullName; var config = actionExecutedContext.Request.GetConfiguration(); EnsureCache(config, actionExecutedContext.Request); var key = actionExecutedContext.Request.GetConfiguration().CacheOutputConfiguration().MakeBaseCachekey(_controller, _methodName, Thread.CurrentPrincipal.Identity.Name); if (WebApiCache.Contains(key)) { Logger.Debug("删除缓存key完成" + key); WebApiCache.RemoveStartsWith(key); } }
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) { if (actionExecutedContext.Response != null && !actionExecutedContext.Response.IsSuccessStatusCode) { return; } _controllerName = _controllerName ?? actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerType.FullName; var config = actionExecutedContext.Request.GetConfiguration(); EnsureCache(config, actionExecutedContext.Request); var partFinalKey = string.Format("{0}-{1}", _controllerName.ToLower(), _methodName.ToLower()); var keys = WebApiCache.AllKeys.Where(x => x.Contains(partFinalKey)); var enumerable = keys as IList <string> ?? keys.ToList(); if (enumerable.Any()) { enumerable.ForEach(x => { WebApiCache.RemoveStartsWith(x); }); } }
public override void OnActionExecuting(HttpActionContext actionContext) { if (actionContext == null) { throw new ArgumentNullException("actionContext"); } if (!_isCachingAllowed(actionContext, AnonymousOnly)) { return; } var config = actionContext.Request.GetConfiguration(); EnsureCacheTimeQuery(); EnsureCache(config, actionContext.Request); var cacheKeyGenerator = config.CacheOutputConfiguration().GetCacheKeyGenerator(actionContext.Request, CacheKeyGenerator); _responseMediaType = GetExpectedMediaType(config, actionContext); var cachekey = cacheKeyGenerator.MakeCacheKey(actionContext, _responseMediaType, ExcludeQueryStringFromCacheKey); if (!WebApiCache.Contains(cachekey)) { return; } if (actionContext.Request.Headers.IfNoneMatch != null) { var etag = WebApiCache.Get(cachekey + Constants.EtagKey) as string; if (etag != null) { if (actionContext.Request.Headers.IfNoneMatch.Any(x => x.Tag == etag)) { var time = CacheTimeQuery.Execute(DateTime.Now); var quickResponse = actionContext.Request.CreateResponse(HttpStatusCode.NotModified); ApplyCacheHeaders(quickResponse, time); actionContext.Response = quickResponse; return; } } } var val = WebApiCache.Get(cachekey) as byte[]; if (val == null) { return; } var contenttype = WebApiCache.Get(cachekey + Constants.ContentTypeKey) as string ?? cachekey.Split(':')[1]; actionContext.Response = actionContext.Request.CreateResponse(); actionContext.Response.Content = new ByteArrayContent(val); actionContext.Response.Content.Headers.ContentType = new MediaTypeHeaderValue(contenttype); var responseEtag = WebApiCache.Get(cachekey + Constants.EtagKey) as string; if (responseEtag != null) { SetEtag(actionContext.Response, responseEtag); } var cacheTime = CacheTimeQuery.Execute(DateTime.Now); ApplyCacheHeaders(actionContext.Response, cacheTime); }
/// <summary> /// Runs before the action executes. If we find a matching ETag, return 304 Not Modified. If we have the response /// bytes cached, return them. Otherwise, let the request continue. /// </summary> /// <param name="actionContext"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken) { if (actionContext == null) { throw new ArgumentNullException(nameof(actionContext)); } // Make sure we obey anonymity if set, or attributes to ignore caching. if (!IsCachingAllowed(actionContext, AnonymousOnly)) { return; } // If not set by the constructor, grab the controller and action names from the action context. var controllerLowered = actionContext.ControllerContext.Controller.GetType().FullName.ToLower(); var actionLowered = actionContext.ActionDescriptor.ActionName.ToLower(); var config = actionContext.Request.GetConfiguration(); // Ensure that we have properly set certain properties. EnsureCacheTimeQuery(); EnsureCache(config, actionContext.Request); // Get the media type that the client expects. var responseMediaType = GetExpectedMediaType(config, actionContext); actionContext.Request.Properties[CurrentRequestMediaType] = responseMediaType; // // Generate the cache key for this action. It will look something like this: // // somenamespace.controllers.somecontroller-someaction_v1-customerid=3583_v1&userid=31eb2386-1b98-4a5d-bba0-a62d008ea976_v1&qsparam1=ohai:application/json; charset=utf-8" // var cacheKeyGenerator = config.GetOutputCacheConfiguration().GetCacheKeyGenerator(actionContext.Request, typeof(CatchallCacheKeyGenerator)); var fullCacheKey = await cacheKeyGenerator.MakeCacheKeyAsync(WebApiCache, actionContext, responseMediaType, controllerLowered, actionLowered); actionContext.Request.Properties[FullCacheKey] = fullCacheKey; if (!(await WebApiCache.ContainsAsync(fullCacheKey))) { // Output for this action with these parameters is not in the cache, so we can't short circuit the request. Let it continue. return; } // Check to see if we have any cached requests that match by ETag. var etagCacheKey = fullCacheKey + Constants.EtagKey; if (actionContext.Request.Headers.IfNoneMatch != null) { // Try to get the ETag from cache. var etag = await WebApiCache.GetAsync <string>(etagCacheKey); if (etag != null) { // There is an ETag in the cache for this request. Does it match the any of the ETags sent by the client? if (actionContext.Request.Headers.IfNoneMatch.Any(x => x.Tag == etag)) { // Yes! Send them a 304 Not Modified response. var time = CacheTimeQuery.Execute(DateTime.Now); var quickResponse = actionContext.Request.CreateResponse(HttpStatusCode.NotModified); ApplyCacheHeaders(quickResponse, time); actionContext.Response = quickResponse; return; } } } // No matching ETags. See if we have the actual response bytes cached. var val = await WebApiCache.GetAsync <byte[]>(fullCacheKey); if (val == null) { // No response bytes cached for this action/parameters. Let the request continue. return; } // // We have a cached response. Send it back to the caller instead of exeucting the full request. // // Get the content type for the request. MediaTypeHeaderValue is not serializable (it deserializes in a // very strange state with duplicate charset attributes), so we're going cache it as a string and // then parse it here. var contentTypeCached = await WebApiCache.GetAsync <string>(fullCacheKey + Constants.ContentTypeKey); MediaTypeHeaderValue contentType; if (!MediaTypeHeaderValue.TryParse(contentTypeCached, out contentType)) { // That didn't work. Extract it from the cache key. contentType = new MediaTypeHeaderValue(GetMediaTypeFromFullCacheKey(fullCacheKey)); } // Create a new response and populated it with the cached bytes. actionContext.Response = actionContext.Request.CreateResponse(); actionContext.Response.Content = new ByteArrayContent(val); actionContext.Response.Content.Headers.ContentType = contentType; // If there is a cached ETag, add it to the response. var responseEtag = await WebApiCache.GetAsync <string>(etagCacheKey); if (responseEtag != null) { SetEtag(actionContext.Response, responseEtag); } var cacheTime = CacheTimeQuery.Execute(DateTime.Now); ApplyCacheHeaders(actionContext.Response, cacheTime); }
/// <summary> /// Called when the attributed action has finished executing. /// </summary> /// <param name="actionExecutedContext">The action executed context.</param> /// <param name="cancellationToken"></param> public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken) { if (actionExecutedContext.Response != null && !actionExecutedContext.Response.IsSuccessStatusCode) { // Don't invalidate any cached values if the request failed. return; } var config = actionExecutedContext.Request.GetConfiguration(); var cacheConfig = config.GetOutputCacheConfiguration(); EnsureCache(config, actionExecutedContext.Request); // // What we know about the target action's parameters and Invalidate By at this point: // * If _invalidateByParamLowered is not null or whitespace, then _targetActionParamsLowered contains exactly one action parameter // whose name matches _invalidateByParamLowered. // * We have access to the attributed action argument's value. // // If local caching is enabled, we'll notify other nodes that they should evict this item from their local caches. var localCacheNotificationChannel = cacheConfig.IsLocalCachingEnabled ? cacheConfig.ChannelForNotificationsToInvalidateLocalCache : null; if (_targetActionParamsLowered.Count == 0 || string.IsNullOrWhiteSpace(_invalidateByParamLowered)) { // The target action has no parameters, or the attributed action didn't specify a parameter by which to // invalidate, so we're going to invalidate at the controller/action level. var controllerActionVersionKey = CacheKey.ControllerActionVersion(_targetControllerLowered, _targetActionLowered); await WebApiCache.IncrAsync(controllerActionVersionKey, localCacheNotificationChannel); return; } // All you have to do to invalidate by a specific parameter is get its name (lowercase) and value, and // then increment its version number. // The constructor already validated that the "invalidate by" parameter is in the target action's parameter // list, so we can use Single here. string controllerActionArgVersionKey = null; var theActionArg = actionExecutedContext.ActionContext.ActionArguments.SingleOrDefault(kvp => kvp.Key.Equals(_invalidateByParamLowered, StringComparison.OrdinalIgnoreCase)); if (default(KeyValuePair <string, object>).Equals(theActionArg) == false) { // We found it, and we can trivially retrieve the value from the action arguments by name. controllerActionArgVersionKey = CacheKey.ControllerActionArgumentVersion(_targetControllerLowered, _targetActionLowered, _invalidateByParamLowered, theActionArg.Value.GetValueAsString()); } else { // The "invalidate by" parameter is part of a view model in the invalidating action's method signature. We have to retrieve // the value from it. We know the parameter is not one of the simple, default uri-bindable types, so exclude those. var viewModelActionParameters = actionExecutedContext.ActionContext.ActionDescriptor .GetParameters() .Where(p => !p.ParameterType.IsDefaultUriBindableType()) .ToArray(); foreach (var vmActionParam in viewModelActionParameters) { // Get the corresponding action argument matching the parameter name. var actionArg = actionExecutedContext.ActionContext.ActionArguments.Single(kvp => kvp.Key == vmActionParam.ParameterName); if (typeof(IEnumerable).IsAssignableFrom(vmActionParam.ParameterType)) { // It's an array or list of some type. It didn't match by parameter name above, or else we wouldn't be here. There // are no public instance properties to examine, so ignore it. //nonDefaultUriBindableArgNamesValues.Add(new KeyValuePair<string, string>(actionArg.Key, actionArg.Value.GetValueAsString())); continue; } else { // It's a view model/dto. We need its public instance property names and values. if (actionArg.Value != null) { // Get the name/value of the public instance property matching it by name. var pubInstProp = actionArg.Value .GetType() .GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.Name.Equals(_invalidateByParamLowered, StringComparison.OrdinalIgnoreCase)) .SingleOrDefault(); if (pubInstProp != null) { // We found a matching public instance property. Get its value as a string controllerActionArgVersionKey = CacheKey.ControllerActionArgumentVersion(_targetControllerLowered, _targetActionLowered, _invalidateByParamLowered, pubInstProp.GetValue(actionArg.Value).GetValueAsString()); break; } } else { // The view model/dto object is null. We still need its public instance property names. If a query string parameter of the // same name exists, we'll use its value. Otherwise, we'll use the value from a new instance of the parameter // type. var objInstance = Activator.CreateInstance(vmActionParam.ParameterType); var pubInstProps = vmActionParam.ParameterType.GetProperties(BindingFlags.Public | BindingFlags.Instance); // Exclude jsonp callback parameters, if any. var qsParams = actionExecutedContext.ActionContext.Request.GetQueryNameValuePairs() .Where(x => x.Key.ToLower() != "callback") .ToArray(); foreach (var pubInstProp in pubInstProps) { // Case insenstitive compare because query string parameter names can come in as any case. var matchingQsParams = qsParams.Where(kvp => kvp.Key.Equals(pubInstProp.Name, StringComparison.OrdinalIgnoreCase)).ToArray(); if (matchingQsParams.Length > 0) { // When the target is a non-collection, scalar value, the default model binder only selects the first value if // there are multiple query string parameters with the same name. Mimic that behavior here. controllerActionArgVersionKey = CacheKey.ControllerActionArgumentVersion(_targetControllerLowered, _targetActionLowered, _invalidateByParamLowered, matchingQsParams[0].Value); break; } else { // Punt. We don't have anywhere in the current request from which to grab a value, so take the default value // of the matching property on the instance we created above. controllerActionArgVersionKey = CacheKey.ControllerActionArgumentVersion(_targetControllerLowered, _targetActionLowered, _invalidateByParamLowered, pubInstProp.GetValue(objInstance).GetValueAsString()); break; } } } } } if (string.IsNullOrWhiteSpace(controllerActionArgVersionKey)) { // The "invalidate by" parameter value was not a simple, default uri-bindable type in the action method list, and // we couldn't find it in any view model/dto public instance properties. // This is bad, but we don't want to throw an exception and cause the application to stop working. Definitely log // the failure, though. var controller = actionExecutedContext.ActionContext.ControllerContext.Controller.GetType().FullName; var action = actionExecutedContext.ActionContext.ActionDescriptor.ActionName; Logger.Error($"Output cache invalidation failed on {controller}.{action} with invalidation parameter {_invalidateByParamLowered} targeting {_targetControllerLowered}.{_targetActionLowered}. Unable to find a view model/dto with a matching public instance property."); } } await WebApiCache.IncrAsync(controllerActionArgVersionKey, localCacheNotificationChannel); }