Caching WebAPI 2

本文关键字:WebAPI Caching | 更新日期: 2023-09-27 18:29:27

编辑:对于每个请求,都会创建一个新的控制器实例。但是,对于Attribute类,情况并非如此。一旦创建了它们,它就会用于多个请求。我希望它能有所帮助

我编写了自己的WebAPI(使用最新版本的WebAPI和.net框架)缓存操作过滤器。我知道CacheCow&这不管怎样,我还是想要我的。

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

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

问题:当我导航到sayapi/GetTech时,它会返回我个人博客类别中的rss提要项目。当我导航到说api/GetPersonal时,它会返回给我api/Food

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

有人能指出这个代码的任何问题吗?特别是当我们说每分钟有300个请求时?

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));
                }
            }
        }

    }

Caching WebAPI 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实例字段,这些属性也不像为每个请求创建新属性的控制器。相反,可以重新调整属性实例的用途,为多个同时请求提供服务。因此,不要使用实例字段来存储它,而是在每次需要时根据请求重新生成它