2
2
using System . Collections . Generic ;
3
3
using System . Net ;
4
4
using System . Net . Http ;
5
+ using System . Net . Http . Headers ;
5
6
using System . Threading ;
6
7
using System . Threading . Tasks ;
7
8
using Microsoft . Extensions . Caching . Abstractions ;
@@ -14,7 +15,27 @@ namespace Microsoft.Extensions.Caching.InMemory
14
15
/// </summary>
15
16
public class InMemoryCacheHandler : DelegatingHandler
16
17
{
17
- private static HashSet < HttpMethod > CachedHttpMethods = new HashSet < HttpMethod >
18
+ #if NET5_0_OR_GREATER
19
+ /// <summary>
20
+ /// The key to use to store the UseCache value in the HttpRequestMessage.Options dictionary.
21
+ /// This key is used to determine if the cache should be checked for the request.
22
+ /// If the key is not present, the cache will be checked.
23
+ /// If the key is present and the value is false, the cache will not be checked.
24
+ /// If the key is present and the value is true, the cache will be checked.
25
+ /// </summary>
26
+ public static readonly HttpRequestOptionsKey < bool > UseCache = new HttpRequestOptionsKey < bool > ( nameof ( UseCache ) ) ;
27
+ #else
28
+ /// <summary>
29
+ /// The key to use to store the UseCache value in the HttpRequestMessage.Properties dictionary.
30
+ /// This key is used to determine if the cache should be checked for the request.
31
+ /// If the key is not present, the cache will be checked.
32
+ /// If the key is present and the value is false, the cache will not be checked.
33
+ /// If the key is present and the value is true, the cache will be checked.
34
+ /// </summary>
35
+ public const string UseCache = nameof ( UseCache ) ;
36
+ #endif
37
+
38
+ private static readonly HashSet < HttpMethod > CachedHttpMethods = new HashSet < HttpMethod >
18
39
{
19
40
HttpMethod . Get ,
20
41
HttpMethod . Head
@@ -50,10 +71,10 @@ public InMemoryCacheHandler(HttpMessageHandler innerHandler = null,
50
71
IStatsProvider statsProvider = null ,
51
72
ICacheKeysProvider cacheKeysProvider = null )
52
73
: this ( innerHandler ,
53
- cacheExpirationPerHttpResponseCode ,
54
- statsProvider ,
55
- new MemoryCache ( new MemoryCacheOptions ( ) ) ,
56
- cacheKeysProvider )
74
+ cacheExpirationPerHttpResponseCode ,
75
+ statsProvider ,
76
+ new MemoryCache ( new MemoryCacheOptions ( ) ) ,
77
+ cacheKeysProvider )
57
78
{
58
79
}
59
80
@@ -104,6 +125,40 @@ public void InvalidateCache(Uri uri, HttpMethod httpMethod = null)
104
125
}
105
126
}
106
127
128
+ /// <summary>
129
+ /// Determines if the cache should be checked for the request.
130
+ /// </summary>
131
+ /// <param name="request"></param>
132
+ /// <returns>A bool representing if the cache should be cached or not</returns>
133
+ private static bool ShouldTheCacheBeChecked ( HttpRequestMessage request )
134
+ {
135
+ #if NET5_0_OR_GREATER
136
+ var useCacheOption = request . Options . TryGetValue ( UseCache , out var useCache ) == false || useCache == true ;
137
+ #else
138
+ var useCacheOption = request . Properties . TryGetValue ( UseCache , out var useCache ) == false || ( bool ) useCache == true ;
139
+ #endif
140
+
141
+ return useCacheOption && request . Headers . CacheControl ? . NoCache != true ;
142
+ }
143
+
144
+ /// <summary>
145
+ /// Determines if the response should be cached.
146
+ /// </summary>
147
+ /// <param name="response"></param>
148
+ /// <returns>A bool representing if the response should be cached or not</returns>
149
+ private static bool ShouldCacheResponse ( HttpResponseMessage response )
150
+ {
151
+ if ( response . Headers . CacheControl is CacheControlHeaderValue cacheControl )
152
+ {
153
+ return
154
+ cacheControl . NoStore == false &&
155
+ cacheControl . NoCache == false &&
156
+ response . StatusCode != HttpStatusCode . NotModified ;
157
+ }
158
+
159
+ return response . StatusCode != HttpStatusCode . NotModified ;
160
+ }
161
+
107
162
/// <summary>
108
163
/// Tries to get the value from the cache, and only calls the delegating handler on cache misses.
109
164
/// </summary>
@@ -114,7 +169,10 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
114
169
115
170
// Gets the data from cache, and returns the data if it's a cache hit
116
171
var isCachedHttpMethod = CachedHttpMethods . Contains ( request . Method ) ;
117
- if ( isCachedHttpMethod )
172
+
173
+ // Check if the cache should be checked
174
+ var shouldCheckCache = ShouldTheCacheBeChecked ( request ) ;
175
+ if ( shouldCheckCache && isCachedHttpMethod )
118
176
{
119
177
key = this . CacheKeysProvider . GetKey ( request ) ;
120
178
@@ -131,13 +189,17 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
131
189
if ( isCachedHttpMethod )
132
190
{
133
191
var absoluteExpirationRelativeToNow = response . StatusCode . GetAbsoluteExpirationRelativeToNow ( this . cacheExpirationPerHttpResponseCode ) ;
192
+ var maxAgeHeader = response . Headers . CacheControl ? . MaxAge ?? TimeSpan . MaxValue ;
193
+
194
+ // If the server sends a Cache-Control header with a max-age that is less than the configured expiration time, use the max-age value
195
+ var maxCacheTime = ( maxAgeHeader < absoluteExpirationRelativeToNow ) ? maxAgeHeader : absoluteExpirationRelativeToNow ;
134
196
135
197
this . StatsProvider . ReportCacheMiss ( response . StatusCode ) ;
136
198
137
- if ( TimeSpan . Zero != absoluteExpirationRelativeToNow )
199
+ if ( ShouldCacheResponse ( response ) && TimeSpan . Zero != maxCacheTime )
138
200
{
139
201
var entry = await response . ToCacheEntryAsync ( ) ;
140
- await this . responseCache . TrySetAsync ( key , entry , absoluteExpirationRelativeToNow ) ;
202
+ await this . responseCache . TrySetAsync ( key , entry , maxCacheTime ) ;
141
203
return request . PrepareCachedEntry ( entry ) ;
142
204
}
143
205
}
@@ -153,7 +215,10 @@ protected override HttpResponseMessage Send(HttpRequestMessage request, Cancella
153
215
154
216
// Gets the data from cache, and returns the data if it's a cache hit
155
217
var isCachedHttpMethod = CachedHttpMethods . Contains ( request . Method ) ;
156
- if ( isCachedHttpMethod )
218
+
219
+ // Check if the cache should be checked
220
+ var shouldCheckCache = ShouldTheCacheBeChecked ( request ) ;
221
+ if ( shouldCheckCache && isCachedHttpMethod )
157
222
{
158
223
key = this . CacheKeysProvider . GetKey ( request ) ;
159
224
@@ -169,13 +234,17 @@ protected override HttpResponseMessage Send(HttpRequestMessage request, Cancella
169
234
if ( isCachedHttpMethod )
170
235
{
171
236
var absoluteExpirationRelativeToNow = response . StatusCode . GetAbsoluteExpirationRelativeToNow ( this . cacheExpirationPerHttpResponseCode ) ;
237
+ var maxAgeHeader = response . Headers . CacheControl ? . MaxAge ?? TimeSpan . MaxValue ;
238
+
239
+ // If the server sends a Cache-Control header with a max-age that is less than the configured expiration time, use the max-age value
240
+ var maxCacheTime = ( maxAgeHeader < absoluteExpirationRelativeToNow ) ? maxAgeHeader : absoluteExpirationRelativeToNow ;
172
241
173
242
this . StatsProvider . ReportCacheMiss ( response . StatusCode ) ;
174
243
175
- if ( TimeSpan . Zero != absoluteExpirationRelativeToNow )
244
+ if ( ShouldCacheResponse ( response ) && TimeSpan . Zero != maxCacheTime )
176
245
{
177
246
var cacheData = response . ToCacheEntry ( ) ;
178
- this . responseCache . TrySetCacheData ( key , cacheData , absoluteExpirationRelativeToNow ) ;
247
+ this . responseCache . TrySetCacheData ( key , cacheData , maxCacheTime ) ;
179
248
return request . PrepareCachedEntry ( cacheData ) ;
180
249
}
181
250
}
0 commit comments