Allow lookup of multiple keys
- Do not cache if content-length mismatches with the length of the response body
This commit is contained in:
parent
6a04fe5fb7
commit
65b89668bb
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCaching
|
||||
|
|
@ -8,18 +9,33 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
public interface IKeyProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a base key using the HTTP request.
|
||||
/// Create a base key using the HTTP context for storing items.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/>.</param>
|
||||
/// <returns>The created base key.</returns>
|
||||
string CreateBaseKey(HttpContext httpContext);
|
||||
string CreateStorageBaseKey(HttpContext httpContext);
|
||||
|
||||
/// <summary>
|
||||
/// Create a vary key using the HTTP context and vary rules.
|
||||
/// Create one or more base keys using the HTTP context for looking up items.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/>.</param>
|
||||
/// <returns>An ordered <see cref="IEnumerable{T}"/> containing the base keys to try when looking up items.</returns>
|
||||
IEnumerable<string> CreateLookupBaseKey(HttpContext httpContext);
|
||||
|
||||
/// <summary>
|
||||
/// Create a vary key using the HTTP context and vary rules for storing items.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/>.</param>
|
||||
/// <param name="varyRules">The <see cref="VaryRules"/>.</param>
|
||||
/// <returns>The created vary key.</returns>
|
||||
string CreateVaryKey(HttpContext httpContext, VaryRules varyRules);
|
||||
string CreateStorageVaryKey(HttpContext httpContext, VaryRules varyRules);
|
||||
|
||||
/// <summary>
|
||||
/// Create one or more vary keys using the HTTP context and vary rules for looking up items.
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/>.</param>
|
||||
/// <param name="varyRules">The <see cref="VaryRules"/>.</param>
|
||||
/// <returns>An ordered <see cref="IEnumerable{T}"/> containing the vary keys to try when looking up items.</returns>
|
||||
IEnumerable<string> CreateLookupVaryKey(HttpContext httpContext, VaryRules varyRules);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
|
||||
public bool ShouldCacheResponse { get; internal set; }
|
||||
|
||||
public string BaseKey { get; internal set; }
|
||||
public string StorageBaseKey { get; internal set; }
|
||||
|
||||
public string VaryKey { get; internal set; }
|
||||
public string StorageVaryKey { get; internal set; }
|
||||
|
||||
public DateTimeOffset ResponseTime { get; internal set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
|
@ -35,9 +36,19 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
_options = options.Value;
|
||||
}
|
||||
|
||||
public virtual IEnumerable<string> CreateLookupBaseKey(HttpContext httpContext)
|
||||
{
|
||||
return new string[] { CreateStorageBaseKey(httpContext) };
|
||||
}
|
||||
|
||||
public virtual IEnumerable<string> CreateLookupVaryKey(HttpContext httpContext, VaryRules varyRules)
|
||||
{
|
||||
return new string[] { CreateStorageVaryKey(httpContext, varyRules) };
|
||||
}
|
||||
|
||||
// GET<delimiter>/PATH
|
||||
// TODO: Method invariant retrieval? E.g. HEAD after GET to the same resource.
|
||||
public virtual string CreateBaseKey(HttpContext httpContext)
|
||||
public virtual string CreateStorageBaseKey(HttpContext httpContext)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
|
|
@ -63,7 +74,7 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
}
|
||||
|
||||
// BaseKey<delimiter>H<delimiter>HeaderName=HeaderValue<delimiter>Q<delimiter>QueryName=QueryValue
|
||||
public virtual string CreateVaryKey(HttpContext httpContext, VaryRules varyRules)
|
||||
public virtual string CreateStorageVaryKey(HttpContext httpContext, VaryRules varyRules)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -59,84 +59,101 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
|
||||
private IHttpSendFileFeature OriginalSendFileFeature { get; set; }
|
||||
|
||||
internal async Task<bool> TryServeFromCacheAsync()
|
||||
internal async Task<bool> TryServeCachedResponseAsync(CachedResponse cachedResponse)
|
||||
{
|
||||
State.BaseKey = _keyProvider.CreateBaseKey(_httpContext);
|
||||
var cacheEntry = _cache.Get(State.BaseKey);
|
||||
var responseServed = false;
|
||||
State.CachedResponse = cachedResponse;
|
||||
var cachedResponseHeaders = new ResponseHeaders(State.CachedResponse.Headers);
|
||||
|
||||
if (cacheEntry is CachedVaryRules)
|
||||
State.ResponseTime = _options.SystemClock.UtcNow;
|
||||
var cachedEntryAge = State.ResponseTime - State.CachedResponse.Created;
|
||||
State.CachedEntryAge = cachedEntryAge > TimeSpan.Zero ? cachedEntryAge : TimeSpan.Zero;
|
||||
|
||||
if (_cacheabilityValidator.CachedEntryIsFresh(_httpContext, cachedResponseHeaders))
|
||||
{
|
||||
// Request contains vary rules, recompute key and try again
|
||||
State.CachedVaryRules = cacheEntry as CachedVaryRules;
|
||||
var varyKey = _keyProvider.CreateVaryKey(_httpContext, ((CachedVaryRules)cacheEntry).VaryRules);
|
||||
cacheEntry = _cache.Get(varyKey);
|
||||
}
|
||||
|
||||
if (cacheEntry is CachedResponse)
|
||||
{
|
||||
State.CachedResponse = cacheEntry as CachedResponse;
|
||||
var cachedResponseHeaders = new ResponseHeaders(State.CachedResponse.Headers);
|
||||
|
||||
State.ResponseTime = _options.SystemClock.UtcNow;
|
||||
var cachedEntryAge = State.ResponseTime - State.CachedResponse.Created;
|
||||
State.CachedEntryAge = cachedEntryAge > TimeSpan.Zero ? cachedEntryAge : TimeSpan.Zero;
|
||||
|
||||
if (_cacheabilityValidator.CachedEntryIsFresh(_httpContext, cachedResponseHeaders))
|
||||
// Check conditional request rules
|
||||
if (ConditionalRequestSatisfied(cachedResponseHeaders))
|
||||
{
|
||||
responseServed = true;
|
||||
|
||||
// Check conditional request rules
|
||||
if (ConditionalRequestSatisfied(cachedResponseHeaders))
|
||||
{
|
||||
_httpContext.Response.StatusCode = StatusCodes.Status304NotModified;
|
||||
}
|
||||
else
|
||||
{
|
||||
var response = _httpContext.Response;
|
||||
// Copy the cached status code and response headers
|
||||
response.StatusCode = State.CachedResponse.StatusCode;
|
||||
foreach (var header in State.CachedResponse.Headers)
|
||||
{
|
||||
response.Headers.Add(header);
|
||||
}
|
||||
|
||||
response.Headers[HeaderNames.Age] = State.CachedEntryAge.TotalSeconds.ToString("F0", CultureInfo.InvariantCulture);
|
||||
|
||||
var body = State.CachedResponse.Body ??
|
||||
((CachedResponseBody)_cache.Get(State.CachedResponse.BodyKeyPrefix))?.Body;
|
||||
|
||||
// If the body is not found, something went wrong.
|
||||
if (body == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy the cached response body
|
||||
if (body.Length > 0)
|
||||
{
|
||||
// Add a content-length if required
|
||||
if (response.ContentLength == null && StringValues.IsNullOrEmpty(response.Headers[HeaderNames.TransferEncoding]))
|
||||
{
|
||||
response.ContentLength = body.Length;
|
||||
}
|
||||
await response.Body.WriteAsync(body, 0, body.Length);
|
||||
}
|
||||
}
|
||||
_httpContext.Response.StatusCode = StatusCodes.Status304NotModified;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Validate with endpoint instead
|
||||
var response = _httpContext.Response;
|
||||
// Copy the cached status code and response headers
|
||||
response.StatusCode = State.CachedResponse.StatusCode;
|
||||
foreach (var header in State.CachedResponse.Headers)
|
||||
{
|
||||
response.Headers.Add(header);
|
||||
}
|
||||
|
||||
response.Headers[HeaderNames.Age] = State.CachedEntryAge.TotalSeconds.ToString("F0", CultureInfo.InvariantCulture);
|
||||
|
||||
var body = State.CachedResponse.Body ??
|
||||
((CachedResponseBody)_cache.Get(State.CachedResponse.BodyKeyPrefix))?.Body;
|
||||
|
||||
// If the body is not found, something went wrong.
|
||||
if (body == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy the cached response body
|
||||
if (body.Length > 0)
|
||||
{
|
||||
// Add a content-length if required
|
||||
if (response.ContentLength == null && StringValues.IsNullOrEmpty(response.Headers[HeaderNames.TransferEncoding]))
|
||||
{
|
||||
response.ContentLength = body.Length;
|
||||
}
|
||||
await response.Body.WriteAsync(body, 0, body.Length);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Validate with endpoint instead
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal async Task<bool> TryServeFromCacheAsync()
|
||||
{
|
||||
foreach (var baseKey in _keyProvider.CreateLookupBaseKey(_httpContext))
|
||||
{
|
||||
var cacheEntry = _cache.Get(baseKey);
|
||||
|
||||
if (cacheEntry is CachedVaryRules)
|
||||
{
|
||||
// Request contains vary rules, recompute key(s) and try again
|
||||
State.CachedVaryRules = cacheEntry as CachedVaryRules;
|
||||
|
||||
foreach (var varyKey in _keyProvider.CreateLookupVaryKey(_httpContext, State.CachedVaryRules.VaryRules))
|
||||
{
|
||||
cacheEntry = _cache.Get(varyKey);
|
||||
|
||||
if (cacheEntry is CachedResponse && await TryServeCachedResponseAsync(cacheEntry as CachedResponse))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cacheEntry is CachedResponse && await TryServeCachedResponseAsync(cacheEntry as CachedResponse))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!responseServed && State.RequestCacheControl.OnlyIfCached)
|
||||
|
||||
if (State.RequestCacheControl.OnlyIfCached)
|
||||
{
|
||||
_httpContext.Response.StatusCode = StatusCodes.Status504GatewayTimeout;
|
||||
responseServed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return responseServed;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal bool ConditionalRequestSatisfied(ResponseHeaders cachedResponseHeaders)
|
||||
|
|
@ -174,6 +191,7 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
if (_cacheabilityValidator.ResponseIsCacheable(_httpContext))
|
||||
{
|
||||
State.ShouldCacheResponse = true;
|
||||
State.StorageBaseKey = _keyProvider.CreateStorageBaseKey(_httpContext);
|
||||
|
||||
// Create the cache entry now
|
||||
var response = _httpContext.Response;
|
||||
|
|
@ -209,10 +227,10 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
};
|
||||
|
||||
State.CachedVaryRules = cachedVaryRules;
|
||||
_cache.Set(State.BaseKey, cachedVaryRules, State.CachedResponseValidFor);
|
||||
_cache.Set(State.StorageBaseKey, cachedVaryRules, State.CachedResponseValidFor);
|
||||
}
|
||||
|
||||
State.VaryKey = _keyProvider.CreateVaryKey(_httpContext, State.CachedVaryRules.VaryRules);
|
||||
State.StorageVaryKey = _keyProvider.CreateStorageVaryKey(_httpContext, State.CachedVaryRules.VaryRules);
|
||||
}
|
||||
|
||||
// Ensure date header is set
|
||||
|
|
@ -245,12 +263,15 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
|
||||
internal void FinalizeCachingBody()
|
||||
{
|
||||
if (State.ShouldCacheResponse && ResponseCacheStream.BufferingEnabled)
|
||||
if (State.ShouldCacheResponse &&
|
||||
ResponseCacheStream.BufferingEnabled &&
|
||||
(State.ResponseHeaders.ContentLength == null ||
|
||||
State.ResponseHeaders.ContentLength == ResponseCacheStream.BufferedStream.Length))
|
||||
{
|
||||
if (ResponseCacheStream.BufferedStream.Length >= _options.MinimumSplitBodySize)
|
||||
{
|
||||
// Store response and response body separately
|
||||
_cache.Set(State.VaryKey ?? State.BaseKey, State.CachedResponse, State.CachedResponseValidFor);
|
||||
_cache.Set(State.StorageVaryKey ?? State.StorageBaseKey, State.CachedResponse, State.CachedResponseValidFor);
|
||||
|
||||
var cachedResponseBody = new CachedResponseBody()
|
||||
{
|
||||
|
|
@ -263,7 +284,7 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
{
|
||||
// Store response and response body together
|
||||
State.CachedResponse.Body = ResponseCacheStream.BufferedStream.ToArray();
|
||||
_cache.Set(State.VaryKey ?? State.BaseKey, State.CachedResponse, State.CachedResponseValidFor);
|
||||
_cache.Set(State.StorageVaryKey ?? State.StorageBaseKey, State.CachedResponse, State.CachedResponseValidFor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
};
|
||||
|
||||
[Fact]
|
||||
public void DefaultKeyProvider_CreateBaseKey_IncludesOnlyNormalizedMethodAndPath()
|
||||
public void DefaultKeyProvider_CreateStorageBaseKey_IncludesOnlyNormalizedMethodAndPath()
|
||||
{
|
||||
var httpContext = CreateDefaultContext();
|
||||
httpContext.Request.Method = "head";
|
||||
|
|
@ -30,11 +30,11 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
httpContext.Request.QueryString = new QueryString("?query.Key=a&query.Value=b");
|
||||
var keyProvider = CreateTestKeyProvider();
|
||||
|
||||
Assert.Equal($"HEAD{KeyDelimiter}/PATH/SUBPATH", keyProvider.CreateBaseKey(httpContext));
|
||||
Assert.Equal($"HEAD{KeyDelimiter}/PATH/SUBPATH", keyProvider.CreateStorageBaseKey(httpContext));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultKeyProvider_CreateBaseKey_CaseInsensitivePath_NormalizesPath()
|
||||
public void DefaultKeyProvider_CreateStorageBaseKey_CaseInsensitivePath_NormalizesPath()
|
||||
{
|
||||
var httpContext = CreateDefaultContext();
|
||||
httpContext.Request.Method = "GET";
|
||||
|
|
@ -44,11 +44,11 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
CaseSensitivePaths = false
|
||||
});
|
||||
|
||||
Assert.Equal($"GET{KeyDelimiter}/PATH", keyProvider.CreateBaseKey(httpContext));
|
||||
Assert.Equal($"GET{KeyDelimiter}/PATH", keyProvider.CreateStorageBaseKey(httpContext));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultKeyProvider_CreateBaseKey_CaseSensitivePath_PreservesPathCase()
|
||||
public void DefaultKeyProvider_CreateStorageBaseKey_CaseSensitivePath_PreservesPathCase()
|
||||
{
|
||||
var httpContext = CreateDefaultContext();
|
||||
httpContext.Request.Method = "GET";
|
||||
|
|
@ -58,21 +58,21 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
CaseSensitivePaths = true
|
||||
});
|
||||
|
||||
Assert.Equal($"GET{KeyDelimiter}/Path", keyProvider.CreateBaseKey(httpContext));
|
||||
Assert.Equal($"GET{KeyDelimiter}/Path", keyProvider.CreateStorageBaseKey(httpContext));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultKeyProvider_CreateVaryKey_ReturnsCachedVaryGuid_IfVaryRulesIsNullOrEmpty()
|
||||
public void DefaultKeyProvider_CreateStorageVaryKey_ReturnsCachedVaryGuid_IfVaryRulesIsNullOrEmpty()
|
||||
{
|
||||
var httpContext = CreateDefaultContext();
|
||||
var keyProvider = CreateTestKeyProvider();
|
||||
|
||||
Assert.Equal($"{TestVaryRules.VaryKeyPrefix}", keyProvider.CreateVaryKey(httpContext, null));
|
||||
Assert.Equal($"{TestVaryRules.VaryKeyPrefix}", keyProvider.CreateVaryKey(httpContext, new VaryRules()));
|
||||
Assert.Equal($"{TestVaryRules.VaryKeyPrefix}", keyProvider.CreateStorageVaryKey(httpContext, null));
|
||||
Assert.Equal($"{TestVaryRules.VaryKeyPrefix}", keyProvider.CreateStorageVaryKey(httpContext, new VaryRules()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultKeyProvider_CreateVaryKey_IncludesListedHeadersOnly()
|
||||
public void DefaultKeyProvider_CreateStorageVaryKey_IncludesListedHeadersOnly()
|
||||
{
|
||||
var httpContext = CreateDefaultContext();
|
||||
httpContext.Request.Headers["HeaderA"] = "ValueA";
|
||||
|
|
@ -80,42 +80,42 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
var keyProvider = CreateTestKeyProvider();
|
||||
|
||||
Assert.Equal($"{TestVaryRules.VaryKeyPrefix}{KeyDelimiter}H{KeyDelimiter}HeaderA=ValueA{KeyDelimiter}HeaderC=null",
|
||||
keyProvider.CreateVaryKey(httpContext, new VaryRules()
|
||||
keyProvider.CreateStorageVaryKey(httpContext, new VaryRules()
|
||||
{
|
||||
Headers = new string[] { "HeaderA", "HeaderC" }
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultKeyProvider_CreateVaryKey_IncludesListedParamsOnly()
|
||||
public void DefaultKeyProvider_CreateStorageVaryKey_IncludesListedParamsOnly()
|
||||
{
|
||||
var httpContext = CreateDefaultContext();
|
||||
httpContext.Request.QueryString = new QueryString("?ParamA=ValueA&ParamB=ValueB");
|
||||
var keyProvider = CreateTestKeyProvider();
|
||||
|
||||
Assert.Equal($"{TestVaryRules.VaryKeyPrefix}{KeyDelimiter}Q{KeyDelimiter}ParamA=ValueA{KeyDelimiter}ParamC=null",
|
||||
keyProvider.CreateVaryKey(httpContext, new VaryRules()
|
||||
keyProvider.CreateStorageVaryKey(httpContext, new VaryRules()
|
||||
{
|
||||
Params = new string[] { "ParamA", "ParamC" }
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultKeyProvider_CreateVaryKey_IncludesParams_ParamNameCaseInsensitive_UseParamCasing()
|
||||
public void DefaultKeyProvider_CreateStorageVaryKey_IncludesParams_ParamNameCaseInsensitive_UseParamCasing()
|
||||
{
|
||||
var httpContext = CreateDefaultContext();
|
||||
httpContext.Request.QueryString = new QueryString("?parama=ValueA¶mB=ValueB");
|
||||
var keyProvider = CreateTestKeyProvider();
|
||||
|
||||
Assert.Equal($"{TestVaryRules.VaryKeyPrefix}{KeyDelimiter}Q{KeyDelimiter}ParamA=ValueA{KeyDelimiter}ParamC=null",
|
||||
keyProvider.CreateVaryKey(httpContext, new VaryRules()
|
||||
keyProvider.CreateStorageVaryKey(httpContext, new VaryRules()
|
||||
{
|
||||
Params = new string[] { "ParamA", "ParamC" }
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultKeyProvider_CreateVaryKey_IncludesAllQueryParamsGivenAsterisk()
|
||||
public void DefaultKeyProvider_CreateStorageVaryKey_IncludesAllQueryParamsGivenAsterisk()
|
||||
{
|
||||
var httpContext = CreateDefaultContext();
|
||||
httpContext.Request.QueryString = new QueryString("?ParamA=ValueA&ParamB=ValueB");
|
||||
|
|
@ -124,14 +124,14 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
// To support case insensitivity, all param keys are converted to upper case.
|
||||
// Explicit params uses the casing specified in the setting.
|
||||
Assert.Equal($"{TestVaryRules.VaryKeyPrefix}{KeyDelimiter}Q{KeyDelimiter}PARAMA=ValueA{KeyDelimiter}PARAMB=ValueB",
|
||||
keyProvider.CreateVaryKey(httpContext, new VaryRules()
|
||||
keyProvider.CreateStorageVaryKey(httpContext, new VaryRules()
|
||||
{
|
||||
Params = new string[] { "*" }
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultKeyProvider_CreateVaryKey_IncludesListedHeadersAndParams()
|
||||
public void DefaultKeyProvider_CreateStorageVaryKey_IncludesListedHeadersAndParams()
|
||||
{
|
||||
var httpContext = CreateDefaultContext();
|
||||
httpContext.Request.Headers["HeaderA"] = "ValueA";
|
||||
|
|
@ -140,7 +140,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
var keyProvider = CreateTestKeyProvider();
|
||||
|
||||
Assert.Equal($"{TestVaryRules.VaryKeyPrefix}{KeyDelimiter}H{KeyDelimiter}HeaderA=ValueA{KeyDelimiter}HeaderC=null{KeyDelimiter}Q{KeyDelimiter}ParamA=ValueA{KeyDelimiter}ParamC=null",
|
||||
keyProvider.CreateVaryKey(httpContext, new VaryRules()
|
||||
keyProvider.CreateStorageVaryKey(httpContext, new VaryRules()
|
||||
{
|
||||
Headers = new string[] { "HeaderA", "HeaderC" },
|
||||
Params = new string[] { "ParamA", "ParamC" }
|
||||
|
|
|
|||
|
|
@ -10,16 +10,100 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Http.Headers;
|
||||
using Microsoft.AspNetCore.ResponseCaching.Internal;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Xunit;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||
{
|
||||
public class ResponseCachingContextTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task TryServeFromCacheAsync_OnlyIfCached_Serves504()
|
||||
{
|
||||
var cache = new TestResponseCache();
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var context = CreateTestContext(httpContext, responseCache: cache, keyProvider: new TestKeyProvider());
|
||||
httpContext.Request.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
||||
{
|
||||
OnlyIfCached = true
|
||||
};
|
||||
|
||||
Assert.True(await context.TryServeFromCacheAsync());
|
||||
Assert.Equal(StatusCodes.Status504GatewayTimeout, httpContext.Response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryServeFromCacheAsync_CachedResponseNotFound_Fails()
|
||||
{
|
||||
var cache = new TestResponseCache();
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var context = CreateTestContext(httpContext, responseCache: cache, keyProvider: new TestKeyProvider(new[] { "BaseKey", "BaseKey2" }));
|
||||
|
||||
Assert.False(await context.TryServeFromCacheAsync());
|
||||
Assert.Equal(2, cache.GetCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryServeFromCacheAsync_CachedResponseFound_Succeeds()
|
||||
{
|
||||
var cache = new TestResponseCache();
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var context = CreateTestContext(httpContext, responseCache: cache, keyProvider: new TestKeyProvider(new[] { "BaseKey", "BaseKey2" }));
|
||||
|
||||
cache.Set(
|
||||
"BaseKey2",
|
||||
new CachedResponse()
|
||||
{
|
||||
Body = new byte[0]
|
||||
},
|
||||
TimeSpan.Zero);
|
||||
|
||||
Assert.True(await context.TryServeFromCacheAsync());
|
||||
Assert.Equal(2, cache.GetCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryServeFromCacheAsync_VaryRuleFound_CachedResponseNotFound_Fails()
|
||||
{
|
||||
var cache = new TestResponseCache();
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var context = CreateTestContext(httpContext, responseCache: cache, keyProvider: new TestKeyProvider(new[] { "BaseKey", "BaseKey2" }));
|
||||
|
||||
cache.Set(
|
||||
"BaseKey2",
|
||||
new CachedVaryRules(),
|
||||
TimeSpan.Zero);
|
||||
|
||||
Assert.False(await context.TryServeFromCacheAsync());
|
||||
Assert.Equal(2, cache.GetCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryServeFromCacheAsync_VaryRuleFound_CachedResponseFound_Succeeds()
|
||||
{
|
||||
var cache = new TestResponseCache();
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var context = CreateTestContext(httpContext, responseCache: cache, keyProvider: new TestKeyProvider(new[] { "BaseKey", "BaseKey2" }, new[] { "VaryKey", "VaryKey2" }));
|
||||
|
||||
cache.Set(
|
||||
"BaseKey2",
|
||||
new CachedVaryRules(),
|
||||
TimeSpan.Zero);
|
||||
cache.Set(
|
||||
"BaseKey2VaryKey2",
|
||||
new CachedResponse()
|
||||
{
|
||||
Body = new byte[0]
|
||||
},
|
||||
TimeSpan.Zero);
|
||||
|
||||
Assert.True(await context.TryServeFromCacheAsync());
|
||||
Assert.Equal(6, cache.GetCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConditionalRequestSatisfied_NotConditionalRequest_Fails()
|
||||
|
|
@ -159,7 +243,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
public void FinalizeCachingHeaders_DoNotUpdateShouldCacheResponse_IfResponseIsNotCacheable()
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var context = CreateTestContext(httpContext);
|
||||
var context = CreateTestContext(httpContext, cacheabilityValidator: new CacheabilityValidator());
|
||||
var state = httpContext.GetResponseCachingState();
|
||||
|
||||
Assert.False(state.ShouldCacheResponse);
|
||||
|
|
@ -178,7 +262,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
{
|
||||
Public = true
|
||||
};
|
||||
var context = CreateTestContext(httpContext);
|
||||
var context = CreateTestContext(httpContext, cacheabilityValidator: new CacheabilityValidator());
|
||||
var state = httpContext.GetResponseCachingState();
|
||||
|
||||
Assert.False(state.ShouldCacheResponse);
|
||||
|
|
@ -192,10 +276,6 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
public void FinalizeCachingHeaders_DefaultResponseValidity_Is10Seconds()
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
||||
{
|
||||
Public = true
|
||||
};
|
||||
var context = CreateTestContext(httpContext);
|
||||
|
||||
context.FinalizeCachingHeaders();
|
||||
|
|
@ -207,10 +287,6 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
public void FinalizeCachingHeaders_ResponseValidity_UseExpiryIfAvailable()
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
||||
{
|
||||
Public = true
|
||||
};
|
||||
var context = CreateTestContext(httpContext);
|
||||
|
||||
var state = httpContext.GetResponseCachingState();
|
||||
|
|
@ -229,7 +305,6 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
||||
{
|
||||
Public = true,
|
||||
MaxAge = TimeSpan.FromSeconds(12)
|
||||
};
|
||||
var context = CreateTestContext(httpContext);
|
||||
|
|
@ -249,7 +324,6 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
||||
{
|
||||
Public = true,
|
||||
MaxAge = TimeSpan.FromSeconds(12),
|
||||
SharedMaxAge = TimeSpan.FromSeconds(13)
|
||||
};
|
||||
|
|
@ -269,16 +343,12 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var cache = new TestResponseCache();
|
||||
var context = CreateTestContext(httpContext, cache, new ResponseCachingOptions());
|
||||
var context = CreateTestContext(httpContext, cache);
|
||||
var state = httpContext.GetResponseCachingState();
|
||||
|
||||
httpContext.Response.Headers[HeaderNames.Vary] = new StringValues(new[] { "headerA", "HEADERB", "HEADERc" });
|
||||
httpContext.AddResponseCachingFeature();
|
||||
httpContext.GetResponseCachingFeature().VaryParams = new StringValues(new[] { "paramB", "PARAMAA" });
|
||||
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
||||
{
|
||||
Public = true,
|
||||
};
|
||||
var cachedVaryRules = new CachedVaryRules()
|
||||
{
|
||||
VaryRules = new VaryRules()
|
||||
|
|
@ -291,7 +361,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
|
||||
context.FinalizeCachingHeaders();
|
||||
|
||||
Assert.Equal(1, cache.StoredItems);
|
||||
Assert.Equal(1, cache.SetCount);
|
||||
Assert.NotSame(cachedVaryRules, state.CachedVaryRules);
|
||||
}
|
||||
|
||||
|
|
@ -300,16 +370,12 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var cache = new TestResponseCache();
|
||||
var context = CreateTestContext(httpContext, cache, new ResponseCachingOptions());
|
||||
var context = CreateTestContext(httpContext, cache);
|
||||
var state = httpContext.GetResponseCachingState();
|
||||
|
||||
httpContext.Response.Headers[HeaderNames.Vary] = new StringValues(new[] { "headerA", "HEADERB" });
|
||||
httpContext.AddResponseCachingFeature();
|
||||
httpContext.GetResponseCachingFeature().VaryParams = new StringValues(new[] { "paramB", "PARAMA" });
|
||||
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
||||
{
|
||||
Public = true,
|
||||
};
|
||||
var cachedVaryRules = new CachedVaryRules()
|
||||
{
|
||||
VaryKeyPrefix = FastGuid.NewGuid().IdString,
|
||||
|
|
@ -323,7 +389,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
|
||||
context.FinalizeCachingHeaders();
|
||||
|
||||
Assert.Equal(0, cache.StoredItems);
|
||||
Assert.Equal(0, cache.SetCount);
|
||||
Assert.Same(cachedVaryRules, state.CachedVaryRules);
|
||||
}
|
||||
|
||||
|
|
@ -331,10 +397,6 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
public void FinalizeCachingHeaders_DoNotAddDate_IfSpecified()
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
||||
{
|
||||
Public = true
|
||||
};
|
||||
var context = CreateTestContext(httpContext);
|
||||
var state = httpContext.GetResponseCachingState();
|
||||
var utcNow = DateTimeOffset.MinValue;
|
||||
|
|
@ -351,10 +413,6 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
public void FinalizeCachingHeaders_AddsDate_IfNoneSpecified()
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
||||
{
|
||||
Public = true
|
||||
};
|
||||
var context = CreateTestContext(httpContext);
|
||||
var state = httpContext.GetResponseCachingState();
|
||||
var utcNow = DateTimeOffset.MinValue;
|
||||
|
|
@ -372,10 +430,6 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
public void FinalizeCachingHeaders_StoresCachedResponse_InState()
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
||||
{
|
||||
Public = true
|
||||
};
|
||||
var context = CreateTestContext(httpContext);
|
||||
var state = httpContext.GetResponseCachingState();
|
||||
|
||||
|
|
@ -391,7 +445,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var cache = new TestResponseCache();
|
||||
var context = CreateTestContext(httpContext, cache, new ResponseCachingOptions());
|
||||
var context = CreateTestContext(httpContext, cache);
|
||||
|
||||
context.ShimResponseStream();
|
||||
await httpContext.Response.WriteAsync(new string('0', 70 * 1024));
|
||||
|
|
@ -402,12 +456,12 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
{
|
||||
BodyKeyPrefix = FastGuid.NewGuid().IdString
|
||||
};
|
||||
state.BaseKey = "BaseKey";
|
||||
state.StorageBaseKey = "BaseKey";
|
||||
state.CachedResponseValidFor = TimeSpan.FromSeconds(10);
|
||||
|
||||
context.FinalizeCachingBody();
|
||||
|
||||
Assert.Equal(2, cache.StoredItems);
|
||||
Assert.Equal(2, cache.SetCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -415,7 +469,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var cache = new TestResponseCache();
|
||||
var context = CreateTestContext(httpContext, cache, new ResponseCachingOptions());
|
||||
var context = CreateTestContext(httpContext, cache);
|
||||
|
||||
context.ShimResponseStream();
|
||||
await httpContext.Response.WriteAsync(new string('0', 70 * 1024 - 1));
|
||||
|
|
@ -426,12 +480,113 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
{
|
||||
BodyKeyPrefix = FastGuid.NewGuid().IdString
|
||||
};
|
||||
state.BaseKey = "BaseKey";
|
||||
state.StorageBaseKey = "BaseKey";
|
||||
state.CachedResponseValidFor = TimeSpan.FromSeconds(10);
|
||||
|
||||
context.FinalizeCachingBody();
|
||||
|
||||
Assert.Equal(1, cache.StoredItems);
|
||||
Assert.Equal(1, cache.SetCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinalizeCachingBody_StoreResponseBodySeparately_LimitIsConfigurable()
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var cache = new TestResponseCache();
|
||||
var context = CreateTestContext(httpContext, cache, new ResponseCachingOptions()
|
||||
{
|
||||
MinimumSplitBodySize = 2048
|
||||
});
|
||||
|
||||
context.ShimResponseStream();
|
||||
await httpContext.Response.WriteAsync(new string('0', 1024));
|
||||
|
||||
var state = httpContext.GetResponseCachingState();
|
||||
state.ShouldCacheResponse = true;
|
||||
state.CachedResponse = new CachedResponse()
|
||||
{
|
||||
BodyKeyPrefix = FastGuid.NewGuid().IdString
|
||||
};
|
||||
state.StorageBaseKey = "BaseKey";
|
||||
state.CachedResponseValidFor = TimeSpan.FromSeconds(10);
|
||||
|
||||
context.FinalizeCachingBody();
|
||||
|
||||
Assert.Equal(1, cache.SetCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinalizeCachingBody_Cache_IfContentLengthMatches()
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var cache = new TestResponseCache();
|
||||
var context = CreateTestContext(httpContext, cache);
|
||||
|
||||
context.ShimResponseStream();
|
||||
httpContext.Response.ContentLength = 10;
|
||||
await httpContext.Response.WriteAsync(new string('0', 10));
|
||||
|
||||
var state = httpContext.GetResponseCachingState();
|
||||
state.ShouldCacheResponse = true;
|
||||
state.CachedResponse = new CachedResponse()
|
||||
{
|
||||
BodyKeyPrefix = FastGuid.NewGuid().IdString
|
||||
};
|
||||
state.StorageBaseKey = "BaseKey";
|
||||
state.CachedResponseValidFor = TimeSpan.FromSeconds(10);
|
||||
|
||||
context.FinalizeCachingBody();
|
||||
|
||||
Assert.Equal(1, cache.SetCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinalizeCachingBody_DoNotCache_IfContentLengthMismatches()
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var cache = new TestResponseCache();
|
||||
var context = CreateTestContext(httpContext, cache);
|
||||
|
||||
context.ShimResponseStream();
|
||||
httpContext.Response.ContentLength = 9;
|
||||
await httpContext.Response.WriteAsync(new string('0', 10));
|
||||
|
||||
var state = httpContext.GetResponseCachingState();
|
||||
state.ShouldCacheResponse = true;
|
||||
state.CachedResponse = new CachedResponse()
|
||||
{
|
||||
BodyKeyPrefix = FastGuid.NewGuid().IdString
|
||||
};
|
||||
state.StorageBaseKey = "BaseKey";
|
||||
state.CachedResponseValidFor = TimeSpan.FromSeconds(10);
|
||||
|
||||
context.FinalizeCachingBody();
|
||||
|
||||
Assert.Equal(0, cache.SetCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FinalizeCachingBody_Cache_IfContentLengthAbsent()
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var cache = new TestResponseCache();
|
||||
var context = CreateTestContext(httpContext, cache);
|
||||
|
||||
context.ShimResponseStream();
|
||||
await httpContext.Response.WriteAsync(new string('0', 10));
|
||||
|
||||
var state = httpContext.GetResponseCachingState();
|
||||
state.ShouldCacheResponse = true;
|
||||
state.CachedResponse = new CachedResponse()
|
||||
{
|
||||
BodyKeyPrefix = FastGuid.NewGuid().IdString
|
||||
};
|
||||
state.StorageBaseKey = "BaseKey";
|
||||
state.CachedResponseValidFor = TimeSpan.FromSeconds(10);
|
||||
|
||||
context.FinalizeCachingBody();
|
||||
|
||||
Assert.Equal(1, cache.SetCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -456,48 +611,30 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
Assert.Equal(orderedStrings, normalizedStrings);
|
||||
}
|
||||
|
||||
private static ResponseCachingContext CreateTestContext(HttpContext httpContext)
|
||||
{
|
||||
return CreateTestContext(
|
||||
httpContext,
|
||||
new TestResponseCache(),
|
||||
new ResponseCachingOptions(),
|
||||
new CacheabilityValidator());
|
||||
}
|
||||
|
||||
private static ResponseCachingContext CreateTestContext(HttpContext httpContext, ResponseCachingOptions options)
|
||||
{
|
||||
return CreateTestContext(
|
||||
httpContext,
|
||||
new TestResponseCache(),
|
||||
options,
|
||||
new CacheabilityValidator());
|
||||
}
|
||||
|
||||
private static ResponseCachingContext CreateTestContext(HttpContext httpContext, ICacheabilityValidator cacheabilityValidator)
|
||||
{
|
||||
return CreateTestContext(
|
||||
httpContext,
|
||||
new TestResponseCache(),
|
||||
new ResponseCachingOptions(),
|
||||
cacheabilityValidator);
|
||||
}
|
||||
|
||||
private static ResponseCachingContext CreateTestContext(HttpContext httpContext, IResponseCache responseCache, ResponseCachingOptions options)
|
||||
{
|
||||
return CreateTestContext(
|
||||
httpContext,
|
||||
responseCache,
|
||||
options,
|
||||
new CacheabilityValidator());
|
||||
}
|
||||
|
||||
private static ResponseCachingContext CreateTestContext(
|
||||
HttpContext httpContext,
|
||||
IResponseCache responseCache,
|
||||
ResponseCachingOptions options,
|
||||
ICacheabilityValidator cacheabilityValidator)
|
||||
IResponseCache responseCache = null,
|
||||
ResponseCachingOptions options = null,
|
||||
IKeyProvider keyProvider = null,
|
||||
ICacheabilityValidator cacheabilityValidator = null)
|
||||
{
|
||||
if (responseCache == null)
|
||||
{
|
||||
responseCache = new TestResponseCache();
|
||||
}
|
||||
if (options == null)
|
||||
{
|
||||
options = new ResponseCachingOptions();
|
||||
}
|
||||
if (keyProvider == null)
|
||||
{
|
||||
keyProvider = new KeyProvider(new DefaultObjectPoolProvider(), Options.Create(options));
|
||||
}
|
||||
if (cacheabilityValidator == null)
|
||||
{
|
||||
cacheabilityValidator = new TestCacheabilityValidator();
|
||||
}
|
||||
|
||||
httpContext.AddResponseCachingState();
|
||||
|
||||
return new ResponseCachingContext(
|
||||
|
|
@ -505,16 +642,82 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
responseCache,
|
||||
options,
|
||||
cacheabilityValidator,
|
||||
new KeyProvider(new DefaultObjectPoolProvider(), Options.Create(options)));
|
||||
keyProvider);
|
||||
}
|
||||
|
||||
private class TestCacheabilityValidator : ICacheabilityValidator
|
||||
{
|
||||
public bool CachedEntryIsFresh(HttpContext httpContext, ResponseHeaders cachedResponseHeaders) => true;
|
||||
|
||||
public bool RequestIsCacheable(HttpContext httpContext) => true;
|
||||
|
||||
public bool ResponseIsCacheable(HttpContext httpContext) => true;
|
||||
}
|
||||
|
||||
private class TestKeyProvider : IKeyProvider
|
||||
{
|
||||
private readonly StringValues _baseKey;
|
||||
private readonly StringValues _varyKey;
|
||||
|
||||
public TestKeyProvider(StringValues? lookupBaseKey = null, StringValues? lookupVaryKey = null)
|
||||
{
|
||||
if (lookupBaseKey.HasValue)
|
||||
{
|
||||
_baseKey = lookupBaseKey.Value;
|
||||
}
|
||||
if (lookupVaryKey.HasValue)
|
||||
{
|
||||
_varyKey = lookupVaryKey.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> CreateLookupBaseKey(HttpContext httpContext) => _baseKey;
|
||||
|
||||
|
||||
public IEnumerable<string> CreateLookupVaryKey(HttpContext httpContext, VaryRules varyRules)
|
||||
{
|
||||
foreach (var baseKey in _baseKey)
|
||||
{
|
||||
foreach (var varyKey in _varyKey)
|
||||
{
|
||||
yield return baseKey + varyKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string CreateBodyKey(HttpContext httpContext)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string CreateStorageBaseKey(HttpContext httpContext)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string CreateStorageVaryKey(HttpContext httpContext, VaryRules varyRules)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private class TestResponseCache : IResponseCache
|
||||
{
|
||||
public int StoredItems { get; private set; }
|
||||
private readonly IDictionary<string, object> _storage = new Dictionary<string, object>();
|
||||
public int GetCount { get; private set; }
|
||||
public int SetCount { get; private set; }
|
||||
|
||||
public object Get(string key)
|
||||
{
|
||||
return null;
|
||||
GetCount++;
|
||||
try
|
||||
{
|
||||
return _storage[key];
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(string key)
|
||||
|
|
@ -523,7 +726,8 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
|
||||
public void Set(string key, object entry, TimeSpan validFor)
|
||||
{
|
||||
StoredItems++;
|
||||
SetCount++;
|
||||
_storage[key] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -531,7 +735,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
{
|
||||
public Task SendFileAsync(string path, long offset, long? count, CancellationToken cancellation)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue