• web api 缓存类


      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Text;
      5 using System.Web.Http.Filters;
      6 using System.Net.Http.Headers;
      7 using System.Net.Http;
      8 using System.Threading;
      9 using System.Net.Http.Formatting;
     10 using System.Web.Http.Controllers;
     11 using System.Linq.Expressions;
     12 using System.Reflection;
     13 using System.Threading.Tasks;
     14 using System.Collections;
     15 using System.Net;
     16 using System.Runtime.Caching;
     17 using System.Web.Http;
     18 
     19 
     20 
     21 namespace Cashlibary
     22 {
     23     public class CacheOutputAttribute : ActionFilterAttribute
     24     {
     25         private const string CurrentRequestMediaType = "CacheOutput:CurrentRequestMediaType";
     26         protected static MediaTypeHeaderValue DefaultMediaType = new MediaTypeHeaderValue("application/json") { CharSet = Encoding.UTF8.HeaderName };
     27 
     28         /// <summary>
     29         /// Cache enabled only for requests when Thread.CurrentPrincipal is not set
     30         /// </summary>
     31         public bool AnonymousOnly { get; set; }
     32 
     33         /// <summary>
     34         /// Corresponds to MustRevalidate HTTP header - indicates whether the origin server requires revalidation of a cache entry on any subsequent use when the cache entry becomes stale
     35         /// </summary>
     36         public bool MustRevalidate { get; set; }
     37 
     38         /// <summary>
     39         /// Do not vary cache by querystring values
     40         /// </summary>
     41         public bool ExcludeQueryStringFromCacheKey { get; set; }
     42 
     43         /// <summary>
     44         /// How long response should be cached on the server side (in seconds)
     45         /// </summary>
     46         public int ServerTimeSpan { get; set; }
     47 
     48         /// <summary>
     49         /// Corresponds to CacheControl MaxAge HTTP header (in seconds)
     50         /// </summary>
     51         public int ClientTimeSpan { get; set; }
     52 
     53         /// <summary>
     54         /// Corresponds to CacheControl NoCache HTTP header
     55         /// </summary>
     56         public bool NoCache { get; set; }
     57 
     58         /// <summary>
     59         /// Corresponds to CacheControl Private HTTP header. Response can be cached by browser but not by intermediary cache
     60         /// </summary>
     61         public bool Private { get; set; }
     62 
     63         /// <summary>
     64         /// Class used to generate caching keys
     65         /// </summary>
     66         public Type CacheKeyGenerator { get; set; }
     67 
     68         // cache repository
     69         private IApiOutputCache _webApiCache;
     70 
     71         protected virtual void EnsureCache(HttpConfiguration config, HttpRequestMessage req)
     72         {
     73             _webApiCache = config.CacheOutputConfiguration().GetCacheOutputProvider(req);
     74         }
     75 
     76         internal IModelQuery<DateTime, CacheTime> CacheTimeQuery;
     77 
     78         protected virtual bool IsCachingAllowed(HttpActionContext actionContext, bool anonymousOnly)
     79         {
     80             if (anonymousOnly)
     81             {
     82                 if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
     83                 {
     84                     return false;
     85                 }
     86             }
     87 
     88             if (actionContext.ActionDescriptor.GetCustomAttributes<IgnoreCacheOutputAttribute>().Any())
     89             {
     90                 return false;
     91             }
     92 
     93             return actionContext.Request.Method == HttpMethod.Post;
     94         }
     95 
     96         protected virtual void EnsureCacheTimeQuery()
     97         {
     98             if (CacheTimeQuery == null) ResetCacheTimeQuery();
     99         }
    100 
    101         protected void ResetCacheTimeQuery()
    102         {
    103             CacheTimeQuery = new ShortTime(ServerTimeSpan, ClientTimeSpan);
    104         }
    105 
    106         protected virtual MediaTypeHeaderValue GetExpectedMediaType(HttpConfiguration config, HttpActionContext actionContext)
    107         {
    108             MediaTypeHeaderValue responseMediaType = null;
    109 
    110             var negotiator = config.Services.GetService(typeof(IContentNegotiator)) as IContentNegotiator;
    111             var returnType = actionContext.ActionDescriptor.ReturnType;
    112 
    113             if (negotiator != null && returnType != typeof(HttpResponseMessage) && (returnType != typeof(IHttpActionResult) || typeof(IHttpActionResult).IsAssignableFrom(returnType)))
    114             {
    115                 var negotiatedResult = negotiator.Negotiate(returnType, actionContext.Request, config.Formatters);
    116 
    117                 if (negotiatedResult == null)
    118                 {
    119                     return DefaultMediaType;
    120                 }
    121 
    122                 responseMediaType = negotiatedResult.MediaType;
    123                 if (string.IsNullOrWhiteSpace(responseMediaType.CharSet))
    124                 {
    125                     responseMediaType.CharSet = Encoding.UTF8.HeaderName;
    126                 }
    127             }
    128             else
    129             {
    130                 if (actionContext.Request.Headers.Accept != null)
    131                 {
    132                     responseMediaType = actionContext.Request.Headers.Accept.FirstOrDefault();
    133                     if (responseMediaType == null || !config.Formatters.Any(x => x.SupportedMediaTypes.Contains(responseMediaType)))
    134                     {
    135                         return DefaultMediaType;
    136                     }
    137                 }
    138             }
    139 
    140             return responseMediaType;
    141         }
    142 
    143         public override void OnActionExecuting(HttpActionContext actionContext)
    144         {
    145             if (actionContext == null) throw new ArgumentNullException("actionContext");
    146 
    147             if (!IsCachingAllowed(actionContext, AnonymousOnly)) return;
    148 
    149             var config = actionContext.Request.GetConfiguration();
    150 
    151             EnsureCacheTimeQuery();
    152             EnsureCache(config, actionContext.Request);
    153 
    154             var cacheKeyGenerator = config.CacheOutputConfiguration().GetCacheKeyGenerator(actionContext.Request, CacheKeyGenerator);
    155 
    156             var responseMediaType = GetExpectedMediaType(config, actionContext);
    157             actionContext.Request.Properties[CurrentRequestMediaType] = responseMediaType;
    158             var cachekey = cacheKeyGenerator.MakeCacheKey(actionContext, responseMediaType, ExcludeQueryStringFromCacheKey);
    159 
    160             if (!_webApiCache.Contains(cachekey)) return;
    161 
    162             if (actionContext.Request.Headers.IfNoneMatch != null)
    163             {
    164                 var etag = _webApiCache.Get<string>(cachekey + Constants.EtagKey);
    165                 if (etag != null)
    166                 {
    167                     if (actionContext.Request.Headers.IfNoneMatch.Any(x => x.Tag == etag))
    168                     {
    169                         var time = CacheTimeQuery.Execute(DateTime.Now);
    170                         var quickResponse = actionContext.Request.CreateResponse(HttpStatusCode.NotModified);
    171                         ApplyCacheHeaders(quickResponse, time);
    172                         actionContext.Response = quickResponse;
    173                         return;
    174                     }
    175                 }
    176             }
    177 
    178             var val = _webApiCache.Get<byte[]>(cachekey);
    179             if (val == null) return;
    180 
    181             var contenttype = _webApiCache.Get<MediaTypeHeaderValue>(cachekey + Constants.ContentTypeKey) ?? new MediaTypeHeaderValue(cachekey.Split(new[] { ':' }, 2)[1].Split(';')[0]);
    182 
    183             actionContext.Response = actionContext.Request.CreateResponse();
    184             actionContext.Response.Content = new ByteArrayContent(val);
    185 
    186             actionContext.Response.Content.Headers.ContentType = contenttype;
    187             var responseEtag = _webApiCache.Get<string>(cachekey + Constants.EtagKey);
    188             if (responseEtag != null) SetEtag(actionContext.Response, responseEtag);
    189 
    190             var cacheTime = CacheTimeQuery.Execute(DateTime.Now);
    191             ApplyCacheHeaders(actionContext.Response, cacheTime);
    192         }
    193 
    194         public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
    195         {
    196             if (actionExecutedContext.ActionContext.Response == null || !actionExecutedContext.ActionContext.Response.IsSuccessStatusCode) return;
    197 
    198             if (!IsCachingAllowed(actionExecutedContext.ActionContext, AnonymousOnly)) return;
    199 
    200             var cacheTime = CacheTimeQuery.Execute(DateTime.Now);
    201             if (cacheTime.AbsoluteExpiration > DateTime.Now)
    202             {
    203                 var httpConfig = actionExecutedContext.Request.GetConfiguration();
    204                 var config = httpConfig.CacheOutputConfiguration();
    205                 var cacheKeyGenerator = config.GetCacheKeyGenerator(actionExecutedContext.Request, CacheKeyGenerator);
    206 
    207                 var responseMediaType = actionExecutedContext.Request.Properties[CurrentRequestMediaType] as MediaTypeHeaderValue ?? GetExpectedMediaType(httpConfig, actionExecutedContext.ActionContext);
    208                 var cachekey = cacheKeyGenerator.MakeCacheKey(actionExecutedContext.ActionContext, responseMediaType, ExcludeQueryStringFromCacheKey);
    209 
    210                 if (!string.IsNullOrWhiteSpace(cachekey) && !(_webApiCache.Contains(cachekey)))
    211                 {
    212                     SetEtag(actionExecutedContext.Response, CreateEtag(actionExecutedContext, cachekey, cacheTime));
    213 
    214                     var responseContent = actionExecutedContext.Response.Content;
    215 
    216                     if (responseContent != null)
    217                     {
    218                         var baseKey = config.MakeBaseCachekey(actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerType.FullName, actionExecutedContext.ActionContext.ActionDescriptor.ActionName);
    219                         var contentType = responseContent.Headers.ContentType;
    220                         string etag = actionExecutedContext.Response.Headers.ETag.Tag;
    221                         //ConfigureAwait false to avoid deadlocks
    222                         var content = await responseContent.ReadAsByteArrayAsync().ConfigureAwait(false);
    223 
    224                         responseContent.Headers.Remove("Content-Length");
    225 
    226                         _webApiCache.Add(baseKey, string.Empty, cacheTime.AbsoluteExpiration);
    227                         _webApiCache.Add(cachekey, content, cacheTime.AbsoluteExpiration, baseKey);
    228 
    229 
    230                         _webApiCache.Add(cachekey + Constants.ContentTypeKey,
    231                                         contentType,
    232                                         cacheTime.AbsoluteExpiration, baseKey);
    233 
    234 
    235                         _webApiCache.Add(cachekey + Constants.EtagKey,
    236                                         etag,
    237                                         cacheTime.AbsoluteExpiration, baseKey);
    238                     }
    239                 }
    240             }
    241 
    242             ApplyCacheHeaders(actionExecutedContext.ActionContext.Response, cacheTime);
    243         }
    244 
    245         protected virtual void ApplyCacheHeaders(HttpResponseMessage response, CacheTime cacheTime)
    246         {
    247             if (cacheTime.ClientTimeSpan > TimeSpan.Zero || MustRevalidate || Private)
    248             {
    249                 var cachecontrol = new CacheControlHeaderValue
    250                 {
    251                     MaxAge = cacheTime.ClientTimeSpan,
    252                     MustRevalidate = MustRevalidate,
    253                     Private = Private
    254                 };
    255 
    256                 response.Headers.CacheControl = cachecontrol;
    257             }
    258             else if (NoCache)
    259             {
    260                 response.Headers.CacheControl = new CacheControlHeaderValue { NoCache = true };
    261                 response.Headers.Add("Pragma", "no-cache");
    262             }
    263         }
    264 
    265         protected virtual string CreateEtag(HttpActionExecutedContext actionExecutedContext, string cachekey, CacheTime cacheTime)
    266         {
    267             return Guid.NewGuid().ToString();
    268         }
    269 
    270         private static void SetEtag(HttpResponseMessage message, string etag)
    271         {
    272             if (etag != null)
    273             {
    274                 var eTag = new EntityTagHeaderValue(@"""" + etag.Replace(""", string.Empty) + @"""");
    275                 message.Headers.ETag = eTag;
    276             }
    277         }
    278     }
    279 
    280     public interface ICacheKeyGenerator
    281     {
    282         string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString = false);
    283     }
    284     public sealed class Constants
    285     {
    286         public const string ContentTypeKey = ":response-ct";
    287         public const string EtagKey = ":response-etag";
    288     }
    289 
    290     public class DefaultCacheKeyGenerator : ICacheKeyGenerator
    291     {
    292         public virtual string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString = false)
    293         {
    294             var controller = context.ControllerContext.ControllerDescriptor.ControllerType.FullName;
    295             var action = context.ActionDescriptor.ActionName;
    296             var key = context.Request.GetConfiguration().CacheOutputConfiguration().MakeBaseCachekey(controller, action);
    297             var actionParameters = context.ActionArguments.Where(x => x.Value != null).Select(x => x.Key + "=" + GetValue(x.Value));
    298 
    299             string parameters;
    300 
    301             if (!excludeQueryString)
    302             {
    303                 var queryStringParameters =
    304                     context.Request.GetQueryNameValuePairs()
    305                            .Where(x => x.Key.ToLower() != "callback")
    306                            .Select(x => x.Key + "=" + x.Value);
    307                 var parametersCollections = actionParameters.Union(queryStringParameters);
    308                 parameters = "-" + string.Join("&", parametersCollections);
    309 
    310                 var callbackValue = GetJsonpCallback(context.Request);
    311                 if (!string.IsNullOrWhiteSpace(callbackValue))
    312                 {
    313                     var callback = "callback=" + callbackValue;
    314                     if (parameters.Contains("&" + callback)) parameters = parameters.Replace("&" + callback, string.Empty);
    315                     if (parameters.Contains(callback + "&")) parameters = parameters.Replace(callback + "&", string.Empty);
    316                     if (parameters.Contains("-" + callback)) parameters = parameters.Replace("-" + callback, string.Empty);
    317                     if (parameters.EndsWith("&")) parameters = parameters.TrimEnd('&');
    318                 }
    319             }
    320             else
    321             {
    322                 parameters = "-" + string.Join("&", actionParameters);
    323             }
    324 
    325             if (parameters == "-") parameters = string.Empty;
    326 
    327             var cachekey = string.Format("{0}{1}:{2}", key, parameters, mediaType);
    328             return cachekey;
    329         }
    330 
    331         private string GetJsonpCallback(HttpRequestMessage request)
    332         {
    333             var callback = string.Empty;
    334             if (request.Method == HttpMethod.Get)
    335             {
    336                 var query = request.GetQueryNameValuePairs();
    337 
    338                 if (query != null)
    339                 {
    340                     var queryVal = query.FirstOrDefault(x => x.Key.ToLower() == "callback");
    341                     if (!queryVal.Equals(default(KeyValuePair<string, string>))) callback = queryVal.Value;
    342                 }
    343             }
    344             return callback;
    345         }
    346 
    347         private string GetValue(object val)
    348         {
    349             if (val is IEnumerable && !(val is string))
    350             {
    351                 var concatValue = string.Empty;
    352                 var paramArray = val as IEnumerable;
    353                 return paramArray.Cast<object>().Aggregate(concatValue, (current, paramValue) => current + (paramValue + ";"));
    354             }
    355             return val.ToString();
    356         }
    357     }
    358 
    359 
    360     public class CacheOutputConfiguration
    361     {
    362         private readonly HttpConfiguration _configuration;
    363 
    364         public CacheOutputConfiguration(HttpConfiguration configuration)
    365         {
    366             _configuration = configuration;
    367         }
    368 
    369         public void RegisterCacheOutputProvider(Func<IApiOutputCache> provider)
    370         {
    371             _configuration.Properties.GetOrAdd(typeof(IApiOutputCache), x => provider);
    372         }
    373 
    374         public void RegisterCacheKeyGeneratorProvider<T>(Func<T> provider)
    375             where T : ICacheKeyGenerator
    376         {
    377             _configuration.Properties.GetOrAdd(typeof(T), x => provider);
    378         }
    379 
    380         public void RegisterDefaultCacheKeyGeneratorProvider(Func<ICacheKeyGenerator> provider)
    381         {
    382             RegisterCacheKeyGeneratorProvider(provider);
    383         }
    384 
    385         public string MakeBaseCachekey(string controller, string action)
    386         {
    387             return string.Format("{0}-{1}", controller.ToLower(), action.ToLower());
    388         }
    389 
    390         public string MakeBaseCachekey<T, U>(Expression<Func<T, U>> expression)
    391         {
    392             var method = expression.Body as MethodCallExpression;
    393             if (method == null) throw new ArgumentException("Expression is wrong");
    394 
    395             var methodName = method.Method.Name;
    396             var nameAttribs = method.Method.GetCustomAttributes(typeof(ActionNameAttribute), false);
    397             if (nameAttribs.Any())
    398             {
    399                 var actionNameAttrib = (ActionNameAttribute)nameAttribs.FirstOrDefault();
    400                 if (actionNameAttrib != null)
    401                 {
    402                     methodName = actionNameAttrib.Name;
    403                 }
    404             }
    405 
    406             return string.Format("{0}-{1}", typeof(T).FullName.ToLower(), methodName.ToLower());
    407         }
    408 
    409         private static ICacheKeyGenerator TryActivateCacheKeyGenerator(Type generatorType)
    410         {
    411             var hasEmptyOrDefaultConstructor =
    412                 generatorType.GetConstructor(Type.EmptyTypes) != null ||
    413                 generatorType.GetConstructors(BindingFlags.Instance | BindingFlags.Public)
    414                 .Any(x => x.GetParameters().All(p => p.IsOptional));
    415             return hasEmptyOrDefaultConstructor
    416                 ? Activator.CreateInstance(generatorType) as ICacheKeyGenerator
    417                 : null;
    418         }
    419 
    420         public ICacheKeyGenerator GetCacheKeyGenerator(HttpRequestMessage request, Type generatorType)
    421         {
    422             generatorType = generatorType ?? typeof(ICacheKeyGenerator);
    423             object cache;
    424             _configuration.Properties.TryGetValue(generatorType, out cache);
    425 
    426             var cacheFunc = cache as Func<ICacheKeyGenerator>;
    427 
    428             var generator = cacheFunc != null
    429                 ? cacheFunc()
    430                 : request.GetDependencyScope().GetService(generatorType) as ICacheKeyGenerator;
    431 
    432             return generator
    433                 ?? TryActivateCacheKeyGenerator(generatorType)
    434                 ?? new DefaultCacheKeyGenerator();
    435         }
    436 
    437         public IApiOutputCache GetCacheOutputProvider(HttpRequestMessage request)
    438         {
    439             object cache;
    440             _configuration.Properties.TryGetValue(typeof(IApiOutputCache), out cache);
    441 
    442             var cacheFunc = cache as Func<IApiOutputCache>;
    443 
    444             var cacheOutputProvider = cacheFunc != null ? cacheFunc() : request.GetDependencyScope().GetService(typeof(IApiOutputCache)) as IApiOutputCache ?? new MemoryCacheDefault();
    445             return cacheOutputProvider;
    446         }
    447     }
    448 
    449     public static class HttpConfigurationExtensions
    450     {
    451         public static CacheOutputConfiguration CacheOutputConfiguration(this HttpConfiguration config)
    452         {
    453             return new CacheOutputConfiguration(config);
    454         }
    455     }
    456     public sealed class IgnoreCacheOutputAttribute : Attribute
    457     {
    458     }
    459 
    460     public class CacheTime
    461     {
    462         // client cache length in seconds
    463         public TimeSpan ClientTimeSpan { get; set; }
    464 
    465         public DateTimeOffset AbsoluteExpiration { get; set; }
    466     }
    467     public interface IApiOutputCache
    468     {
    469         void RemoveStartsWith(string key);
    470 
    471         T Get<T>(string key) where T : class;
    472 
    473         [Obsolete("Use Get<T> instead")]
    474         object Get(string key);
    475 
    476         void Remove(string key);
    477 
    478         bool Contains(string key);
    479 
    480         void Add(string key, object o, DateTimeOffset expiration, string dependsOnKey = null);
    481 
    482         IEnumerable<string> AllKeys { get; }
    483     }
    484 
    485     public class ShortTime : IModelQuery<DateTime, CacheTime>
    486     {
    487         private readonly int serverTimeInSeconds;
    488         private readonly int clientTimeInSeconds;
    489 
    490         public ShortTime(int serverTimeInSeconds, int clientTimeInSeconds)
    491         {
    492             if (serverTimeInSeconds < 0)
    493                 serverTimeInSeconds = 0;
    494 
    495             this.serverTimeInSeconds = serverTimeInSeconds;
    496 
    497             if (clientTimeInSeconds < 0)
    498                 clientTimeInSeconds = 0;
    499 
    500             this.clientTimeInSeconds = clientTimeInSeconds;
    501         }
    502 
    503         public CacheTime Execute(DateTime model)
    504         {
    505             var cacheTime = new CacheTime
    506             {
    507                 AbsoluteExpiration = model.AddSeconds(serverTimeInSeconds),
    508                 ClientTimeSpan = TimeSpan.FromSeconds(clientTimeInSeconds)
    509             };
    510 
    511             return cacheTime;
    512         }
    513     }
    514     public interface IModelQuery<in TModel, out TResult>
    515     {
    516         TResult Execute(TModel model);
    517     }
    518     public class MemoryCacheDefault : IApiOutputCache
    519     {
    520         private static readonly MemoryCache Cache = MemoryCache.Default;
    521 
    522         public void RemoveStartsWith(string key)
    523         {
    524             lock (Cache)
    525             {
    526                 Cache.Remove(key);
    527             }
    528         }
    529 
    530         public T Get<T>(string key) where T : class
    531         {
    532             var o = Cache.Get(key) as T;
    533             return o;
    534         }
    535 
    536         [Obsolete("Use Get<T> instead")]
    537         public object Get(string key)
    538         {
    539             return Cache.Get(key);
    540         }
    541 
    542         public void Remove(string key)
    543         {
    544             lock (Cache)
    545             {
    546                 Cache.Remove(key);
    547             }
    548         }
    549 
    550         public bool Contains(string key)
    551         {
    552             return Cache.Contains(key);
    553         }
    554 
    555         public void Add(string key, object o, DateTimeOffset expiration, string dependsOnKey = null)
    556         {
    557             var cachePolicy = new CacheItemPolicy
    558             {
    559                 AbsoluteExpiration = expiration
    560             };
    561 
    562             if (!string.IsNullOrWhiteSpace(dependsOnKey))
    563             {
    564                 cachePolicy.ChangeMonitors.Add(
    565                     Cache.CreateCacheEntryChangeMonitor(new[] { dependsOnKey })
    566                 );
    567             }
    568             lock (Cache)
    569             {
    570                 Cache.Add(key, o, cachePolicy);
    571             }
    572         }
    573 
    574         public IEnumerable<string> AllKeys
    575         {
    576             get
    577             {
    578                 return Cache.Select(x => x.Key);
    579             }
    580         }
    581     }
    582 
    583     // 使用方法 [CacheOutput(ClientTimeSpan = 350, ServerTimeSpan = 50)]
    584 
    585 
    586 
    587 }
  • 相关阅读:
    27-Perl 进程管理
    26-Perl 包和模块
    25-Perl CGI编程
    YUM极速安装mariadb
    yum极速安装mysql5.7
    切换阿里yum镜像源
    mysql生成随机字符串函数
    Unable to locate value meta plugin of type (id)
    centos 挂载NTFS移动硬盘
    总有你要的编程书单(GitHub )
  • 原文地址:https://www.cnblogs.com/hbsfgl/p/5076630.html
Copyright © 2020-2023  润新知