Кэширование WebAPI 2

1

EDIT: для каждого запроса создается новый экземпляр контроллера. Однако это не относится к классам атрибутов. После их создания он используется для нескольких запросов. Я надеюсь, что это помогает.

Я написал свой собственный WebAPI (с использованием последней версии WebAPI и.net framework) фильтра фильтрации кеширования. Я знаю о CacheCow и этом. Тем не менее, я хотел, чтобы я все равно.

Тем не менее, есть некоторые проблемы с моим кодом, потому что я не получаю exepected вывод, когда я использую его в своем проекте на реальном сервере. На локальной машине все работает нормально.

Я использовал ниже код в своем блоге RSS-генераторе и я кэширую данные для каждой категории. Существует около 5 категорий (продукты питания, технологии, личные и т.д.).

Проблема. Когда я перехожу к api/GetTech, он возвращает мне элементы RSS-ленты из личной категории блога. Когда я перехожу к api/GetPersonal, он возвращает мне api/Food

Я не могу найти основную причину, но я думаю, что это связано с использованием статического метода/переменной. Я дважды проверил, что мой _cachekey имеет уникальное значение для каждой категории моего блога.

Может ли кто-нибудь указать какие-либо проблемы с этим кодом esp, если мы скажем 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));
                }

            }

        }



    }

  • 0
    «Я думал, что когда у класса есть закрытая переменная, скажем, у нас есть 2 экземпляра класса, каждый из которых будет иметь свою собственную память и свои собственные переменные». - Да, это правда, я ввел вас в заблуждение. Тем не менее, вы не имеете дело с 2 экземплярами в этом случае. Вы имеете дело с 1 экземпляром, используемым двумя или более различными потоками запросов.
  • 0
    Ага. И я думаю, что мне не нужна блокировка в коде, так как MemoryCache является потокобезопасным. Правильно ли мое понимание?
Показать ещё 1 комментарий
Теги:
asp.net-mvc
asp.net-web-api

1 ответ

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 cache при развертывании на реальном сервере.

Несмотря на то, что ваш код сбрасывает _cachekey экземпляра _cachekey во время каждого запроса, эти атрибуты не похожи на контроллеры, где для каждого запроса создается новый. Вместо этого экземпляр атрибута может быть переназначен для обслуживания нескольких одновременных запросов. Поэтому не используйте поле экземпляра для его сохранения, регенерируйте его на основе запроса каждый раз, когда вам это нужно.

  • 0
    Я не понимаю Скажем, у меня есть элемент кэша с ключом "TechCategory", а затем для следующих всех запросов я просто проверю объект MemoryCache, чтобы увидеть, есть ли у меня эти данные для ключа "TechCategory". если он существует, вернуть результат из кэша. Что не так с моим кодом?
  • 0
    Неправильно то, что _cacheKey можно установить по запросу A, а затем повторно использовать по запросу B, поскольку один экземпляр атрибута можно использовать для обслуживания нескольких одновременных запросов. Вместо того, чтобы устанавливать это поле и использовать его повторно, обновляйте его на основе запроса каждый раз, когда вам нужно его использовать.
Показать ещё 5 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню