• 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运作的基本原理,下面再来看看如何配置出其他不同的效果。

     ResponseCache的设置  响应头
     [ResponseCache(Duration = 600, Location = ResponseCacheLocation.Client)] Cache-Control: private, max-age=600 
     [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]  Cache-Control:no-cache, no-store
     [ResponseCache(Duration = 60, VaryByHeader = "User-Agent")]  Cache-Control : public, max-age=60
    Vary : User-Agent

    注:如果NoStore没有设置成true,则Duration必须要赋值!

    关于ResponseCacheAttribute,还有一个不得不提的属性:CacheProfileName

    它相当于指定了一个“配置文件”,并在这个“配置文件”中设置了ResponseCache的一些值。

    这个时候,只需要在ResponseCacheAttribute上面指定这个“配置文件”的名字就可以了,而不用在给Duration等属性赋值了。

    在添加MVC这个中间件的时候就需要把这些“配置文件”准备好!

    下面的示例代码添加了两份“配置文件”,其中一份名为default,默认是缓存10分钟,还有一份名为Hourly,默认是缓存一个小时,还有一些其他可选配置也用注释的方式列了出来。

    services.AddMvc(options =>
    {
        options.CacheProfiles.Add("default", new Microsoft.AspNetCore.Mvc.CacheProfile
        {
            Duration = 600,  // 10 min
        });
    
        options.CacheProfiles.Add("Hourly", new Microsoft.AspNetCore.Mvc.CacheProfile
        {
            Duration = 60 * 60,  // 1 hour
            //Location = Microsoft.AspNetCore.Mvc.ResponseCacheLocation.Any,
            //NoStore = true,
            //VaryByHeader = "User-Agent",
            //VaryByQueryKeys = new string[] { "aaa" }
        });
    });
    View Code

    现在“配置文件”已经有了,下面就是使用这些配置了!只需要在Attribute上面指定CacheProfileName的名字就可以了

    [ResponseCache(CacheProfileName = "default")]
    public IActionResult Index()
    {
        return View();
    }
    View Code

    ResponseCacheAttribute中还有一个VaryByQueryKeys的属性,这个属性可以根据不同的查询参数进行缓存!

    注:ResponseCacheAttribute即可以加在类上面,也可以加在方法上面,如果类和方法都加了,会优先采用方法上面的配置。

    服务端缓存

    对比前面的客户端缓存,它是将东西存放在客户端,要用的时候就直接从客户端去取!

    同理,服务端缓存就是将东西存放在服务端,要用的时候就从服务端去取。

    需要注意的是,如果服务端的缓存命中了,那么它是直接返回结果的,也是不会去访问Action里面的内容!有点类似代理的感觉。

    这个相比客户端缓存有一个好处,在一定时间内,“刷新”页面的时候会从这里的缓存返回结果,而不用再次访问Action去拿结果。

    要想启用服务端缓存,需要在管道中去注册这个服务,核心代码就是下面的两句。

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddResponseCaching();
    }
    
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseResponseCaching();
    }
    View Code

    当然,仅有这两句代码,并不能完成这里提到的服务端缓存。还需要前面客户端缓存的设置,两者结合起来才能起作用

    1. 第一次刷新的时候,会进入中间件,然后进入Action,返回结果,Fiddler记录到了这一次的请求
    2. 第二次打开新标签页,直接从浏览器缓存中返回的结果,即没有进入中间件,也没有进入Action,Fiddler也没有记录到相关请求
    3. 第三次换了一个浏览器,会进入中间件,直接由缓存返回结果,并没有进入Action,此时Fiddler也将该请求记录了下来,响应头包含了Age

    注意:服务端缓存针对不同浏览器(不同客户端时)才能测试出效果(此时找不到浏览器缓存)

    第三次请求响应头部的部分信息如下:

    Age: 16
    Cache-Control: public,max-age=600

    这个Age是在变化的!它就等价于缓存的寿命
    还有提到ResponseCacheAttribute中的VaryByQueryKeys这个属性,它需要结合ResponseCaching中间件一起用的
    如果代码是这样写的
      [HttpGet] 
            [Route("[Action]")]
            [ResponseCache(Duration = 600)]
            public IActionResult List(int page = 0)
            {
                return Content(page.ToString());
            }
    View Code

    静态文件缓存

    对于静态文件,.NET Core有一个单独的StaticFiles中间件,如果想要对它做一些处理,同样需要在管道中进行注册。

    UseStaticFiles有几个重载方法,这里用的是带StaticFileOptions参数的那个方法。

    因为StaticFileOptions里面有一个OnPrepareResponse可以让我们修改响应头,以达到HTTP缓存的效果。

    下面来看个简单的例子:

    app.UseStaticFiles(new StaticFileOptions
    {
        OnPrepareResponse = context =>
        {
            context.Context.Response.GetTypedHeaders().CacheControl = new Microsoft.Net.Http.Headers.CacheControlHeaderValue
            { 
                Public = true,
                //for 1 year
                MaxAge = System.TimeSpan.FromDays(365)
            };
        }
    });
    View Code

  • 相关阅读:
    网站色彩搭配
    web前端小知识,安书整理的
    java基础
    简单android UI必会
    java学习总结
    java字符常量与字符串常量的区别
    最近的学习
    简单的ps操作
    HTTP协议概述
    ABP 学习 Setting
  • 原文地址:https://www.cnblogs.com/Duko/p/14124189.html
Copyright © 2020-2023  润新知