{"id":1148,"date":"2025-05-27T14:47:46","date_gmt":"2025-05-27T06:47:46","guid":{"rendered":"https:\/\/www.hyy.net\/?p=1148"},"modified":"2025-05-27T14:47:46","modified_gmt":"2025-05-27T06:47:46","slug":"ultimate-asp-net-core-web-api-25-caching","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=1148","title":{"rendered":"Ultimate ASP.NET Core Web API 25 CACHING"},"content":{"rendered":"<p>25 CACHING<br \/>\n25 \u7f13\u5b58<\/p>\n<p>In this section, we are going to learn about caching resources. Caching can improve the quality and performance of our app a lot, but again, it is something first we need to look at as soon as some bug appears. To cover resource caching, we are going to work with HTTP Cache. Additionally, we are going to talk about cache expiration, validation, and cache-control headers.\u200c<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u5b66\u4e60\u7f13\u5b58\u8d44\u6e90\u3002\u7f13\u5b58\u53ef\u4ee5\u5927\u5927\u63d0\u9ad8\u6211\u4eec\u5e94\u7528\u7a0b\u5e8f\u7684\u8d28\u91cf\u548c\u6027\u80fd\uff0c\u4f46\u540c\u6837\uff0c\u4e00\u65e6\u51fa\u73b0\u4e00\u4e9b\u9519\u8bef\uff0c\u6211\u4eec\u9996\u5148\u9700\u8981\u67e5\u770b\u5b83\u3002\u4e3a\u4e86\u6db5\u76d6\u8d44\u6e90\u7f13\u5b58\uff0c\u6211\u4eec\u5c06\u4f7f\u7528 HTTP Cache\u3002\u6b64\u5916\uff0c\u6211\u4eec\u5c06\u8ba8\u8bba\u7f13\u5b58\u8fc7\u671f\u3001\u9a8c\u8bc1\u548c\u7f13\u5b58\u63a7\u5236\u6807\u5934\u3002<\/p>\n<h2>25.1 About Caching<\/h2>\n<p>25.1 \u5173\u4e8e\u7f13\u5b58<\/p>\n<p>We want to use cache in our app because it can significantly improve performance. Otherwise, it would be useless. The main goal of caching is to eliminate the need to send requests towards the API in many cases and also to send full responses in other cases.\u200c<br \/>\n\u6211\u4eec\u5e0c\u671b\u5728\u6211\u4eec\u7684\u5e94\u7528\u7a0b\u5e8f\u4e2d\u4f7f\u7528 cache\uff0c\u56e0\u4e3a\u5b83\u53ef\u4ee5\u663e\u8457\u63d0\u9ad8\u6027\u80fd\u3002\u5426\u5219\uff0c\u5b83\u5c06\u6beb\u65e0\u7528\u5904\u3002\u7f13\u5b58\u7684\u4e3b\u8981\u76ee\u6807\u662f\u5728\u8bb8\u591a\u60c5\u51b5\u4e0b\u65e0\u9700\u5411 API \u53d1\u9001\u8bf7\u6c42\uff0c\u5728\u5176\u4ed6\u60c5\u51b5\u4e0b\u4e5f\u65e0\u9700\u53d1\u9001\u5b8c\u6574\u54cd\u5e94\u3002<\/p>\n<p>To reduce the number of sent requests, caching uses the expiration mechanism, which helps reduce network round trips. Furthermore, to eliminate the need to send full responses, the cache uses the validation mechanism, which reduces network bandwidth. We can now see why these two are so important when caching resources.<br \/>\n\u4e3a\u4e86\u51cf\u5c11\u53d1\u9001\u7684\u8bf7\u6c42\u6570\uff0c\u7f13\u5b58\u4f7f\u7528\u8fc7\u671f\u673a\u5236\uff0c\u8fd9\u6709\u52a9\u4e8e\u51cf\u5c11\u7f51\u7edc\u5f80\u8fd4\u6b21\u6570\u3002\u6b64\u5916\uff0c\u4e3a\u4e86\u6d88\u9664\u53d1\u9001\u5b8c\u6574\u54cd\u5e94\u7684\u9700\u8981\uff0c\u7f13\u5b58\u4f7f\u7528\u9a8c\u8bc1\u673a\u5236\uff0c\u8fd9\u51cf\u5c11\u4e86\u7f51\u7edc\u5e26\u5bbd\u3002\u6211\u4eec\u73b0\u5728\u53ef\u4ee5\u770b\u5230\u4e3a\u4ec0\u4e48\u8fd9\u4e24\u4e2a\u5728\u7f13\u5b58\u8d44\u6e90\u65f6\u5982\u6b64\u91cd\u8981\u3002<\/p>\n<p>The cache is a separate component that accepts requests from the API\u2019s consumer. It also accepts the response from the API and stores that response if they are cacheable. Once the response is stored, if a consumer requests the same response again, the response from the cache should be served.<br \/>\n\u7f13\u5b58\u662f\u4e00\u4e2a\u5355\u72ec\u7684\u7ec4\u4ef6\uff0c\u5b83\u63a5\u53d7\u6765\u81ea API \u4f7f\u7528\u8005\u7684\u8bf7\u6c42\u3002\u5b83\u8fd8\u63a5\u53d7\u6765\u81ea API \u7684\u54cd\u5e94\uff0c\u5e76\u5b58\u50a8\u8be5\u54cd\u5e94\uff08\u5982\u679c\u5b83\u4eec\u662f\u53ef\u7f13\u5b58\u7684\uff09\u3002\u5b58\u50a8\u54cd\u5e94\u540e\uff0c\u5982\u679c\u4f7f\u7528\u8005\u518d\u6b21\u8bf7\u6c42\u76f8\u540c\u7684\u54cd\u5e94\uff0c\u5219\u5e94\u63d0\u4f9b\u6765\u81ea\u7f13\u5b58\u7684\u54cd\u5e94\u3002<\/p>\n<p>But the cache behaves differently depending on what cache type is used.<br \/>\n\u4f46\u662f\uff0c\u7f13\u5b58\u7684\u884c\u4e3a\u4f1a\u6709\u6240\u4e0d\u540c\uff0c\u5177\u4f53\u53d6\u51b3\u4e8e\u6240\u4f7f\u7528\u7684\u7f13\u5b58\u7c7b\u578b\u3002<\/p>\n<h3>25.1.1 Cache Types\u200c<\/h3>\n<p>25.1.1 \u7f13\u5b58\u7c7b\u578b<\/p>\n<p>There are three types of caches: Client Cache, Gateway Cache, and Proxy Cache.<br \/>\n\u6709\u4e09\u79cd\u7c7b\u578b\u7684\u7f13\u5b58\uff1a\u5ba2\u6237\u7aef\u7f13\u5b58\u3001\u7f51\u5173\u7f13\u5b58\u548c\u4ee3\u7406\u7f13\u5b58\u3002<\/p>\n<p>The client cache lives on the client (browser); thus, it is a private cache. It is private because it is related to a single client. So every client consuming our API has a private cache.<br \/>\n\u5ba2\u6237\u7aef\u7f13\u5b58\u4f4d\u4e8e\u5ba2\u6237\u7aef\uff08\u6d4f\u89c8\u5668\uff09\u4e0a;\u56e0\u6b64\uff0c\u5b83\u662f\u4e00\u4e2a\u79c1\u6709\u7f13\u5b58\u3002\u5b83\u662f\u79c1\u6709\u7684\uff0c\u56e0\u4e3a\u5b83\u4e0e\u5355\u4e2a\u5ba2\u6237\u7aef\u76f8\u5173\u3002\u56e0\u6b64\uff0c\u6bcf\u4e2a\u4f7f\u7528\u6211\u4eec API \u7684\u5ba2\u6237\u7aef\u90fd\u6709\u4e00\u4e2a\u79c1\u6709\u7f13\u5b58\u3002<\/p>\n<p>The gateway cache lives on the server and is a shared cache. This cache is shared because the resources it caches are shared over different clients.<br \/>\n\u7f51\u5173\u7f13\u5b58\u4f4d\u4e8e\u670d\u52a1\u5668\u4e0a\uff0c\u662f\u5171\u4eab\u7f13\u5b58\u3002\u6b64\u7f13\u5b58\u662f\u5171\u4eab\u7684\uff0c\u56e0\u4e3a\u5b83\u7f13\u5b58\u7684\u8d44\u6e90\u5728\u4e0d\u540c\u7684\u5ba2\u6237\u7aef\u4e0a\u5171\u4eab\u3002<\/p>\n<p>The proxy cache is also a shared cache, but it doesn\u2019t live on the server nor the client side. It lives on the network.<br \/>\n\u4ee3\u7406\u7f13\u5b58\u4e5f\u662f\u5171\u4eab\u7f13\u5b58\uff0c\u4f46\u5b83\u4e0d\u5b58\u5728\u4e8e\u670d\u52a1\u5668\u6216\u5ba2\u6237\u7aef\u3002\u5b83\u5b58\u5728\u4e8e\u7f51\u7edc\u4e0a\u3002<\/p>\n<p>With the private cache, if five clients request the same response for the first time, every response will be served from the API and not from the cache. But if they request the same response again, that response should come from the cache (if it\u2019s not expired). This is not the case with the shared cache. The response from the first client is going to be cached, and then the other four clients will receive the cached response if they request it.<br \/>\n\u4f7f\u7528\u79c1\u6709\u7f13\u5b58\u65f6\uff0c\u5982\u679c\u4e94\u4e2a\u5ba2\u6237\u7aef\u9996\u6b21\u8bf7\u6c42\u76f8\u540c\u7684\u54cd\u5e94\uff0c\u5219\u6bcf\u4e2a\u54cd\u5e94\u90fd\u5c06\u4ece API \u800c\u4e0d\u662f\u7f13\u5b58\u4e2d\u63d0\u4f9b\u3002\u4f46\u662f\uff0c\u5982\u679c\u4ed6\u4eec\u518d\u6b21\u8bf7\u6c42\u76f8\u540c\u7684\u54cd\u5e94\uff0c\u5219\u8be5\u54cd\u5e94\u5e94\u6765\u81ea\u7f13\u5b58\uff08\u5982\u679c\u5b83\u672a\u8fc7\u671f\uff09\u3002\u5171\u4eab\u7f13\u5b58\u4e0d\u662f\u8fd9\u79cd\u60c5\u51b5\u3002\u6765\u81ea\u7b2c\u4e00\u4e2a\u5ba2\u6237\u7aef\u7684\u54cd\u5e94\u5c06\u88ab\u7f13\u5b58\uff0c\u7136\u540e\u5176\u4ed6\u56db\u4e2a\u5ba2\u6237\u7aef\u5c06\u6536\u5230\u7f13\u5b58\u7684\u54cd\u5e94\uff08\u5982\u679c\u5b83\u4eec\u8bf7\u6c42\uff09\u3002<\/p>\n<h3>25.1.2 Response Cache Attribute\u200c<\/h3>\n<p>25.1.2 \u54cd\u5e94\u7f13\u5b58\u5c5e\u6027<\/p>\n<p>So, to cache some resources, we have to know whether or not it\u2019s cacheable. The response header helps us with that. The one that is used most often is Cache-Control: Cache-Control: max-age=180. This states that the response should be cached for 180 seconds. For that, we use the ResponseCache attribute. But of course, this is just a header. If we want to cache something, we need a cache-store. For our example, we are going to use Response caching middleware provided by ASP.NET Core.<br \/>\n\u6240\u4ee5\uff0c\u8981\u7f13\u5b58\u4e00\u4e9b\u8d44\u6e90\uff0c\u6211\u4eec\u5fc5\u987b\u77e5\u9053\u5b83\u662f\u5426\u662f\u53ef\u7f13\u5b58\u7684\u3002\u54cd\u5e94\u6807\u5934\u53ef\u4ee5\u5e2e\u52a9\u6211\u4eec\u89e3\u51b3\u8fd9\u4e2a\u95ee\u9898\u3002\u6700\u5e38\u7528\u7684\u662f Cache-Control\uff1a Cache-Control\uff1a max-age=180\u3002\u8fd9\u8868\u793a\u54cd\u5e94\u5e94\u7f13\u5b58 180 \u79d2\u3002\u4e3a\u6b64\uff0c\u6211\u4eec\u4f7f\u7528 ResponseCache \u5c5e\u6027\u3002\u4f46\u5f53\u7136\uff0c\u8fd9\u53ea\u662f\u4e00\u4e2a\u6807\u9898\u3002\u5982\u679c\u6211\u4eec\u60f3\u7f13\u5b58\u4e00\u4e9b\u4e1c\u897f\uff0c\u6211\u4eec\u9700\u8981\u4e00\u4e2a cache-store\u3002\u5bf9\u4e8e\u6211\u4eec\u7684\u793a\u4f8b\uff0c\u6211\u4eec\u5c06\u4f7f\u7528 ASP.NET Core \u63d0\u4f9b\u7684\u54cd\u5e94\u7f13\u5b58\u4e2d\u95f4\u4ef6\u3002<\/p>\n<h2>25.2 Adding Cache Headers<\/h2>\n<p>25.2 \u6dfb\u52a0\u7f13\u5b58 Headers<\/p>\n<p>Before we start, let\u2019s open Postman and modify the settings to support caching:\u200c<br \/>\n\u5728\u5f00\u59cb\u4e4b\u524d\uff0c\u8ba9\u6211\u4eec\u6253\u5f00 Postman \u5e76\u4fee\u6539\u8bbe\u7f6e\u4ee5\u652f\u6301\u7f13\u5b58\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2501.jpg\" alt=\"alt text\" \/><\/p>\n<p>In the General tab under Headers, we are going to turn off the Send no- cache header:<br \/>\n\u5728 Headers \u4e0b\u7684 General \u9009\u9879\u5361\u4e2d\uff0c\u6211\u4eec\u5c06\u5173\u95ed Send no- cache \u6807\u5934\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2502.jpg\" alt=\"alt text\" \/><\/p>\n<p>Great. We can move on.<br \/>\n\u6211\u4eec\u53ef\u4ee5\u7ee7\u7eed\u524d\u8fdb\u3002<\/p>\n<p>Let\u2019s assume we want to use the ResponseCache attribute to cache the result from the GetCompany action:<br \/>\n\u5047\u8bbe\u6211\u4eec\u8981\u4f7f\u7528 ResponseCache \u5c5e\u6027\u6765\u7f13\u5b58 GetCompany\u4f5c\u7684\u7ed3\u679c\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2503.jpg\" alt=\"alt text\" \/><\/p>\n<p>It is obvious that we can work with different properties in the ResponseCache attribute \u2014 but for now, we are going to use Duration only:<br \/>\n\u5f88\u660e\u663e\uff0c\u6211\u4eec\u53ef\u4ee5\u5728 ResponseCache \u5c5e\u6027\u4e2d\u4f7f\u7528\u4e0d\u540c\u7684\u5c5e\u6027 \u2014 \u4f46\u73b0\u5728\uff0c\u6211\u4eec\u53ea\u4f7f\u7528 Duration\uff1a<\/p>\n<pre><code>[HttpGet(&quot;{id}&quot;, Name = &quot;CompanyById&quot;)] \n[ResponseCache(Duration = 60)] \npublic async Task&lt;IActionResult&gt; GetCompany(Guid id)<\/code><\/pre>\n<p>And that is it. We can inspect our result now:<br \/>\n\u5c31\u662f\u8fd9\u6837\u3002\u6211\u4eec\u73b0\u5728\u53ef\u4ee5\u68c0\u67e5\u6211\u4eec\u7684\u7ed3\u679c\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/3d490a70-94ce-4d15-9494-5248280c2ce3\">https:\/\/localhost:5001\/api\/companies\/3d490a70-94ce-4d15-9494-5248280c2ce3<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2504.jpg\" alt=\"alt text\" \/><\/p>\n<p>You can see that the Cache-Control header was created with a public cache and a duration of 60 seconds. But as we said, this is just a header; we need a cache-store to cache the response. So, let\u2019s add one.<br \/>\n\u60a8\u53ef\u4ee5\u770b\u5230\uff0cCache-Control \u6807\u5934\u662f\u4f7f\u7528\u516c\u5171\u7f13\u5b58\u521b\u5efa\u7684\uff0c\u6301\u7eed\u65f6\u95f4\u4e3a 60 \u79d2\u3002\u4f46\u6b63\u5982\u6211\u4eec\u6240\u8bf4\uff0c\u8fd9\u53ea\u662f\u4e00\u4e2a\u6807\u9898;\u6211\u4eec\u9700\u8981\u4e00\u4e2a cache-store \u6765\u7f13\u5b58\u54cd\u5e94\u3002\u90a3\u4e48\uff0c\u8ba9\u6211\u4eec\u6dfb\u52a0\u4e00\u4e2a\u3002<\/p>\n<h2>25.3 Adding Cache-Store<\/h2>\n<p>25.3 \u6dfb\u52a0 Cache-Store<\/p>\n<p>The first thing we are going to do is add an extension method in the\u200c ServiceExtensions class:<br \/>\n\u6211\u4eec\u8981\u505a\u7684\u7b2c\u4e00\u4ef6\u4e8b\u662f\u5728 ServiceExtensions \u7c7b\u4e2d\u6dfb\u52a0\u4e00\u4e2a\u6269\u5c55\u65b9\u6cd5\uff1a<\/p>\n<pre><code>public static void ConfigureResponseCaching(this IServiceCollection services) =&gt; services.AddResponseCaching();<\/code><\/pre>\n<p>We register response caching in the IOC container, and now we have to call this method in the Program class:<br \/>\n\u6211\u4eec\u5728 IOC \u5bb9\u5668\u4e2d\u6ce8\u518c\u54cd\u5e94\u7f13\u5b58\uff0c\u73b0\u5728\u6211\u4eec\u5fc5\u987b\u5728 Program \u7c7b\u4e2d\u8c03\u7528\u6b64\u65b9\u6cd5\uff1a<\/p>\n<pre><code>builder.Services.ConfigureResponseCaching();<\/code><\/pre>\n<p>Additionally, we have to add caching to the application middleware right below UseCors() because Microsoft recommends having UseCors before UseResponseCaching, and as we learned in the section 1.8, order is very important for the middleware execution:<br \/>\n\u6b64\u5916\uff0c\u6211\u4eec\u5fc5\u987b\u5c06\u7f13\u5b58\u6dfb\u52a0\u5230\u5e94\u7528\u7a0b\u5e8f\u4e2d\u95f4\u4ef6\u7684 UseCors\uff08\uff09 \u6b63\u4e0b\u65b9\uff0c\u56e0\u4e3a Microsoft \u5efa\u8bae\u5728 UseResponseCached \u4e4b\u524d\u4f7f\u7528 UseCors\uff0c\u6b63\u5982\u6211\u4eec\u5728\u7b2c 1.8 \u8282\u4e2d\u5b66\u5230\u7684\u90a3\u6837\uff0c\u987a\u5e8f\u5bf9\u4e8e\u4e2d\u95f4\u4ef6\u7684\u6267\u884c\u975e\u5e38\u91cd\u8981\uff1a<\/p>\n<pre><code>app.UseResponseCaching();\napp.UseCors(&quot;CorsPolicy&quot;);<\/code><\/pre>\n<p>Now, we can start our application and send the same GetCompany request. It will generate the Cache-Control header. After that, before 60 seconds pass, we are going to send the same request and inspect the headers:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u542f\u52a8\u5e94\u7528\u7a0b\u5e8f\u5e76\u53d1\u9001\u76f8\u540c\u7684 GetCompany \u8bf7\u6c42\u3002\u5b83\u5c06\u751f\u6210 Cache-Control \u6807\u5934\u3002\u4e4b\u540e\uff0c\u5728 60 \u79d2\u8fc7\u53bb\u4e4b\u524d\uff0c\u6211\u4eec\u5c06\u53d1\u9001\u76f8\u540c\u7684\u8bf7\u6c42\u5e76\u68c0\u67e5\u6807\u5934\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/3d490a70-94ce-4d15-9494-5248280c2ce3\">https:\/\/localhost:5001\/api\/companies\/3d490a70-94ce-4d15-9494-5248280c2ce3<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2505.jpg\" alt=\"alt text\" \/><\/p>\n<p>You can see the additional Age header that indicates the number of seconds the object has been stored in the cache. Basically, it means that we received our second response from the cache-store.<br \/>\n\u60a8\u53ef\u4ee5\u770b\u5230\u989d\u5916\u7684 Age \u6807\u5934\uff0c\u8be5\u6807\u5934\u6307\u793a\u5bf9\u8c61\u5728\u7f13\u5b58\u4e2d\u5b58\u50a8\u7684\u79d2\u6570\u3002\u57fa\u672c\u4e0a\uff0c\u8fd9\u610f\u5473\u7740\u6211\u4eec\u6536\u5230\u4e86\u6765\u81ea cache-store \u7684\u7b2c\u4e8c\u4e2a\u54cd\u5e94\u3002<\/p>\n<p>Another way to confirm that is to wait 60 seconds to pass. After that, you can send the request and inspect the console. You will see the SQL query generated. But if you send a second request, you will find no new logs for the SQL query. That\u2019s because we are receiving our response from the cache.<br \/>\n\u53e6\u4e00\u79cd\u786e\u8ba4\u65b9\u6cd5\u662f\u7b49\u5f85 60 \u79d2\u3002\u4e4b\u540e\uff0c\u60a8\u53ef\u4ee5\u53d1\u9001\u8bf7\u6c42\u5e76\u68c0\u67e5\u63a7\u5236\u53f0\u3002\u60a8\u5c06\u770b\u5230\u751f\u6210\u7684 SQL \u67e5\u8be2\u3002\u4f46\u662f\uff0c\u5982\u679c\u60a8\u53d1\u9001\u7b2c\u4e8c\u4e2a\u8bf7\u6c42\uff0c\u5219\u4e0d\u4f1a\u627e\u5230 SQL \u67e5\u8be2\u7684\u65b0\u65e5\u5fd7\u3002\u90a3\u662f\u56e0\u4e3a\u6211\u4eec\u6b63\u5728\u4ece\u7f13\u5b58\u4e2d\u63a5\u6536\u54cd\u5e94\u3002<\/p>\n<p>Additionally, with every subsequent request within 60 seconds, the Age property will increment. After the expiration period passes, the response will be sent from the API, cached again, and the Age header will not be generated. You will also see new logs in the console.<br \/>\n\u6b64\u5916\uff0c\u5bf9\u4e8e 60 \u79d2\u5185\u7684\u6bcf\u4e2a\u540e\u7eed\u8bf7\u6c42\uff0cAge \u5c5e\u6027\u5c06\u9012\u589e\u3002\u8fc7\u671f\u671f\u9650\u8fc7\u540e\uff0c\u5c06\u4ece API \u53d1\u9001\u54cd\u5e94\uff0c\u518d\u6b21\u7f13\u5b58\uff0c\u5e76\u4e14\u4e0d\u4f1a\u751f\u6210 Age \u6807\u5934\u3002\u60a8\u8fd8\u5c06\u5728\u63a7\u5236\u53f0\u4e2d\u770b\u5230\u65b0\u65e5\u5fd7\u3002<\/p>\n<p>Furthermore, we can use cache profiles to apply the same rules to different resources. If you look at the picture that shows all the properties we can use with ResponseCacheAttribute, you can see that there are a lot of properties. Configuring all of them on top of the action or controller could lead to less readable code. Therefore, we can use CacheProfiles to extract that configuration.<br \/>\n\u6b64\u5916\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u7f13\u5b58\u914d\u7f6e\u6587\u4ef6\u5c06\u76f8\u540c\u7684\u89c4\u5219\u5e94\u7528\u4e8e\u4e0d\u540c\u7684\u8d44\u6e90\u3002\u5982\u679c\u4f60\u770b\u4e00\u4e0b\u56fe\u7247\uff0c\u5b83\u663e\u793a\u4e86\u6211\u4eec\u53ef\u4ee5\u4e0e ResponseCacheAttribute \u4e00\u8d77\u4f7f\u7528\u7684\u6240\u6709\u5c5e\u6027\uff0c\u4f60\u53ef\u4ee5\u770b\u5230\u6709\u5f88\u591a\u5c5e\u6027\u3002\u5728 action \u6216 controller \u4e4b\u4e0a\u914d\u7f6e\u6240\u6709\u8fd9\u4e9b\u53ef\u80fd\u4f1a\u5bfc\u81f4\u4ee3\u7801\u7684\u53ef\u8bfb\u6027\u964d\u4f4e\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528 CacheProfiles \u6765\u63d0\u53d6\u8be5\u914d\u7f6e\u3002<\/p>\n<p>To do that, we are going to modify the AddControllers method:<br \/>\n\u4e3a\u6b64\uff0c\u6211\u4eec\u5c06\u4fee\u6539 AddControllers \u65b9\u6cd5\uff1a<\/p>\n<pre><code>builder.Services.AddControllers(config =&gt; { config.RespectBrowserAcceptHeader = true; config.ReturnHttpNotAcceptable = true; config.InputFormatters.Insert(0, GetJsonPatchInputFormatter()); config.CacheProfiles.Add(&quot;120SecondsDuration&quot;, new CacheProfile { Duration = 120 }); })...<\/code><\/pre>\n<p>We only set up Duration, but you can add additional properties as well. Now, let\u2019s implement this profile on top of the Companies controller:<br \/>\n\u6211\u4eec\u53ea\u8bbe\u7f6e\u4e86 Duration\uff0c\u4f46\u60a8\u4e5f\u53ef\u4ee5\u6dfb\u52a0\u5176\u4ed6\u5c5e\u6027\u3002\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u5728 Companies \u63a7\u5236\u5668\u4e0a\u5b9e\u73b0\u8fd9\u4e2a\u914d\u7f6e\u6587\u4ef6\uff1a<\/p>\n<pre><code>[Route(&quot;api\/companies&quot;)] [ApiController] [ResponseCache(CacheProfileName = &quot;120SecondsDuration&quot;)]<\/code><\/pre>\n<p>We have to mention that this cache rule will apply to all the actions inside the controller except the ones that already have the ResponseCache attribute applied.<br \/>\n\u6211\u4eec\u5fc5\u987b\u63d0\u5230\uff0c\u6b64\u7f13\u5b58\u89c4\u5219\u5c06\u5e94\u7528\u4e8e\u63a7\u5236\u5668\u5185\u7684\u6240\u6709\u4f5c\uff0c\u4f46\u5df2\u5e94\u7528 ResponseCache \u5c5e\u6027\u7684\u4f5c\u9664\u5916\u3002<\/p>\n<p>That said, once we send the request to GetCompany, we will still have the maximum age of 60. But once we send the request to GetCompanies:<br \/>\n\u4e5f\u5c31\u662f\u8bf4\uff0c\u4e00\u65e6\u6211\u4eec\u5c06\u8bf7\u6c42\u53d1\u9001\u5230 GetCompany\uff0c\u6211\u4eec\u7684\u6700\u5927\u5e74\u9f84\u4ecd\u7136\u662f 60 \u5c81\u3002\u4f46\u662f\uff0c\u4e00\u65e6\u6211\u4eec\u5c06\u8bf7\u6c42\u53d1\u9001\u5230 GetCompanies\uff1a<br \/>\n<a href=\"https:\/\/localhost:5001\/api\/companies\">https:\/\/localhost:5001\/api\/companies<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2506.jpg\" alt=\"alt text\" \/><\/p>\n<p>There you go. Now, let\u2019s talk some more about the Expiration and Validation models.<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u66f4\u591a\u5730\u8ba8\u8bba Expiration \u548c Validation \u6a21\u578b\u3002<\/p>\n<h2>25.4 Expiration Model<\/h2>\n<p>25.4 \u8fc7\u671f\u6a21\u578b<\/p>\n<p>The expiration model allows the server to recognize whether or not the response has expired. As long as the response is fresh, it will be served from the cache. To achieve that, the Cache-Control header is used. We have seen this in the previous example.\u200c<br \/>\n\u8fc7\u671f\u6a21\u578b\u5141\u8bb8\u670d\u52a1\u5668\u8bc6\u522b\u54cd\u5e94\u662f\u5426\u5df2\u8fc7\u671f\u3002\u53ea\u8981\u54cd\u5e94\u662f\u6700\u65b0\u7684\uff0c\u5b83\u5c31\u4f1a\u4ece\u7f13\u5b58\u4e2d\u63d0\u4f9b\u3002\u4e3a\u6b64\uff0c\u4f7f\u7528\u4e86 Cache-Control \u6807\u5934\u3002\u6211\u4eec\u5728\u524d\u9762\u7684\u793a\u4f8b\u4e2d\u5df2\u7ecf\u770b\u5230\u4e86\u8fd9\u4e00\u70b9\u3002<\/p>\n<p>Let\u2019s look at the diagram to see how caching works:<br \/>\n\u8ba9\u6211\u4eec\u770b\u4e00\u4e0b\u56fe\u8868\uff0c\u770b\u770b\u7f13\u5b58\u662f\u5982\u4f55\u5de5\u4f5c\u7684\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2507.jpg\" alt=\"alt text\" \/><\/p>\n<p>So, the client sends a request to get companies. There is no cached version of that response; therefore, the request is forwarded to the API. The API returns the response with the Cache-Control header with a 10- minute expiration period; it is being stored in the cache and forwarded to the client.<br \/>\n\u56e0\u6b64\uff0c\u5ba2\u6237\u7aef\u53d1\u9001\u8bf7\u6c42\u4ee5\u83b7\u53d6\u516c\u53f8\u3002\u8be5\u54cd\u5e94\u6ca1\u6709\u7f13\u5b58\u7248\u672c;\u56e0\u6b64\uff0c\u8bf7\u6c42\u5c06\u8f6c\u53d1\u5230 API\u3002API \u8fd4\u56de\u5e26\u6709 Cache-Control \u6807\u5934\u7684\u54cd\u5e94\uff0c\u6709\u6548\u671f\u4e3a 10 \u5206\u949f;\u5b83\u88ab\u5b58\u50a8\u5728\u7f13\u5b58\u4e2d\u5e76\u8f6c\u53d1\u5230\u5ba2\u6237\u7aef\u3002<\/p>\n<p>If after two minutes, the same response has been requested:<br \/>\n\u5982\u679c\u4e24\u5206\u949f\u540e\uff0c\u8bf7\u6c42\u4e86\u76f8\u540c\u7684\u54cd\u5e94\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2508.jpg\" alt=\"alt text\" \/><\/p>\n<p>We can see that the cached response was served with an additional Age header with a value of 120 seconds or two minutes. If this is a private cache, that is where it stops. That\u2019s because the private cache is stored in the browser and another client will hit the API for the same response. But if this is a shared cache and another client requests the same response after an additional two minutes:<br \/>\n\u6211\u4eec\u53ef\u4ee5\u770b\u5230\uff0c\u7f13\u5b58\u7684\u54cd\u5e94\u4f7f\u7528\u503c\u4e3a 120 \u79d2\u6216 2 \u5206\u949f\u7684\u989d\u5916 Age \u6807\u5934\u63d0\u4f9b\u3002\u5982\u679c\u8fd9\u662f\u79c1\u6709\u7f13\u5b58\uff0c\u5219\u5b83\u662f\u505c\u6b62\u7684\u5730\u65b9\u3002\u8fd9\u662f\u56e0\u4e3a\u79c1\u6709\u7f13\u5b58\u5b58\u50a8\u5728\u6d4f\u89c8\u5668\u4e2d\uff0c\u53e6\u4e00\u4e2a\u5ba2\u6237\u7aef\u5c06\u70b9\u51fb API \u4ee5\u83b7\u5f97\u76f8\u540c\u7684\u54cd\u5e94\u3002\u4f46\u662f\uff0c\u5982\u679c\u8fd9\u662f\u4e00\u4e2a\u5171\u4eab\u7f13\u5b58\uff0c\u5e76\u4e14\u53e6\u4e00\u4e2a\u5ba2\u6237\u7aef\u5728\u989d\u5916\u7684\u4e24\u5206\u949f\u540e\u8bf7\u6c42\u76f8\u540c\u7684\u54cd\u5e94\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2509.jpg\" alt=\"alt text\" \/><\/p>\n<p>The response is served from the cache with an additional two minutes added to the Age header.<br \/>\n\u54cd\u5e94\u4ece\u7f13\u5b58\u4e2d\u63d0\u4f9b\uff0c\u5e76\u5411 Age \u6807\u5934\u989d\u5916\u6dfb\u52a0\u4e24\u5206\u949f\u3002<\/p>\n<p>We saw how the Expiration model works, now let\u2019s inspect the  Validation model.<br \/>\n\u6211\u4eec\u4e86\u89e3\u4e86 Expiration \u6a21\u578b\u7684\u5de5\u4f5c\u539f\u7406\uff0c\u73b0\u5728\u8ba9\u6211\u4eec\u68c0\u67e5 Validation \u6a21\u578b\u3002<\/p>\n<h2>25.5 Validation Model<\/h2>\n<p>25.5 \u9a8c\u8bc1\u6a21\u578b<\/p>\n<p>The validation model is used to validate the freshness of the response. So it checks if the response is cached and still usable. Let\u2019s assume we have a shared cached GetCompany response for 30 minutes. If someone updates that company after five minutes, without validation the client would receive the wrong response for another 25 minutes \u2014 not the updated one.\u200c<br \/>\n\u9a8c\u8bc1\u6a21\u578b\u7528\u4e8e\u9a8c\u8bc1\u54cd\u5e94\u7684\u65b0\u9c9c\u5ea6\u3002\u56e0\u6b64\uff0c\u5b83\u4f1a\u68c0\u67e5\u54cd\u5e94\u662f\u5426\u5df2\u7f13\u5b58\u4e14\u4ecd\u7136\u53ef\u7528\u3002\u5047\u8bbe\u6211\u4eec\u6709\u4e00\u4e2a\u5171\u4eab\u7f13\u5b58\u7684 GetCompany \u54cd\u5e94 30 \u5206\u949f\u3002\u5982\u679c\u6709\u4eba\u5728 5 \u5206\u949f\u540e\u66f4\u65b0\u4e86\u8be5\u516c\u53f8\uff0c\u5219\u5ba2\u6237\u5c06\u5728 25 \u5206\u949f\u5185\u6536\u5230\u9519\u8bef\u7684\u54cd\u5e94\uff0c\u800c\u4e0d\u662f\u66f4\u65b0\u540e\u7684\u54cd\u5e94\u3002<\/p>\n<p>To prevent that, we use validators. The HTTP standard advises using Last- Modified and ETag validators in combination if possible.<br \/>\n\u4e3a\u4e86\u9632\u6b62\u8fd9\u79cd\u60c5\u51b5\uff0c\u6211\u4eec\u4f7f\u7528\u9a8c\u8bc1\u5668\u3002HTTP \u6807\u51c6\u5efa\u8bae\u5c3d\u53ef\u80fd\u7ed3\u5408\u4f7f\u7528 Last- Modified \u548c ETag \u9a8c\u8bc1\u5668\u3002<\/p>\n<p>Let\u2019s see how validation works:<br \/>\n\u8ba9\u6211\u4eec\u770b\u770b\u9a8c\u8bc1\u662f\u5982\u4f55\u5de5\u4f5c\u7684\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2510.jpg\" alt=\"alt text\" \/><\/p>\n<p>So again, the client sends a request, it is not cached, and so it is forwarded to the API. Our API returns the response that contains the Etag and Last-Modified headers. That response is cached and forwarded to the client.<br \/>\n\u56e0\u6b64\uff0c\u5ba2\u6237\u7aef\u518d\u6b21\u53d1\u9001\u8bf7\u6c42\uff0c\u8be5\u8bf7\u6c42\u672a\u88ab\u7f13\u5b58\uff0c\u56e0\u6b64\u88ab\u8f6c\u53d1\u5230 API\u3002\u6211\u4eec\u7684 API \u8fd4\u56de\u5305\u542b Etag \u548c Last-Modified \u6807\u5934\u7684\u54cd\u5e94\u3002\u8be5\u54cd\u5e94\u5c06\u88ab\u7f13\u5b58\u5e76\u8f6c\u53d1\u5230\u5ba2\u6237\u7aef\u3002<\/p>\n<p>After two minutes, the client sends the same request:<br \/>\n\u4e24\u5206\u949f\u540e\uff0c\u5ba2\u6237\u7aef\u53d1\u9001\u76f8\u540c\u7684\u8bf7\u6c42\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2511.jpg\" alt=\"alt text\" \/><\/p>\n<p>So, the same request is sent, but we don\u2019t know if the response is valid. Therefore, the cache forwards that request to the API with the additional headers If-None-Match \u2014 which is set to the Etag value \u2014 and If- Modified-Since \u2014 which is set to the Last-Modified value. If this request checks out against the validators, our API doesn\u2019t have to recreate the same response; it just sends a 304 Not Modified status. After that, the regular response is served from the cache. Of course, if this doesn\u2019t check out, a new response must be generated.<br \/>\n\u56e0\u6b64\uff0c\u53d1\u9001\u4e86\u76f8\u540c\u7684\u8bf7\u6c42\uff0c\u4f46\u6211\u4eec\u4e0d\u77e5\u9053\u54cd\u5e94\u662f\u5426\u6709\u6548\u3002\u56e0\u6b64\uff0c\u7f13\u5b58\u5c06\u8be5\u8bf7\u6c42\u8f6c\u53d1\u5230 API\uff0c\u5176\u4e2d\u5305\u542b\u989d\u5916\u7684\u6807\u5934 If-None-Match\uff08\u8bbe\u7f6e\u4e3a Etag \u503c\uff09\u548c If- Modified-Since\uff08\u8bbe\u7f6e\u4e3a Last-Modified \u503c\uff09\u3002\u5982\u679c\u6b64\u8bf7\u6c42\u9488\u5bf9\u9a8c\u8bc1\u8005\u8fdb\u884c\u68c0\u67e5\uff0c\u5219\u6211\u4eec\u7684 API \u4e0d\u5fc5\u91cd\u65b0\u521b\u5efa\u76f8\u540c\u7684\u54cd\u5e94;\u5b83\u53ea\u662f\u53d1\u9001 304 Not Modified \u72b6\u6001\u3002\u4e4b\u540e\uff0c\u5c06\u4ece\u7f13\u5b58\u4e2d\u63d0\u4f9b\u5e38\u89c4\u54cd\u5e94\u3002\u5f53\u7136\uff0c\u5982\u679c\u8fd9\u6ca1\u6709\u68c0\u67e5\u51fa\u6765\uff0c\u5219\u5fc5\u987b\u751f\u6210\u65b0\u7684\u54cd\u5e94\u3002<\/p>\n<p>That brings us to the conclusion that for the shared cache if the response hasn\u2019t been modified, that response has to be generated only once. Let\u2019s see all of these in an example.<br \/>\n\u8fd9\u4f7f\u6211\u4eec\u5f97\u51fa\u7ed3\u8bba\uff0c\u5bf9\u4e8e\u5171\u4eab\u7f13\u5b58\uff0c\u5982\u679c\u54cd\u5e94\u5c1a\u672a\u4fee\u6539\uff0c\u5219\u8be5\u54cd\u5e94\u53ea\u9700\u751f\u6210\u4e00\u6b21\u3002\u8ba9\u6211\u4eec\u901a\u8fc7\u4e00\u4e2a\u793a\u4f8b\u6765\u4e86\u89e3\u6240\u6709\u8fd9\u4e9b\u3002<\/p>\n<h2>25.6 Supporting Validation<\/h2>\n<p>25.6 \u652f\u6301\u9a8c\u8bc1<\/p>\n<p>To support validation, we are going to use the Marvin.Cache.Headers library. This library supports HTTP cache headers like Cache-Control, Expires, Etag, and Last-Modified and also implements validation and expiration models.\u200c<br \/>\n\u4e3a\u4e86\u652f\u6301\u9a8c\u8bc1\uff0c\u6211\u4eec\u5c06\u4f7f\u7528 Marvin.Cache.Headers \u5e93\u3002\u6b64\u5e93\u652f\u6301 Cache-Control\u3001Expires\u3001Etag \u548c Last-Modified \u7b49 HTTP \u7f13\u5b58\u6807\u5934\uff0c\u5e76\u4e14\u8fd8\u5b9e\u65bd\u9a8c\u8bc1\u548c\u8fc7\u671f\u6a21\u578b\u3002<\/p>\n<p>So, let\u2019s install the Marvin.Cache.Headers library in the Presentation project, which will enable the reference for the main project as well. We are going to need it in both projects.<br \/>\n\u56e0\u6b64\uff0c\u8ba9\u6211\u4eec\u5728 Presentation \u9879\u76ee\u4e2d\u5b89\u88c5 Marvin.Cache.Headers \u5e93\uff0c\u8fd9\u4e5f\u5c06\u542f\u7528\u4e3b\u9879\u76ee\u7684\u5f15\u7528\u3002\u6211\u4eec\u5c06\u5728\u8fd9\u4e24\u4e2a\u9879\u76ee\u4e2d\u90fd\u9700\u8981\u5b83\u3002<\/p>\n<p>Now, let\u2019s modify the ServiceExtensions class:<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u4fee\u6539 ServiceExtensions \u7c7b\uff1a<\/p>\n<pre><code>public static void ConfigureHttpCacheHeaders(this IServiceCollection services) =&gt; services.AddHttpCacheHeaders();<\/code><\/pre>\n<p>We are going to add additional configuration later.<br \/>\n\u6211\u4eec\u7a0d\u540e\u5c06\u6dfb\u52a0\u5176\u4ed6\u914d\u7f6e\u3002<\/p>\n<p>Then, let\u2019s modify the Program class:<br \/>\n\u7136\u540e\uff0c\u8ba9\u6211\u4eec\u4fee\u6539 Program \u7c7b\uff1a<\/p>\n<pre><code>builder.Services.ConfigureResponseCaching(); \nbuilder.Services.ConfigureHttpCacheHeaders();<\/code><\/pre>\n<p>And finally, let\u2019s add HttpCacheHeaders to the request pipeline:<br \/>\n\u6700\u540e\uff0c\u8ba9\u6211\u4eec\u5c06 HttpCacheHeaders \u6dfb\u52a0\u5230\u8bf7\u6c42\u7ba1\u9053\u4e2d\uff1a<\/p>\n<pre><code>app.UseResponseCaching(); \napp.UseHttpCacheHeaders();<\/code><\/pre>\n<p>To test this, we have to remove or comment out ResponseCache attributes in the CompaniesController. The installed library will provide that for us. Now, let\u2019s send the GetCompany request:<br \/>\n\u4e3a\u4e86\u6d4b\u8bd5\u8fd9\u4e00\u70b9\uff0c\u6211\u4eec\u5fc5\u987b\u5220\u9664\u6216\u6ce8\u91ca\u6389 CompaniesController \u4e2d\u7684 ResponseCache \u5c5e\u6027\u3002\u5df2\u5b89\u88c5\u7684\u5e93\u5c06\u4e3a\u6211\u4eec\u63d0\u4f9b\u3002\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u53d1\u9001 GetCompany \u8bf7\u6c42\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/3d490a70-94ce-4d15-9494-5248280c2ce3\">https:\/\/localhost:5001\/api\/companies\/3d490a70-94ce-4d15-9494-5248280c2ce3<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2512.jpg\" alt=\"alt text\" \/><\/p>\n<p>We can see that we have all the required headers generated. The default expiration is set to 60 seconds and if we send this request one more time, we are going to get an additional Age header.<br \/>\n\u6211\u4eec\u53ef\u4ee5\u770b\u5230\u6211\u4eec\u5df2\u7ecf\u751f\u6210\u4e86\u6240\u6709\u5fc5\u9700\u7684\u6807\u5934\u3002\u9ed8\u8ba4\u8fc7\u671f\u65f6\u95f4\u8bbe\u7f6e\u4e3a 60 \u79d2\uff0c\u5982\u679c\u6211\u4eec\u518d\u6b21\u53d1\u9001\u6b64\u8bf7\u6c42\uff0c\u6211\u4eec\u5c06\u83b7\u5f97\u4e00\u4e2a\u989d\u5916\u7684 Age \u6807\u5934\u3002<\/p>\n<h3>25.6.1 Configuration\u200c<\/h3>\n<p>25.6.1 \u914d\u7f6e<\/p>\n<p>We can globally configure our expiration and validation headers. To do that, let\u2019s modify the ConfigureHttpCacheHeaders method:<br \/>\n\u6211\u4eec\u53ef\u4ee5\u5168\u5c40\u914d\u7f6e\u6211\u4eec\u7684 expiration \u548c validation \u6807\u5934\u3002\u4e3a\u6b64\uff0c\u8ba9\u6211\u4eec\u4fee\u6539 ConfigureHttpCacheHeaders \u65b9\u6cd5\uff1a<\/p>\n<pre><code>public static void ConfigureHttpCacheHeaders(this IServiceCollection services) =&gt; services.AddHttpCacheHeaders(\n(expirationOpt) =&gt; { expirationOpt.MaxAge = 65; expirationOpt.CacheLocation = CacheLocation.Private; }, (validationOpt) =&gt; { validationOpt.MustRevalidate = true; });    <\/code><\/pre>\n<p>After that, we are going to send the same request for a single company:<br \/>\n\u4e4b\u540e\uff0c\u6211\u4eec\u5c06\u4e3a\u5355\u4e2a\u516c\u53f8\u53d1\u9001\u76f8\u540c\u7684\u8bf7\u6c42\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/3d490a70-94ce-4d15-9494-5248280c2ce3\">https:\/\/localhost:5001\/api\/companies\/3d490a70-94ce-4d15-9494-5248280c2ce3<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2513.jpg\" alt=\"alt text\" \/><\/p>\n<p>You can see that the changes are implemented. Now, this is a private cache with an age of 65 seconds. Because it is a private cache, our API won\u2019t cache it. You can check the console again and see the SQL logs for each request you send.<br \/>\n\u60a8\u53ef\u4ee5\u770b\u5230\u66f4\u6539\u5df2\u5b9e\u65bd\u3002\u73b0\u5728\uff0c\u8fd9\u662f\u4e00\u4e2a\u5b58\u5728\u65f6\u95f4\u4e3a 65 \u79d2\u7684\u79c1\u6709\u7f13\u5b58\u3002\u56e0\u4e3a\u5b83\u662f\u79c1\u6709\u7f13\u5b58\uff0c\u6240\u4ee5\u6211\u4eec\u7684 API \u4e0d\u4f1a\u7f13\u5b58\u5b83\u3002\u60a8\u53ef\u4ee5\u518d\u6b21\u68c0\u67e5\u63a7\u5236\u53f0\u5e76\u67e5\u770b\u60a8\u53d1\u9001\u7684\u6bcf\u4e2a\u8bf7\u6c42\u7684 SQL \u65e5\u5fd7\u3002<\/p>\n<p>Other than global configuration, we can apply it on the resource level (on action or controller). The overriding rules are the same. Configuration on the action level will override the configuration on the controller or global level. Also, the configuration on the controller level will override the global level configuration.<br \/>\n\u9664\u4e86\u5168\u5c40\u914d\u7f6e\u4e4b\u5916\uff0c\u6211\u4eec\u53ef\u4ee5\u5728\u8d44\u6e90\u7ea7\u522b\uff08\u5728 action \u6216 controller \u4e0a\uff09\u5e94\u7528\u5b83\u3002\u8986\u76d6\u89c4\u5219\u662f\u76f8\u540c\u7684\u3002\u4f5c\u7ea7\u522b\u7684\u914d\u7f6e\u5c06\u8986\u76d6\u63a7\u5236\u5668\u6216\u5168\u5c40\u7ea7\u522b\u7684\u914d\u7f6e\u3002\u6b64\u5916\uff0c\u63a7\u5236\u5668\u7ea7\u522b\u7684\u914d\u7f6e\u5c06\u8986\u76d6\u5168\u5c40\u7ea7\u522b\u7684\u914d\u7f6e\u3002<\/p>\n<p>To apply a resource level configuration, we have to use the HttpCacheExpiration and HttpCacheValidation attributes:<br \/>\n\u8981\u5e94\u7528\u8d44\u6e90\u7ea7\u522b\u914d\u7f6e\uff0c\u6211\u4eec\u5fc5\u987b\u4f7f\u7528 HttpCacheExpiration \u548c HttpCacheValidation \u5c5e\u6027\uff1a<\/p>\n<pre><code>[HttpGet(&quot;{id}&quot;, Name = &quot;CompanyById&quot;)] [HttpCacheExpiration(CacheLocation = CacheLocation.Public, MaxAge = 60)] [HttpCacheValidation(MustRevalidate = false)] public async Task&lt;IActionResult&gt; GetCompany(Guid id)<\/code><\/pre>\n<p>Once we send the GetCompanies request, we are going to see global values:<br \/>\n\u53d1\u9001 GetCompanies \u8bf7\u6c42\u540e\uff0c\u6211\u4eec\u5c06\u770b\u5230\u5168\u5c40\u503c\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2514.jpg\" alt=\"alt text\" \/><\/p>\n<p>But if we send the GetCompany request:<br \/>\n\u4f46\u662f\uff0c\u5982\u679c\u6211\u4eec\u53d1\u9001 GetCompany \u8bf7\u6c42\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2515.jpg\" alt=\"alt text\" \/><\/p>\n<p>You can see that it is public and you can send the same request again to see the Age header for the cached response.<br \/>\n\u60a8\u53ef\u4ee5\u770b\u5230\u5b83\u662f\u516c\u6709\u7684\uff0c\u5e76\u4e14\u53ef\u4ee5\u518d\u6b21\u53d1\u9001\u76f8\u540c\u7684\u8bf7\u6c42\u4ee5\u67e5\u770b\u7f13\u5b58\u54cd\u5e94\u7684 Age \u6807\u5934\u3002<\/p>\n<h2>25.7 Using ETag and Validation<\/h2>\n<p>25.7 \u4f7f\u7528 ETag \u548c\u9a8c\u8bc1<\/p>\n<p>First, we have to mention that the ResponseCaching library doesn\u2019t correctly implement the validation model. Also, using the authorization header is a problem. We are going to show you alternatives later. But for now, we can simulate how validation with Etag should work.\u200c<br \/>\n\u9996\u5148\uff0c\u6211\u4eec\u5fc5\u987b\u63d0\u5230 ResponseCaching \u5e93\u6ca1\u6709\u6b63\u786e\u5b9e\u73b0\u9a8c\u8bc1\u6a21\u578b\u3002\u6b64\u5916\uff0c\u4f7f\u7528 authorization \u6807\u5934\u4e5f\u662f\u4e00\u4e2a\u95ee\u9898\u3002\u6211\u4eec\u7a0d\u540e\u5c06\u5411\u60a8\u5c55\u793a\u66ff\u4ee3\u65b9\u6848\u3002\u4f46\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u6a21\u62df\u4f7f\u7528 Etag \u8fdb\u884c\u9a8c\u8bc1\u5e94\u8be5\u5982\u4f55\u5de5\u4f5c\u3002<\/p>\n<p>So, let\u2019s restart our app to have a fresh application, and send a GetCompany request one more time. In a header, we are going to get our ETag. Let\u2019s copy the Etag\u2019s value and use another GetCompany request:<br \/>\n\u56e0\u6b64\uff0c\u8ba9\u6211\u4eec\u91cd\u65b0\u542f\u52a8\u5e94\u7528\u7a0b\u5e8f\u4ee5\u83b7\u5f97\u65b0\u7684\u5e94\u7528\u7a0b\u5e8f\uff0c\u5e76\u518d\u6b21\u53d1\u9001 GetCompany \u8bf7\u6c42\u3002\u5728 header \u4e2d\uff0c\u6211\u4eec\u5c06\u83b7\u53d6\u6211\u4eec\u7684 ETag\u3002\u8ba9\u6211\u4eec\u590d\u5236 Etag \u7684\u503c\u5e76\u4f7f\u7528\u53e6\u4e00\u4e2a GetCompany \u8bf7\u6c42\uff1a<br \/>\n<a href=\"https:\/\/localhost:5001\/api\/companies\/3d490a70-94ce-4d15-9494-5248280c2ce3\">https:\/\/localhost:5001\/api\/companies\/3d490a70-94ce-4d15-9494-5248280c2ce3<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2516.jpg\" alt=\"alt text\" \/><\/p>\n<p>We send the If-None-Match tag with the value of our Etag. And we can see as a result we get 304 Not Modified.<br \/>\n\u6211\u4eec\u53d1\u9001\u5e26\u6709 Etag \u503c\u7684 If-None-Match \u6807\u7b7e\u3002\u6211\u4eec\u53ef\u4ee5\u770b\u5230\uff0c\u7ed3\u679c\u662f 304 Not Modified\u3002<\/p>\n<p>But this is not a valid situation. As we said, the client should send a valid request and it is up to the Cache to add an If-None-Match tag. In our example, which we sent from Postman, we simulated that. Then, it is up to the server to return a 304 message to the cache and then the cache should return the same response.<br \/>\n\u4f46\u8fd9\u4e0d\u662f\u4e00\u4e2a\u6709\u6548\u7684\u60c5\u51b5\u3002\u6b63\u5982\u6211\u4eec\u6240\u8bf4\uff0c\u5ba2\u6237\u7aef\u5e94\u8be5\u53d1\u9001\u4e00\u4e2a\u6709\u6548\u7684\u8bf7\u6c42\uff0c\u5e76\u4e14\u7531 Cache \u6dfb\u52a0 If-None-Match \u6807\u7b7e\u3002\u5728\u6211\u4eec\u4ece Postman \u53d1\u9001\u7684\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u5bf9\u6b64\u8fdb\u884c\u4e86\u6a21\u62df\u3002\u7136\u540e\uff0c\u7531\u670d\u52a1\u5668\u5c06 304 \u6d88\u606f\u8fd4\u56de\u5230\u7f13\u5b58\uff0c\u7136\u540e\u7f13\u5b58\u5e94\u8fd4\u56de\u76f8\u540c\u7684\u54cd\u5e94\u3002<\/p>\n<p>But anyhow, we have managed to show you how validation works. If we update that company:<br \/>\n\u4f46\u65e0\u8bba\u5982\u4f55\uff0c\u6211\u4eec\u5df2\u7ecf\u8bbe\u6cd5\u5411\u60a8\u5c55\u793a\u4e86\u9a8c\u8bc1\u7684\u5de5\u4f5c\u539f\u7406\u3002\u5982\u679c\u6211\u4eec\u66f4\u65b0\u8be5\u516c\u53f8\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/3d490a70-94ce-4d15-9494-5248280c2ce3\">https:\/\/localhost:5001\/api\/companies\/3d490a70-94ce-4d15-9494-5248280c2ce3<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2517.jpg\" alt=\"alt text\" \/><\/p>\n<p>And then send the same request with the same If-None-Match value:<br \/>\n\u7136\u540e\u53d1\u9001\u5177\u6709\u76f8\u540c If-None-Match \u503c\u7684\u76f8\u540c\u8bf7\u6c42\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/3d490a70-94ce-4d15-9494-5248280c2ce3\">https:\/\/localhost:5001\/api\/companies\/3d490a70-94ce-4d15-9494-5248280c2ce3<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2518.jpg\" alt=\"alt text\" \/><\/p>\n<p>You can see that we get 200 OK and if we inspect Headers, we will find that ETag is different because the resource changed:<br \/>\n\u4f60\u53ef\u4ee5\u770b\u5230\u6211\u4eec\u5f97\u5230 200 OK\uff0c\u5982\u679c\u6211\u4eec\u68c0\u67e5 Headers\uff0c\u6211\u4eec\u4f1a\u53d1\u73b0 ETag \u662f\u4e0d\u540c\u7684\uff0c\u56e0\u4e3a\u8d44\u6e90\u53d1\u751f\u4e86\u53d8\u5316\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2519.jpg\" alt=\"alt text\" \/><\/p>\n<p>So, we saw how validation works and also concluded that the ResponseCaching library is not that good for validation \u2014 it is much better for just expiration.<br \/>\n\u56e0\u6b64\uff0c\u6211\u4eec\u770b\u5230\u4e86\u9a8c\u8bc1\u7684\u5de5\u4f5c\u539f\u7406\uff0c\u5e76\u5f97\u51fa\u7ed3\u8bba\uff0cResponseCaching \u5e93\u5bf9\u4e8e\u9a8c\u8bc1\u6765\u8bf4\u4e0d\u662f\u90a3\u4e48\u597d \u2014 \u5b83\u66f4\u9002\u5408\u4e8e\u8fc7\u671f\u3002<\/p>\n<p>But then, what are the alternatives? There are a lot of alternatives, such as:<br \/>\n\u4f46\u662f\uff0c\u8fd8\u6709\u54ea\u4e9b\u9009\u62e9\u5462\uff1f\u6709\u5f88\u591a\u9009\u62e9\uff0c\u4f8b\u5982\uff1a<\/p>\n<p>\u2022 Varnish - <a href=\"https:\/\/varnish-cache.org\/\">https:\/\/varnish-cache.org\/<\/a><\/p>\n<p>\u2022 Apache Traffic Server - <a href=\"https:\/\/trafficserver.apache.org\/\">https:\/\/trafficserver.apache.org\/<\/a><\/p>\n<p>\u2022 Squid - <a href=\"http:\/\/www.squid-cache.org\/\">http:\/\/www.squid-cache.org\/<\/a><\/p>\n<p>They implement caching correctly. And if you want to have expiration and validation, you should combine them with the Marvin library and you are good to go. But those servers are not that trivial to implement.<br \/>\n\u5b83\u4eec\u6b63\u786e\u5730\u5b9e\u73b0\u4e86\u7f13\u5b58\u3002\u5982\u679c\u4f60\u60f3\u62e5\u6709\u8fc7\u671f\u548c\u9a8c\u8bc1\uff0c\u4f60\u5e94\u8be5\u5c06\u5b83\u4eec\u4e0e Marvin \u5e93\u7ed3\u5408\u8d77\u6765\uff0c\u4e00\u5207\u987a\u5229\u3002\u4f46\u8fd9\u4e9b\u670d\u52a1\u5668\u5e76\u4e0d\u662f\u90a3\u4e48\u5bb9\u6613\u5b9e\u73b0\u3002<\/p>\n<p>There is another option: CDN (Content Delivery Network). CDN uses HTTP caching and is used by various sites on the internet. The good thing with CDN is we don\u2019t need to set up a cache server by ourselves, but unfortunately, we have to pay for it. The previous cache servers we presented are free to use. So, it\u2019s up to you to decide what suits you best.<br \/>\n\u8fd8\u6709\u53e6\u4e00\u4e2a\u9009\u9879\uff1aCDN\uff08\u5185\u5bb9\u4ea4\u4ed8\u7f51\u7edc\uff09\u3002CDN \u4f7f\u7528 HTTP \u7f13\u5b58\uff0c\u5e76\u88ab Internet \u4e0a\u7684\u5404\u79cd\u7ad9\u70b9\u4f7f\u7528\u3002CDN \u7684\u597d\u5904\u662f\u6211\u4eec\u4e0d\u9700\u8981\u81ea\u5df1\u8bbe\u7f6e\u7f13\u5b58\u670d\u52a1\u5668\uff0c\u4f46\u4e0d\u5e78\u7684\u662f\uff0c\u6211\u4eec\u5fc5\u987b\u4e3a\u6b64\u4ed8\u8d39\u3002\u6211\u4eec\u4e4b\u524d\u4ecb\u7ecd\u7684\u7f13\u5b58\u670d\u52a1\u5668\u53ef\u4ee5\u514d\u8d39\u4f7f\u7528\u3002\u56e0\u6b64\uff0c\u7531\u60a8\u51b3\u5b9a\u4ec0\u4e48\u6700\u9002\u5408\u60a8\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>25 CACHING 25 \u7f13\u5b58 In this section, we are going to learn about caching resources. Caching can improve the quality and performance of our app a lot, but again, it is something first we need to look at as soon as some bug appears. To cover resource caching, we are going to work with HTTP [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1148","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1148","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1148"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1148\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1148"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1148"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1148"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}