日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

谈谈ASP.NET Core中的ResponseCaching

發布時間:2023/12/4 asp.net 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 谈谈ASP.NET Core中的ResponseCaching 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

前面的博客談的大多數都是針對數據的緩存,今天我們來換換口味。來談談在ASP.NET Core中的ResponseCaching,與ResponseCaching關聯密切的也就是常說的HTTP緩存。

在閱讀本文內容之前,默認各位有HTTP緩存相關的基礎,主要是Cache-Control相關的。

這里也貼兩篇相關的博客:

  • 透過瀏覽器看HTTP緩存

  • HTTP協議 (四) 緩存

回到正題,對于ASP.NET Core中的ResponseCaching,本文主要講三個相關的小內容

  • 客戶端(瀏覽器)緩存

  • 服務端緩存

  • 靜態文件緩存

  • 客戶端(瀏覽器)緩存

    這里主要是通過設置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();

    }

    當然,僅有這兩句代碼,并不能完成這里提到的服務端緩存。還需要前面客戶端緩存的設置,兩者結合起來才能起作用。

    可以看看下面的效果,

    簡單解釋一下這張圖,

  • 第一次刷新的時候,會進入中間件,然后進入Action,返回結果,Fiddler記錄到了這一次的請求

  • 第二次打開新標簽頁,直接從瀏覽器緩存中返回的結果,即沒有進入中間件,也沒有進入Action,Fiddler也沒有記錄到相關請求

  • 第三次換了一個瀏覽器,會進入中間件,直接由緩存返回結果,并沒有進入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中間件對下面的情況是不會進行緩存操作的!

  • 一個請求的Status Code不是200

  • 一個請求的Method不是GETHEAD

  • 一個請求的Header包含Authorization

  • 一個請求的Header包含Set-Cookie

  • 一個請求的Header包含僅有值為*的Vary

  • ...

  • 其二,當我們使用了Antiforgery的時候也要特別的注意!!它會直接把響應頭部的Cache-Control和Pragma重置成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

    創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

    總結

    以上是生活随笔為你收集整理的谈谈ASP.NET Core中的ResponseCaching的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。