Exemple #1
0
        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);
        }
Exemple #4
0
        /// <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);
            }
        }
Exemple #6
0
        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);
            }
        }
Exemple #8
0
        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);
        }
Exemple #10
0
        /// <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);
        }