缓存Web API 2

时间:2014-11-06 13:22:57

标签: c# asp.net-mvc asp.net-web-api

编辑:对于每个请求,都会创建一个新的控制器实例。但是,属性类不是这样。创建它们后,它将用于多个请求。我希望它有所帮助。

我编写了自己的WebAPI(使用最新版本的WebAPI和.net框架)缓存操作过滤器。我知道CacheCow& this。但是,无论如何我想要我的。

但是,我的代码存在一些问题,因为当我在实时服务器上的项目中使用它时,我没有得到预期的输出。在本地机器上一切正常。

我在我的博客RSS生成器中使用了以下代码,并且我缓存了每个类别的数据。大约有5个类别(食品,科技,个人等)。

问题:当我导航到说api / GetTech时,它会从个人博客类别返回rss Feed项目。当我导航到说api / GetPersonal时,它会返回api / Food

我无法找到根本原因,但我认为这是由于使用静态方法/变量。我已经仔细检查过我的_cachekey对我博客的每个类别都有独特的价值。

当我们每分钟发出300个请求时,是否可以指出此代码esp的任何问题?

public class WebApiOutputCacheAttribute : ActionFilterAttribute
    {
        // Cache timespan
        private readonly int _timespan;

        // cache key
        private string _cachekey;

        // cache repository
        private static readonly MemoryCache _webApiCache = MemoryCache.Default;
        /// <summary>
        /// Initializes a new instance of the <see cref="WebApiOutputCacheAttribute"/> class.
        /// </summary>
        /// <param name="timespan">The timespan in seconds.</param>
        public WebApiOutputCacheAttribute(int timespan)
        {
            _timespan = timespan;
        }

        public override void OnActionExecuting(HttpActionContext ac)
        {
            if (ac != null)
            {
                _cachekey = ac.Request.RequestUri.PathAndQuery.ToUpperInvariant();

                if (!_webApiCache.Contains(_cachekey)) return;

                var val = (string)_webApiCache.Get(_cachekey);

                if (val == null) return;

                ac.Response = ac.Request.CreateResponse();
                ac.Response.Content = new StringContent(val);
                var contenttype = (MediaTypeHeaderValue)_webApiCache.Get("response-ct") ?? new MediaTypeHeaderValue("application/rss+xml");
                ac.Response.Content.Headers.ContentType = contenttype;
            }
            else
            {
                throw new ArgumentNullException("ac");
            }
        }




        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            if (_webApiCache.Contains(_cachekey)) return;
            var body = actionExecutedContext.Response.Content.ReadAsStringAsync().Result;
            if (actionExecutedContext.Response.StatusCode == HttpStatusCode.OK)
            {
                lock (WebApiCache)
                {
                    _wbApiCache.Add(_cachekey, body, DateTime.Now.AddSeconds(_timespan));
                    _webApiCache.Add("response-ct", actionExecutedContext.Response.Content.Headers.ContentType, DateTimeOffset.UtcNow.AddSeconds(_timespan));
                }

            }

        }



    }

1 个答案:

答案 0 :(得分:2)

相同的WebApiOutputCacheAttribute实例可用于缓存多个并发请求,因此您不应将缓存键存储在属性的实例上。而是在每个请求/方法覆盖期间重新生成缓存键。以下属性用于缓存HTTP GET请求。

using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using Newtonsoft.Json;

// based on strathweb implementation
// http://www.strathweb.com/2012/05/output-caching-in-asp-net-web-api/
public class CacheHttpGetAttribute : ActionFilterAttribute
{
    public int Duration { get; set; }

    public ILogExceptions ExceptionLogger { get; set; }
    public IProvideCache CacheProvider { get; set; }

    private bool IsCacheable(HttpRequestMessage request)
    {
        if (Duration < 1)
            throw new InvalidOperationException("Duration must be greater than zero.");

        // only cache for GET requests
        return request.Method == HttpMethod.Get;
    }

    private CacheControlHeaderValue SetClientCache()
    {
        var cachecontrol = new CacheControlHeaderValue
        {
            MaxAge = TimeSpan.FromSeconds(Duration),
            MustRevalidate = true,
        };
        return cachecontrol;
    }

    private static string GetServerCacheKey(HttpRequestMessage request)
    {
        var acceptHeaders = request.Headers.Accept;
        var acceptHeader = acceptHeaders.Any() ? acceptHeaders.First().ToString() : "*/*";
        return string.Join(":", new[]
        {
            request.RequestUri.AbsoluteUri,
            acceptHeader,
        });
    }

    private static string GetClientCacheKey(string serverCacheKey)
    {
        return string.Join(":", new[]
        {
            serverCacheKey,
            "response-content-type",
        });
    }

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (actionContext == null) throw new ArgumentNullException("actionContext");
        var request = actionContext.Request;
        if (!IsCacheable(request)) return;

        try
        {
            // do NOT store cache keys on this attribute because the same instance
            // can be reused for multiple requests
            var serverCacheKey = GetServerCacheKey(request);
            var clientCacheKey = GetClientCacheKey(serverCacheKey);

            if (CacheProvider.Contains(serverCacheKey))
            {
                var serverValue = CacheProvider.Get(serverCacheKey);
                var clientValue = CacheProvider.Get(clientCacheKey);
                if (serverValue == null) return;

                var contentType = clientValue != null
                    ? JsonConvert.DeserializeObject<MediaTypeHeaderValue>(clientValue.ToString())
                    : new MediaTypeHeaderValue(serverCacheKey.Substring(serverCacheKey.LastIndexOf(':') + 1));

                actionContext.Response = actionContext.Request.CreateResponse();

                // do not try to create a string content if the value is binary
                actionContext.Response.Content = serverValue is byte[]
                    ? new ByteArrayContent((byte[])serverValue)
                    : new StringContent(serverValue.ToString());

                actionContext.Response.Content.Headers.ContentType = contentType;
                actionContext.Response.Headers.CacheControl = SetClientCache();
            }
        }
        catch (Exception ex)
        {
            ExceptionLogger.Log(ex);
        }
    }

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        try
        {
            var request = actionExecutedContext.Request;

            // do NOT store cache keys on this attribute because the same instance
            // can be reused for multiple requests
            var serverCacheKey = GetServerCacheKey(request);
            var clientCacheKey = GetClientCacheKey(serverCacheKey);
            if (!CacheProvider.Contains(serverCacheKey))
            {
                var contentType = actionExecutedContext.Response.Content.Headers.ContentType;
                object serverValue;
                if (contentType.MediaType.StartsWith("image/"))
                    serverValue = actionExecutedContext.Response.Content.ReadAsByteArrayAsync().Result;
                else
                    serverValue = actionExecutedContext.Response.Content.ReadAsStringAsync().Result;
                var clientValue = JsonConvert.SerializeObject(
                    new
                    {
                        contentType.MediaType,
                        contentType.CharSet,
                    });
                CacheProvider.Add(serverCacheKey, serverValue, new TimeSpan(0, 0, Duration));
                CacheProvider.Add(clientCacheKey, clientValue, new TimeSpan(0, 0, Duration));
            }

            if (IsCacheable(actionExecutedContext.Request))
                actionExecutedContext.ActionContext.Response.Headers.CacheControl = SetClientCache();
        }
        catch (Exception ex)
        {
            ExceptionLogger.Log(ex);
        }
    }
}

只需将CacheProvider替换为MemoryCache.Default即可。实际上,上面的代码在开发过程中默认使用相同的代码,并在部署到实时服务器时使用azure缓存。

即使您的代码在每个请求期间重置_cachekey实例字段,这些属性也不像为每个请求创建新属性的控制器。相反,可以将属性实例重新用于为多个同时请求提供服务。因此,不要使用实例字段来存储它,每次需要时都会根据请求重新生成它。