1. 程式人生 > 實用技巧 >ASP.NET Core 中的響應快取中介軟體

ASP.NET Core 中的響應快取中介軟體

客戶端(瀏覽器)快取

通過設定HTTP的響應頭來完成

1、直接用Response物件去設定

[HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            Console.WriteLine("服務響應");
            //直接一,簡單粗暴,不要拼寫錯了就好~~
            Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.CacheControl] = "public, max-age=600";

            
var rng = new Random(); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); }
View Code

檢視http響應頭

上面的示例設定客戶端快取600秒,如果直接重新整理瀏覽器或者按F5進行重新整理,快取會失效(cache-control對重新整理無效)

2、用ResponseCacheAttribute類設定快取

  [HttpGet]
        [ResponseCache(Duration = 100)]
        public IEnumerable<WeatherForecast> Get()
        {
            Console.WriteLine("服務響應");
            ////直接一,簡單粗暴,不要拼寫錯了就好~~
//Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.CacheControl] = "public, max-age=600"; var rng = new Random(); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); }
View Code

效果和上面是一致的,通過原始碼分析發現ResponseCacheAttribute也是通過設定http頭來實現的。

 /// <summary>
    /// Specifies the parameters necessary for setting appropriate headers in response caching.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public class ResponseCacheAttribute : Attribute, IFilterFactory, IOrderedFilter
    {
        
        /// <inheritdoc />
        public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
        {
            if (serviceProvider == null)
            {
                throw new ArgumentNullException(nameof(serviceProvider));
            }

            var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
            var optionsAccessor = serviceProvider.GetRequiredService<IOptions<MvcOptions>>();
            var cacheProfile = GetCacheProfile(optionsAccessor.Value);

            // ResponseCacheFilter cannot take any null values. Hence, if there are any null values,
            // the properties convert them to their defaults and are passed on.
            return new ResponseCacheFilter(cacheProfile, loggerFactory);
        }
    }
View Code

ResponseCacheFilter部分程式碼如下

/// <summary>
    /// An <see cref="IActionFilter"/> which sets the appropriate headers related to response caching.
    /// </summary>
    internal class ResponseCacheFilter : IActionFilter, IResponseCacheFilter
    {
         
        /// <inheritdoc />
        public void OnActionExecuting(ActionExecutingContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            // If there are more filters which can override the values written by this filter,
            // then skip execution of this filter.
            var effectivePolicy = context.FindEffectivePolicy<IResponseCacheFilter>();
            if (effectivePolicy != null && effectivePolicy != this)
            {
                _logger.NotMostEffectiveFilter(GetType(), effectivePolicy.GetType(), typeof(IResponseCacheFilter));
                return;
            }

            _executor.Execute(context);
        }

         
    }
View Code

具體的實現是在ResponseCacheFilterExecutor類中,程式碼如下

internal class ResponseCacheFilterExecutor
    {
       
        public void Execute(FilterContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (!NoStore)
            {
                // Duration MUST be set (either in the cache profile or in this filter) unless NoStore is true.
                if (_cacheProfile.Duration == null && _cacheDuration == null)
                {
                    throw new InvalidOperationException(
                        Resources.FormatResponseCache_SpecifyDuration(nameof(NoStore), nameof(Duration)));
                }
            }

            var headers = context.HttpContext.Response.Headers;

            // Clear all headers
            headers.Remove(HeaderNames.Vary);
            headers.Remove(HeaderNames.CacheControl);
            headers.Remove(HeaderNames.Pragma);

            if (!string.IsNullOrEmpty(VaryByHeader))
            {
                headers[HeaderNames.Vary] = VaryByHeader;
            }

            if (VaryByQueryKeys != null)
            {
                var responseCachingFeature = context.HttpContext.Features.Get<IResponseCachingFeature>();
                if (responseCachingFeature == null)
                {
                    throw new InvalidOperationException(
                        Resources.FormatVaryByQueryKeys_Requires_ResponseCachingMiddleware(nameof(VaryByQueryKeys)));
                }
                responseCachingFeature.VaryByQueryKeys = VaryByQueryKeys;
            }

            if (NoStore)
            {
                headers[HeaderNames.CacheControl] = "no-store";

                // Cache-control: no-store, no-cache is valid.
                if (Location == ResponseCacheLocation.None)
                {
                    headers.AppendCommaSeparatedValues(HeaderNames.CacheControl, "no-cache");
                    headers[HeaderNames.Pragma] = "no-cache";
                }
            }
            else
            {
                string cacheControlValue;
                switch (Location)
                {
                    case ResponseCacheLocation.Any:
                        cacheControlValue = "public,";
                        break;
                    case ResponseCacheLocation.Client:
                        cacheControlValue = "private,";
                        break;
                    case ResponseCacheLocation.None:
                        cacheControlValue = "no-cache,";
                        headers[HeaderNames.Pragma] = "no-cache";
                        break;
                    default:
                        cacheControlValue = null;
                        break;
                }

                cacheControlValue = $"{cacheControlValue}max-age={Duration}";
                headers[HeaderNames.CacheControl] = cacheControlValue;
            }
        }
    }
View Code

通過原始碼分析已經知道了ResponseCacheAttribute運作的基本原理,下面再來看看如何配置出其他不同的效果。