谈谈ASP.NET Core中的ResponseCaching

前言

前面的博客谈的大多数都是针对数据的缓存,今天我们来换换口味。来谈谈在ASP.NET Core中的ResponseCaching,与ResponseCaching关联密切的也就是常说的HTTP缓存。

在阅读本文内容之前,默认各位有HTTP缓存相关的基础,主要是Cache-Control相关的。

这里也贴两篇相关的博客:

  • 透过浏览器看HTTP缓存

  • HTTP协议 (四) 缓存

回到正题,对于ASP.NET Core中的ResponseCaching,本文主要讲三个相关的小内容

  1. 客户端(浏览器)缓存

  2. 服务端缓存

  3. 静态文件缓存

客户端(浏览器)缓存

这里主要是通过设置HTTP的响应头来完成这件事的。方法主要有两种:

其一,直接用Response对象去设置。

这种方式也有两种写法,示例代码如下:

public IActionResult Index()

{

    //直接一,简单粗暴,不要拼写错了就好~~

    Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.CacheControl] = "public, max-age=600";

    

    //直接二,略微优雅点

    //Response.GetTypedHeaders().CacheControl = new Microsoft.Net.Http.Headers.CacheControlHeaderValue()

    //{

    //    Public = true,

    //    MaxAge = TimeSpan.FromSeconds(600)

    //};


    return View();

}

这两者效果是一样的,大致如下:

它们都会给响应头加上 Cache-Control: public, max-age=600,可能有人会问,加上这个有什么用?

那我们再来看张动图,应该会清晰不少。

这里事先在代码里面设置了一个断点,正常情况下,只要请求这个action都是会进来的。

但是从上图可以发现,只是第一次才进了断点,其他直接打开的都没有进,而是直接返回结果给我们了,这也就说明缓存起作用了。

同样的,再来看看下面的图,from disk cache也足以说明,它并没有请求到服务器,而是直接从本地返回的结果。

注:如果是刷新的话,还是会进断点的。这里需要区分好刷新,地址栏回车等行为。不同浏览器也有些许差异,这里可以用fiddler和postman来模拟。

在上面的做法中,我们将设置头部信息的代码和业务代码混在一起了,这显然不那么合适。

下面来看看第二种方法,也是比较推荐的方法。

其二,用ResponseCacheAttribute去处理缓存相关的事情。

对于和上面的同等配置,只需要下面这样简单设置一个属性就可以了。

[ResponseCache(Duration = 600)]
public IActionResult Index(){    
       return View(); }

效果和上面是一致的!处理起来是不是简单多了。

既然这两种方式都能完成一样的效果,那么ResponseCache这个Attribute本质也是往响应头写了相应的值。

但是我们知道,纯粹的Attribute并不能完成这一操作,其中肯定另有玄机!

翻了一下源码,可以看到它实现了IFilterFactory这个关键的接口。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]

public class ResponseCacheAttribute : Attribute, IFilterFactory, IOrderedFilter

{

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)

    {

        //..

        

        return new ResponseCacheFilter(new CacheProfile

        {

            Duration = _duration,

            Location = _location,

            NoStore = _noStore,

            VaryByHeader = VaryByHeader,

            VaryByQueryKeys = VaryByQueryKeys,

        });

    }

}

也就是说,真正起作用的是ResponseCacheFilter这个Filter,核心代码如下:

public void OnActionExecuting(ActionExecutingContext context)

{

    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 (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

    {

        headers[HeaderNames.CacheControl] = cacheControlValue;

    }

}

它的本质自然就是给响应头部写了一些东西。

通过上面的例子已经知道了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" }

    });

});


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

示例代码如下:

[ResponseCache(CacheProfileName = "default")]

public IActionResult Index()

{

    return View();

}

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

但是这个属性的使用需要结合下一小节的内容,所以这里就不展开了。

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

服务端缓存

先简单解释一下这里的服务端缓存是什么,对比前面的客户端缓存,它是将东西存放在客户端,要用的时候就直接从客户端去取!

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

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

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

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

public void ConfigureServices(IServiceCollection services)

{

    services.AddResponseCaching();

}


public void Configure(IApplicationBuilder app, IHostingEnvironment env)

{

    app.UseResponseCaching();

}

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

可以看看下面的效果,

简单解释一下这张图,

  1. 第一次刷新的时候,会进入中间件,然后进入Action,返回结果,Fiddler记录到了这一次的请求

  2. 第二次打开新标签页,直接从浏览器缓存中返回的结果,即没有进入中间件,也没有进入Action,Fiddler也没有记录到相关请求

  3. 第三次换了一个浏览器,会进入中间件,直接由缓存返回结果,并没有进入Action,此时Fiddler也将该请求记录了下来,响应头包含了Age

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

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

这个Age是在变化的!它就等价于缓存的寿命。

如果启用了日志,也会看到一些比较重要的日记信息。

在上一小节中,我们还有提到ResponseCacheAttribute中的VaryByQueryKeys这个属性,它需要结合ResponseCaching中间件一起用的,这点在注释中也是可以看到的!

//

// Summary:

//     Gets or sets the query keys to vary by.

//

// Remarks:

//     Microsoft.AspNetCore.Mvc.ResponseCacheAttribute.VaryByQueryKeys requires the

//     response cache middleware.

public string[] VaryByQueryKeys { get; set; }

举个例子(不一定很合适)来看看,假设现在有一个电影列表页面(http://localhost:5001),可以通过在URL地址上面加查询参数来决定显示第几页的数据。

如果代码是这样写的,

[ResponseCache(Duration = 600)]

public IActionResult List(int page = 0)

{

    return Content(page.ToString());

}

结果就会像下面这样,三次请求,返回的都是页码为0的结果!page参数,压根就没起作用!

GET http://localhost:5001/Home/List HTTP/1.1

Host: localhost:5001


HTTP/1.1 200 OK

Date: Thu, 05 Apr 2018 07:38:51 GMT

Content-Type: text/plain; charset=utf-8

Server: Kestrel

Content-Length: 1

Cache-Control: public,max-age=600


0


GET http://localhost:5001/Home/List?page=2 HTTP/1.1

Host: localhost:5001


HTTP/1.1 200 OK

Date: Thu, 05 Apr 2018 07:38:51 GMT

Content-Type: text/plain; charset=utf-8

Server: Kestrel

Content-Length: 1

Cache-Control: public,max-age=600

Age: 5


0


GET http://localhost:5001/Home/List?page=5 HTTP/1.1

Host: localhost:5001


HTTP/1.1 200 OK

Date: Thu, 05 Apr 2018 07:38:51 GMT

Content-Type: text/plain; charset=utf-8

Server: Kestrel

Content-Length: 1

Cache-Control: public,max-age=600

Age: 8


0


正确的做法应该是要指定VaryByQueryKeys,如下所示:

[ResponseCache(Duration = 600, VaryByQueryKeys = new string[] { "page" })]

public IActionResult List(int page = 0)

{

    return Content(page.ToString());

}


这个时候的结果就是和预期的一样了,不同参数都有对应的结果并且这些数据都缓存了起来。

GET http://localhost:5001/Home/List HTTP/1.1Host: localhost:5001HTTP/1.1 200 OK Date: Thu, 05 Apr 2018 07:45:13 GMT Content-Type: text/plain; charset=utf-8 Server: Kestrel Content-Length: 1Cache-Control: public,max-age=6000GET http://localhost:5001/Home/List?page=2 HTTP/1.1Host: localhost:5001HTTP/1.1 200 OKDate: Thu, 05 Apr 2018 07:45:22 GMTContent-Type: text/plain; charset=utf-8 Server: Kestrel Content-Length: 1Cache-Control: public,max-age=6002GET http://localhost:5001/Home/List?page=5 HTTP/1.1Host: localhost:5001HTTP/1.1 200 OKDate: Thu, 05 Apr 2018 07:45:27 GMTContent-Type: text/plain; charset=utf-8 Server: Kestrel Content-Length: 1Cache-Control: public,max-age=6005

ResponseCachingMiddleware在这里是用了MemoryCache来读写缓存数据的。如果应用重启了,缓存的数据就会失效,要重新来过。

静态文件缓存

对于一些常年不变或比较少变的js,css等静态文件,也可以把它们缓存起来,避免让它们总是发起请求到服务器,而且这些静态文件可以缓存更长的时间!

如果已经使用了CDN,这一小节的内容就可以暂且忽略掉了。。。

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

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

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

//

// Summary:

//     Called after the status code and headers have been set, but before the body has

//     been written. This can be used to add or change the response headers.

public Action<StaticFileResponseContext> OnPrepareResponse { get; set; }


下面来看个简单的例子:

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)

        };

    }

});


此时的效果如下:

一些需要注意的地方

其一,ResponseCaching中间件对下面的情况是不会进行缓存操作的!

  1. 一个请求的Status Code不是200

  2. 一个请求的Method不是GETHEAD

  3. 一个请求的Header包含Authorization

  4. 一个请求的Header包含Set-Cookie

  5. 一个请求的Header包含仅有值为*的Vary

  6. ...

其二,当我们使用了Antiforgery的时候也要特别的注意!!它会直接把响应头部的Cache-ControlPragma重置成no-cache。换句话说,这两者是水火不容的!

详情可见DefaultAntiforgery.cs#L381

/// <summary>

/// Sets the 'Cache-Control' header to 'no-cache, no-store' and 'Pragma' header to 'no-cache' overriding any user set value.

/// </summary>

/// <param name="httpContext">The <see cref="HttpContext"/>.</param>

protected virtual void SetDoNotCacheHeaders(HttpContext httpContext)

{

    // Since antifogery token generation is not very obvious to the end users (ex: MVC's form tag generates them

    // by default), log a warning to let users know of the change in behavior to any cache headers they might

    // have set explicitly.

    LogCacheHeaderOverrideWarning(httpContext.Response);


    httpContext.Response.Headers[HeaderNames.CacheControl] = "no-cache, no-store";

    httpContext.Response.Headers[HeaderNames.Pragma] = "no-cache";

}

当然,在某个页面用到了Antiforgery的时候,也该避免在这个页面使用HTTP缓存!

它会在form表单中生成一个隐藏域,并且隐藏域的值是一个生成的token ,难道还想连这个一起缓存?

总结

在.NET Core中用ResponseCaching还是比较简单的,虽然还有一些值得注意的地方,但是并不影响我们的正常使用。

当然,最重要的还是合理使用!仅在需要的地方使用!

最后附上文中Demo的地址 :https://github.com/catcherwong/Demos/tree/master/src/ResponseCachingDemo

原文地址 https://www.cnblogs.com/catcher1994/p/responsecaching.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/321711.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用 dynamic 类型让 ASP.NET Core 实现 HATEOAS 结构的 RESTful API

上一篇写的是使用静态基类方法的实现步骤: 使用dynamic (ExpandoObject)的好处就是可以动态组建返回类型, 之前使用的是ViewModel, 如果想返回结果的话, 肯定需要把ViewModel所有的属性都返回, 如果属性比较多, 就有可能造成性能和灵活性等问题. 而使用ExpandoObject(dynamic)就…

使用 BenchmarkDotnet 测试代码性能

先来点题外话&#xff0c;清明节前把工作辞了&#xff08;去 tm 的垃圾团队&#xff0c;各种拉帮结派、勾心斗角&#xff09;。这次找工作就得慢慢找了&#xff0c;不能急了&#xff0c;希望能找到个好团队&#xff0c;好岗位吧。顺便这段时间也算是比较闲&#xff0c;也能学习…

2017西安交大ACM小学期数论 [阅兵式]

阅兵式 发布时间: 2017年6月25日 12:53 最后更新: 2017年7月3日 09:27 时间限制: 1000ms 内存限制: 128M 描述 阅兵式上&#xff0c;将士们排成一个整齐的方阵&#xff0c;每个将士面朝前方。问正中心的将士能向前看到几个将士&#xff1f;注意&#xff0c;一条直线上的将…

28、jdbc操作数据库(5)

介绍一个稍微封装了jdbc的工具类org.apache.commons.dbutils&#xff0c;使用dbutils可以简化对数据库操作程序的开发。 API介绍 接下来通过实例的方式说一下dbutils的具体使用 添加jar包&#xff1a;commons-dbutils-1.7.jar 增、删、改 进行增、删、改操作&#xff0c;在…

2017西安交大ACM小学期数论 [等差数列]

等差数列 发布时间: 2017年6月25日 13:42 最后更新: 2017年7月3日 09:27 时间限制: 1000ms 内存限制: 128M 描述 给定正整数n&#xff0c;试问存在多少个和为n的等差数列&#xff1f; 当然&#xff0c;等差数列中每一项要为非负整数&#xff0c;且不考虑降序的等差数列。…

上古时期(大雾)的数据结构pdf

分块点分治Treap byWYCby\ WYCby WYC Part1 分块 概念 就是将nnn个数分成若干个块&#xff0c;然后要处理的时候整块一起的加上局部的直接暴力。 如果将块的大小分配好一般每次都是O(n)O(\sqrt n)O(n​)的。 而且因为十分暴力&#xff0c;所以有很多优秀的性质。 实现方法 …

33、JAVA_WEB开发基础之会话机制

会话是什么 一个客户端浏览器与web服务器之间连续发生的一系列请求和响应过程就是会话&#xff0c;这些过程中产生的一系列信息就是会话信息&#xff0c;会话机制就是用于维护这些信息一致性的一种技术。通俗的说就是&#xff0c;一个A账号访问服务器&#xff0c;进行多次交互…

35、JAVA_WEB开发基础之过滤器

是什么 过滤器javaweb的一个重要组件&#xff0c;一种规范&#xff0c;可以对发送到serlvet的请求进行拦截和响应进行过滤。实际开发中可以使用过滤器来对访问服务器的请求进行过滤&#xff0c;以提高安全性 过滤器的原理 可以配置过滤器对指定的请求进行过滤&#xff0c;就…

2、安装和连接mysql

安装mysql 1、官网下载mysql 下载网址&#xff1a;https://www.mysql.com/ 2、解压并配置mysql 解压下载的&#xff08;前提下载的zip版本的mysql&#xff09;mysql安装包&#xff0c;放到指定磁盘 配置环境变量&#xff1a;将mysql下的bin目录的全路径名配置到环境变量的p…

6、mysql中字段

对数据表的操作是比较重要的&#xff0c;在实际开发中&#xff0c;日常做的主要工作就是对数据表的操作 对数据表的操作分为两大部分&#xff1a;操作数据表的结构、操作数据表中的数据 组成数据表的基本单元就是字段&#xff0c;所以&#xff0c;接下来先介绍一下mysql中的字…

在Linux环境下使用Apache部署ASP.NET Core

在前几篇文章中我们一起探讨了如何在Linux环境中安装ASP.NET Core运行时环境及将ASP.NET Core项目部署在Jexus中&#xff0c;这篇文章中我们将探讨如何将ASP.NET Core部署于Apache&#xff08;阿帕奇&#xff09;中。 很幸运能够和大家一起学习和探讨ASP.NET Core本文章运行…

傲娇码农的自我修养

一个热爱自己职业的人一定会对自己的工作充满自豪感&#xff0c;同样&#xff0c;也应该对自己的工作充满热情和自信。对自己的专业能力骄傲而不自满。身为一个码农&#xff0c;如果你热爱自己的工作&#xff0c;我想&#xff0c;你很有可能也是一位傲娇码农。在我的眼里&#…

中国到底有多少个.NET 程序员?都在哪个城市写代码?

中国到底多少个.NET 程序员&#xff0c;对于这个问题&#xff0c;似乎没有一个准确的答案&#xff0c;而且最近很多使用.NET 开发技术的老板在抱怨找不到.NET 开发人员&#xff0c;所以我想基于我的公众号粉丝数据给大家分享下中国的.NET程序员到底有多少&#xff0c;他们也都是…

微软西雅图总部DevOps交流总结

本文转自Study4台湾社区。Study4台湾社区&#xff0c;成立于2011/9/25&#xff0c;希望藉由社群推广的力量&#xff0c;让台下的朋友听到来自不同县市的大师讲课&#xff0c;也让台上年轻一辈的技术传教士能不断的琢磨并且追上大师这是一个社群&#xff0c;社区希望透过分享&am…

C# 快速高效率复制对象另一种方式 表达式树

一、需求在代码中经常会遇到需要把对象复制一遍&#xff0c;或者把属性名相同的值复制一遍。比如&#xff1a;public class Student{public int Id { get; set; }public string Name { get; set; } public int Age { get; set; } }public class StudentSecond{public int Id { …

用C# (.NET Core) 实现抽象工厂设计模式

本文的概念性内容来自深入浅出设计模式一书.上一篇文章讲了简单工厂和工厂方法设计模式 使用的是披萨店的例子. 文将继续使用这个例子, 这里要用到抽象工厂.披萨店的需求变更现在披萨店在各地授权了很多连锁分店, 但是有的分店偷工减料, 使用劣质原料代替标准原料.披萨店老板现…

14、mysql中事务的应用

是什么 事务是一种保护连续操作同时满足&#xff08;实现&#xff09;的一种机制&#xff0c;用来保护数据的完整性&#xff0c;只适用于数据操作&#xff0c;不适用于结构操作&#xff0c;只有 innodb引擎的表具有事务安全的机制。就是说&#xff0c;在一个事务中做一系列的…

Summer Training day4 欧拉降幂

Input2 Output2Hint 1. For N 2, S(1) S(2) 1.2. The input file consists of multiple test cases. Sample Input2 Sample Output2这道题的公式非常简单&#xff0c;就是求2^(N-1) %1e97 由于N实在是太大了&#xff0c;不能直接求快速幂&#xff0c;考虑到2^x % MOD是有循…

Project Honolulu 正式版发布为 Windows Admin Center

微软今天正式发布了 Project Honolulu 的正式版&#xff0c;其正式的名称为 Windows Admin Center&#xff0c;gOxiA 之前一直在这个 TAP 中&#xff0c;从 1711 到 1804 可以看出微软现在的开发速度之快&#xff0c;从测试情况看 WAC 质量非常高。正如之前日志说讲 Windows Ad…

使用C# (.NET Core) 实现命令设计模式 (Command Pattern)

本文的概念内容来自深入浅出设计模式一书.项目需求有这样一个可编程的新型遥控器, 它有7个可编程插槽, 每个插槽可连接不同的家用电器设备. 每个插槽对应两个按钮: 开, 关(ON, OFF). 此外还有一个全局的取消按钮(UNDO).现在客户想使用这个遥控器来控制不同厂家的家用电器, 例如…