| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 | using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using iiie.CacheControl.Business.HttpExtensions;
using iiie.CacheControl.Business.OutputCache;
using iiie.CacheControl.DBO;
namespace iiie.CacheControl.Attributes
{
    [AttributeUsage(AttributeTargets.Method)]
    public abstract class CacheControlAttribute : FilterAttribute, IActionFilter
    {
        protected static MediaTypeHeaderValue DefaultMediaType = new MediaTypeHeaderValue("application/json");
        /// <summary>
        /// Indicates if the client can reuse cached data without asking origin server
        /// </summary>
        protected bool MustRevalidate { get; set; }
        /// <summary>
        /// Data used by OutputCache
        /// </summary>
        protected object CacheOutputData { get; set; }
        /// <summary>
        /// Indicates if the query string must be used to control cache
        /// </summary>
        protected bool ExcludeQueryStringFromCacheKey { get; set; }
        /// <summary>
        /// Define the cache type used to store cache
        /// </summary>
        protected OutputCacheType CacheType { get; set; }
        protected Type CacheKeyGenerator { get; set; }
        private MediaTypeHeaderValue _responseMediaType;
        private IOutputCache _webCache;
        protected void EnsureCache(HttpConfiguration config, HttpRequestMessage req)
        {
            _webCache = config.CacheOutputConfiguration(CacheType).GetCacheOutputProvider(req, CacheOutputData);
        }
        protected abstract bool IsValid(CacheDbo data);
        protected virtual CacheDbo CreateCacheUser()
        {
            return new CacheDbo();
        }
        protected virtual MediaTypeHeaderValue GetExpectedMediaType(HttpConfiguration config, HttpActionContext actionContext)
        {
            MediaTypeHeaderValue responseMediaType = null;
            var negotiator = config.Services.GetService(typeof(IContentNegotiator)) as IContentNegotiator;
            var returnType = actionContext.ActionDescriptor.ReturnType;
            if (negotiator != null && returnType != typeof(HttpResponseMessage))
            {
                var negotiatedResult = negotiator.Negotiate(returnType, actionContext.Request, config.Formatters);
                responseMediaType = negotiatedResult.MediaType;
                responseMediaType.CharSet = Encoding.UTF8.HeaderName;
            }
            else
            {
                if (actionContext.Request.Headers.Accept != null)
                {
                    responseMediaType = actionContext.Request.Headers.Accept.FirstOrDefault();
                    if (responseMediaType == null ||
                        !config.Formatters.Any(x => x.SupportedMediaTypes.Contains(responseMediaType)))
                    {
                        DefaultMediaType.CharSet = Encoding.UTF8.HeaderName;
                        return DefaultMediaType;
                    }
                }
            }
            return responseMediaType;
        }
        private void OnActionExecuting(HttpActionContext actionContext)
        {
            if (actionContext == null) throw new ArgumentNullException("actionContext");
            var config = actionContext.Request.GetConfiguration();
            EnsureCache(config, actionContext.Request);
            var cacheKeyGenerator = config.CacheOutputConfiguration(CacheType).GetCacheKeyGenerator(actionContext.Request, CacheKeyGenerator);
            _responseMediaType = GetExpectedMediaType(config, actionContext);
            var cachekey = cacheKeyGenerator.MakeCacheKey(actionContext, _responseMediaType, CacheType, ExcludeQueryStringFromCacheKey);
            var data = _webCache.Get<CacheDbo>(cachekey);
            if (data == null)
                return;
            if (!IsValid(data))
            {
                _webCache.Remove(cachekey);
                return;
            }
            if (actionContext.Request.Headers.IfNoneMatch != null)
            {
                if (data.ETag != null)
                {
                    if (actionContext.Request.Headers.IfNoneMatch.Any(x => x.Tag == data.ETag))
                    {
                        var quickResponse = actionContext.Request.CreateResponse(HttpStatusCode.NotModified);
                        ApplyCacheHeaders(quickResponse);
                        actionContext.Response = quickResponse;
                        return;
                    }
                }
            }
            if (data.Content == null) return;
            actionContext.Response = actionContext.Request.CreateResponse();
            actionContext.Response.Content = new ByteArrayContent(data.Content);
            actionContext.Response.Content.Headers.ContentType = new MediaTypeHeaderValue(data.ContentType);
            if (data.ETag != null) SetEtag(actionContext.Response, data.ETag);
            ApplyCacheHeaders(actionContext.Response);
        }
        private async Task OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            if (actionExecutedContext.ActionContext.Response == null || !actionExecutedContext.ActionContext.Response.IsSuccessStatusCode) return;
            var config = actionExecutedContext.Request.GetConfiguration().CacheOutputConfiguration(CacheType);
            var cacheKeyGenerator = config.GetCacheKeyGenerator(actionExecutedContext.Request, CacheKeyGenerator);
            var cachekey = cacheKeyGenerator.MakeCacheKey(actionExecutedContext.ActionContext, _responseMediaType, CacheType, ExcludeQueryStringFromCacheKey);
            if (!string.IsNullOrWhiteSpace(cachekey) && !(_webCache.Contains(cachekey)))
            {
                SetEtag(actionExecutedContext.Response, Guid.NewGuid().ToString());
                if (actionExecutedContext.Response.Content != null)
                {
                    var data = CreateCacheUser();
                    data.Content = await actionExecutedContext.Response.Content.ReadAsByteArrayAsync();
                    data.ContentType = actionExecutedContext.Response.Content.Headers.ContentType.MediaType;
                    data.ETag = actionExecutedContext.Response.Headers.ETag.Tag;
                    data.Date = DateTime.Now;
                    _webCache.Add(cachekey, data);
                }
            }
            ApplyCacheHeaders(actionExecutedContext.ActionContext.Response);
        }
        private void ApplyCacheHeaders(HttpResponseMessage response)
        {
            if (MustRevalidate)
            {
                response.Headers.CacheControl = new CacheControlHeaderValue
                {
                    MustRevalidate = MustRevalidate
                };
            }
        }
        private static void SetEtag(HttpResponseMessage message, string etag)
        {
            if (etag != null)
            {
                message.Headers.ETag = new EntityTagHeaderValue(@"""" + etag.Replace("\"", string.Empty) + @"""");
            }
        }
        Task<HttpResponseMessage> IActionFilter.ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
        {
            if (actionContext == null)
            {
                throw new ArgumentNullException("actionContext");
            }
            if (continuation == null)
            {
                throw new ArgumentNullException("continuation");
            }
            OnActionExecuting(actionContext);
            if (actionContext.Response != null)
            {
                return Task.FromResult(actionContext.Response);
            }
            return CallOnActionExecutedAsync(actionContext, cancellationToken, continuation);
        }
        private async Task<HttpResponseMessage> CallOnActionExecutedAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
        {
            cancellationToken.ThrowIfCancellationRequested();
            HttpResponseMessage response = null;
            Exception exception = null;
            try
            {
                response = await continuation();
            }
            catch (Exception e)
            {
                exception = e;
            }
            try
            {
                var executedContext = new HttpActionExecutedContext(actionContext, exception) { Response = response };
                await OnActionExecuted(executedContext);
                if (executedContext.Response != null)
                {
                    return executedContext.Response;
                }
                if (executedContext.Exception != null)
                {
                    ExceptionDispatchInfo.Capture(executedContext.Exception).Throw();
                }
            }
            catch (Exception e)
            {
                actionContext.Response = null;
                ExceptionDispatchInfo.Capture(e).Throw();
            }
            throw new InvalidOperationException(GetType().Name);
        }
    }
}
 |