public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { await base.OnActionExecutionAsync(context, next); if ( context.HttpContext.Response != null && !( context.HttpContext.Response.StatusCode >= (int)HttpStatusCode.OK && context.HttpContext.Response.StatusCode < (int)HttpStatusCode.Ambiguous ) ) { return; } IServiceProvider serviceProvider = context.HttpContext.RequestServices; IApiOutputCache cache = serviceProvider.GetService(typeof(IApiOutputCache)) as IApiOutputCache; ICacheKeyGenerator cacheKeyGenerator = serviceProvider.GetService(typeof(ICacheKeyGenerator)) as ICacheKeyGenerator; if (cache != null && cacheKeyGenerator != null) { string controllerName = this.controller ?? (context.ActionDescriptor as ControllerActionDescriptor)?.ControllerTypeInfo.FullName; string baseCachekey = cacheKeyGenerator.MakeBaseCachekey(controllerName, this.methodName); await cache.RemoveStartsWithAsync(baseCachekey); } }
protected override void EnsureCache(HttpConfiguration config, HttpRequestMessage req) { _webApiCache = config.CacheOutputConfiguration().GetCacheOutputProvider(req); if (_webApiCache is CustomCacheProvider) { ((CustomCacheProvider)_webApiCache).ExpirationMode = this.ExpirationMode; } }
protected virtual void EnsureCache(HttpConfiguration config, HttpRequestMessage req) { object cache; config.Properties.TryGetValue(typeof (IApiOutputCache), out cache); var cacheFunc = cache as Func<IApiOutputCache>; _webApiCache = cacheFunc != null ? cacheFunc() : req.GetDependencyScope().GetService(typeof(IApiOutputCache)) as IApiOutputCache ?? new MemoryCacheDefault(); }
/// <summary> /// Classe para pegar o cache /// </summary> /// <typeparam name="T"></typeparam> /// <param name="cache"></param> /// <param name="key"></param> /// <param name="expiry"></param> /// <param name="resultGetter"></param> /// <param name="bypassCache"></param> /// <returns></returns> public static T GetCachedResult <T>(this IApiOutputCache cache, string key, DateTimeOffset expiry, Func <T> resultGetter, bool bypassCache = true) where T : class { var result = cache.Get <T>(key); if (result == null || bypassCache) { result = resultGetter(); if (result != null) { cache.Add(key, result, expiry); } } return(result); }
protected virtual void EnsureCache(HttpConfiguration config, HttpRequestMessage req) { _webApiCache = config.CacheOutputConfiguration().GetCacheOutputProvider(req); }
public CacheOutputCacheProvider(IApiOutputCache cache) { this.cacheConfig = GlobalConfiguration.Configuration.CacheOutputConfiguration(); this.cache = cache; }
/// <summary> /// Generates a cache key containing the namespace/controller/action, and name/value pairs for action arguments. /// Query string parameters that do not map to an action parameter are not included. /// </summary> /// <param name="cache"></param> /// <param name="actionContext"></param> /// <param name="mediaType"></param> /// <param name="controllerLowered"></param> /// <param name="actionLowered"></param> /// <returns></returns> public async Task <string> MakeCacheKeyAsync(IApiOutputCache cache, HttpActionContext actionContext, MediaTypeHeaderValue mediaType, string controllerLowered, string actionLowered) { // The default set of action argument names/values that make up the cache key: // * name=value of all default URI-bindlable action parameters. // * name=val1;val2;val3 of all URI-bindable IEnumerable action parameters. // * prop1name=prop1val, etc. of all public instance properties of URI-bindable, non-IEnumerable action parameters (aka, view models or DTOs). // // The key point is that they must match up to a named parameter so that the invalidation logic can // access the value and increment its counter. var allActionParameters = actionContext.ActionDescriptor.GetParameters(); // // Get name=value pairs from "simple" Web API action parameters (i.e., URI-bound by default). // var defaultUriBindableArgNamesValues = new List <KeyValuePair <string, string> >(); var defaultUriBindableActionParams = allActionParameters.Where(ap => ap.ParameterType.IsDefaultUriBindableType()).ToList(); foreach (var defaultUriBindableActionParam in defaultUriBindableActionParams) { var actionArg = actionContext.ActionArguments.Single(kvp => kvp.Key == defaultUriBindableActionParam.ParameterName); defaultUriBindableArgNamesValues.Add(new KeyValuePair <string, string>(actionArg.Key, actionArg.Value.GetValueAsString())); } // // Get name=value pairs from complex types that are explicitly URI-bound via FromUriAttribute. // var nonDefaultUriBindableArgNamesValues = new List <KeyValuePair <string, string> >(); var nonDefaultUriBindableActionParams = allActionParameters.Where(ap => !ap.ParameterType.IsDefaultUriBindableType() && ap.IsUriBindableParameter()).ToList(); foreach (var nonDefaultUriBindableActionParam in nonDefaultUriBindableActionParams) { // Get the corresponding action argument matching the parameter name. var actionArg = actionContext.ActionArguments.Single(kvp => kvp.Key == nonDefaultUriBindableActionParam.ParameterName); if (typeof(IEnumerable).IsAssignableFrom(nonDefaultUriBindableActionParam.ParameterType)) { // It's an array or list of some type. Join its values as a semicolon-separated string. nonDefaultUriBindableArgNamesValues.Add(new KeyValuePair <string, string>(actionArg.Key, actionArg.Value.GetValueAsString())); } else { // It's a view model/dto. We need its public instance property names and values. if (actionArg.Value != null) { // Get the names/values of its public instance properties. var pubInstProps = actionArg.Value.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var pubInstProp in pubInstProps) { nonDefaultUriBindableArgNamesValues.Add(new KeyValuePair <string, string>(pubInstProp.Name, pubInstProp.GetValue(actionArg.Value).GetValueAsString())); } } else { // The 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(nonDefaultUriBindableActionParam.ParameterType); var pubInstProps = nonDefaultUriBindableActionParam.ParameterType.GetProperties(BindingFlags.Public | BindingFlags.Instance); // Exclude jsonp callback parameters, if any. var qsParams = 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. // For consistency, use the casing of the property name. nonDefaultUriBindableArgNamesValues.Add(new KeyValuePair <string, string>(pubInstProp.Name, matchingQsParams[0].Value)); } 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. nonDefaultUriBindableArgNamesValues.Add(new KeyValuePair <string, string>(pubInstProp.Name, pubInstProp.GetValue(objInstance).GetValueAsString())); } } } } } // Combine default URI-bindable arg names/values with non-default URI-bindable arg names/values. //TODO: look for and combine args with same name from default and non-default? var allArgNameValues = defaultUriBindableArgNamesValues .Concat(nonDefaultUriBindableArgNamesValues) .OrderBy(kvp => kvp.Key) .ToList(); // Get the versions for the controller/action, and for each argument name/value. var cacheConfig = actionContext.Request.GetConfiguration().GetOutputCacheConfiguration(); var controllerActionVersionId = await GetControllerActionVersionIdAsync(cache, controllerLowered, actionLowered, cacheConfig.IsLocalCachingEnabled); var finalList = new List <string>(); foreach (var argNameValue in allArgNameValues) { var argNameLowered = argNameValue.Key.ToLower(); // Get or create the argument name/value version from redis. It is scoped at the namespace/controller/action level, so // it will be unique. var key = CacheKey.ControllerActionArgumentVersion(controllerLowered, actionLowered, argNameLowered, argNameValue.Value); var version = await cache.GetOrIncrAsync(key, cacheConfig.IsLocalCachingEnabled); finalList.Add(CacheKey.VersionedArgumentNameAndValue(argNameLowered, argNameValue.Value?.Trim(), version)); } var parameters = $"-{string.Join("&", finalList)}"; if (parameters == "-") { parameters = string.Empty; } return($"{controllerLowered}-{actionLowered}_v{controllerActionVersionId}{parameters}{MediaTypeSeparator}{mediaType}"); }
private async Task <long> GetControllerActionVersionIdAsync(IApiOutputCache cache, string controllerLowered, string actionLowered, bool localCacheEnabled) { return(await cache.GetOrIncrAsync(CacheKey.ControllerActionVersion(controllerLowered, actionLowered), localCacheEnabled)); }