Updated the ResponseCacheFilter class to store a reference to the CacheProfile passed into the contructor
Updated the ResponseCacheFilter Duration, Location, NoStore, and VaryByHeader properties to return one of 3 values: the specific setting applied to the instance, the setting supplied by the CachePolicy (from the constructor), or a default value. The emphasis being not to change the outward function of these properties to consumers, but to defer the evaluation of these properties until the OnActionExecuting method. Updated the ResponseCacheFilter to check whether a cache duration has been supplied when the NoStore property is false and throw an InvalidOperationException when a duration has not been provided. Updated ResponseCacheFilterTest to reflect the changes in behavior in the ResponseCacheFilter Updated the ResponseCacheFilterAttributeTest to reflect the change in the behavior of the ResponseCacheFilter Added unit tests to ResponseCacheFilterTest.cs to verify that the CachePolicy properties are properly overriden with the properties set directly on the ResponseCacheFilter. Added functional tests to test the ability to override CacheProfile settings with properties on the ResponseCacheAttribute
This commit is contained in:
parent
4f419eee55
commit
70b56f157c
|
|
@ -14,6 +14,12 @@ namespace Microsoft.AspNet.Mvc
|
|||
/// </summary>
|
||||
public class ResponseCacheFilter : IActionFilter, IResponseCacheFilter
|
||||
{
|
||||
private readonly CacheProfile _cacheProfile;
|
||||
private int? _cacheDuration;
|
||||
private ResponseCacheLocation? _cacheLocation;
|
||||
private bool? _cacheNoStore;
|
||||
private string _cacheVaryByHeader;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="ResponseCacheFilter"/>
|
||||
/// </summary>
|
||||
|
|
@ -21,20 +27,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
/// <see cref="ResponseCacheFilter"/>.</param>
|
||||
public ResponseCacheFilter(CacheProfile cacheProfile)
|
||||
{
|
||||
if (!(cacheProfile.NoStore ?? false))
|
||||
{
|
||||
// Duration MUST be set (either in the cache profile or in the attribute) unless NoStore is true.
|
||||
if (cacheProfile.Duration == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatResponseCache_SpecifyDuration(nameof(NoStore), nameof(Duration)));
|
||||
}
|
||||
}
|
||||
|
||||
Duration = cacheProfile.Duration ?? 0;
|
||||
Location = cacheProfile.Location ?? ResponseCacheLocation.Any;
|
||||
NoStore = cacheProfile.NoStore ?? false;
|
||||
VaryByHeader = cacheProfile.VaryByHeader;
|
||||
_cacheProfile = cacheProfile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -42,12 +35,20 @@ namespace Microsoft.AspNet.Mvc
|
|||
/// This is a required parameter.
|
||||
/// This sets "max-age" in "Cache-control" header.
|
||||
/// </summary>
|
||||
public int Duration { get; set; }
|
||||
public int Duration
|
||||
{
|
||||
get { return (_cacheDuration ?? _cacheProfile.Duration) ?? 0; }
|
||||
set { _cacheDuration = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the location where the data from a particular URL must be cached.
|
||||
/// </summary>
|
||||
public ResponseCacheLocation Location { get; set; }
|
||||
public ResponseCacheLocation Location
|
||||
{
|
||||
get { return (_cacheLocation ?? _cacheProfile.Location) ?? ResponseCacheLocation.Any; }
|
||||
set { _cacheLocation = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value which determines whether the data should be stored or not.
|
||||
|
|
@ -55,13 +56,21 @@ namespace Microsoft.AspNet.Mvc
|
|||
/// Ignores the "Location" parameter for values other than "None".
|
||||
/// Ignores the "duration" parameter.
|
||||
/// </summary>
|
||||
public bool NoStore { get; set; }
|
||||
public bool NoStore
|
||||
{
|
||||
get { return (_cacheNoStore ?? _cacheProfile.NoStore) ?? false; }
|
||||
set { _cacheNoStore = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value for the Vary response header.
|
||||
/// </summary>
|
||||
public string VaryByHeader { get; set; }
|
||||
|
||||
public string VaryByHeader
|
||||
{
|
||||
get { return _cacheVaryByHeader ?? _cacheProfile.VaryByHeader; }
|
||||
set { _cacheVaryByHeader = value; }
|
||||
}
|
||||
|
||||
// <inheritdoc />
|
||||
public void OnActionExecuting([NotNull] ActionExecutingContext context)
|
||||
{
|
||||
|
|
@ -72,6 +81,16 @@ namespace Microsoft.AspNet.Mvc
|
|||
return;
|
||||
}
|
||||
|
||||
if (!NoStore)
|
||||
{
|
||||
// Duration MUST be set (either in the cache profile or in this filter) unless NoStore is true.
|
||||
if (_cacheProfile.Duration == null && _cacheDuration == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatResponseCache_SpecifyDuration(nameof(NoStore), nameof(Duration)));
|
||||
}
|
||||
}
|
||||
|
||||
var headers = context.HttpContext.Response.Headers;
|
||||
|
||||
// Clear all headers
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateInstance_ThrowsWhenTheDurationIsNotSet_WithNoStoreFalse()
|
||||
public void CreateInstance_DoesNotThrowWhenTheDurationIsNotSet_WithNoStoreFalse()
|
||||
{
|
||||
// Arrange
|
||||
var responseCache = new ResponseCacheAttribute()
|
||||
|
|
@ -172,12 +172,11 @@ namespace Microsoft.AspNet.Mvc
|
|||
var cacheProfiles = new Dictionary<string, CacheProfile>();
|
||||
cacheProfiles.Add("Test", new CacheProfile { NoStore = false });
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(
|
||||
() => responseCache.CreateInstance(GetServiceProvider(cacheProfiles)));
|
||||
Assert.Equal(
|
||||
"If the 'NoStore' property is not set to true, 'Duration' property must be specified.",
|
||||
ex.Message);
|
||||
// Act
|
||||
var filter = responseCache.CreateInstance(GetServiceProvider(cacheProfiles));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(filter);
|
||||
}
|
||||
|
||||
private IServiceProvider GetServiceProvider(Dictionary<string, CacheProfile> cacheProfiles)
|
||||
|
|
|
|||
|
|
@ -31,17 +31,34 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void ResponseCacheFilter_ThrowsIfDurationIsNotSet_WhenNoStoreIsFalse()
|
||||
public void ResponseCacheFilter_DoesNotThrowIfDurationIsNotSet_WhenNoStoreIsFalse()
|
||||
{
|
||||
// Arrange, Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(
|
||||
() => new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = null
|
||||
}));
|
||||
Assert.Equal(
|
||||
"If the 'NoStore' property is not set to true, 'Duration' property must be specified.",
|
||||
// Arrange, Act
|
||||
var cache = new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = null
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(cache);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnActionExecuting_ThrowsIfDurationIsNotSet_WhenNoStoreIsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var cache = new ResponseCacheFilter(
|
||||
new CacheProfile()
|
||||
{
|
||||
Duration = null
|
||||
});
|
||||
|
||||
var context = GetActionExecutingContext(new List<IFilter> { cache });
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => cache.OnActionExecuting(context));
|
||||
Assert.Equal("If the 'NoStore' property is not set to true, 'Duration' property must be specified.",
|
||||
ex.Message);
|
||||
}
|
||||
|
||||
|
|
@ -306,6 +323,85 @@ namespace Microsoft.AspNet.Mvc
|
|||
Assert.False(cache.IsOverridden(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FilterDurationProperty_OverridesCachePolicySetting()
|
||||
{
|
||||
// Arrange
|
||||
var cache = new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 10
|
||||
});
|
||||
cache.Duration = 20;
|
||||
var context = GetActionExecutingContext(new List<IFilter> { cache });
|
||||
|
||||
// Act
|
||||
cache.OnActionExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("public,max-age=20", context.HttpContext.Response.Headers.Get("Cache-control"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FilterLocationProperty_OverridesCachePolicySetting()
|
||||
{
|
||||
// Arrange
|
||||
var cache = new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 10,
|
||||
Location = ResponseCacheLocation.None
|
||||
});
|
||||
cache.Location = ResponseCacheLocation.Client;
|
||||
var context = GetActionExecutingContext(new List<IFilter> { cache });
|
||||
|
||||
// Act
|
||||
cache.OnActionExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("private,max-age=10", context.HttpContext.Response.Headers.Get("Cache-control"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FilterNoStoreProperty_OverridesCachePolicySetting()
|
||||
{
|
||||
// Arrange
|
||||
var cache = new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
NoStore = true
|
||||
});
|
||||
cache.NoStore = false;
|
||||
cache.Duration = 10;
|
||||
var context = GetActionExecutingContext(new List<IFilter> { cache });
|
||||
|
||||
// Act
|
||||
cache.OnActionExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("public,max-age=10", context.HttpContext.Response.Headers.Get("Cache-control"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FilterVaryByProperty_OverridesCachePolicySetting()
|
||||
{
|
||||
// Arrange
|
||||
var cache = new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
NoStore = true,
|
||||
VaryByHeader = "Accept"
|
||||
});
|
||||
cache.VaryByHeader = "Test";
|
||||
var context = GetActionExecutingContext(new List<IFilter> { cache });
|
||||
|
||||
// Act
|
||||
cache.OnActionExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Test", context.HttpContext.Response.Headers.Get("Vary"));
|
||||
}
|
||||
|
||||
private ActionExecutingContext GetActionExecutingContext(List<IFilter> filters = null)
|
||||
{
|
||||
return new ActionExecutingContext(
|
||||
|
|
|
|||
|
|
@ -280,5 +280,84 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
response.Headers.TryGetValues("Pragma", out pragmaValues);
|
||||
Assert.Null(pragmaValues);
|
||||
}
|
||||
|
||||
// Cache profile overrides
|
||||
[Fact]
|
||||
public async Task ResponseCacheAttribute_OverridesProfileDuration_FromAttributeProperty()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("http://localhost/CacheProfileOverrides/PublicCache30SecTo15Sec");
|
||||
|
||||
// Assert
|
||||
var data = Assert.Single(response.Headers.GetValues("Cache-control"));
|
||||
Assert.Equal("public, max-age=15", data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResponseCacheAttribute_OverridesProfileLocation_FromAttributeProperty()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("http://localhost/CacheProfileOverrides/PublicCache30SecToPrivateCache");
|
||||
|
||||
// Assert
|
||||
var data = Assert.Single(response.Headers.GetValues("Cache-control"));
|
||||
Assert.Equal("max-age=30, private", data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResponseCacheAttribute_OverridesProfileNoStore_FromAttributeProperty()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("http://localhost/CacheProfileOverrides/PublicCache30SecToNoStore");
|
||||
|
||||
// Assert
|
||||
var data = Assert.Single(response.Headers.GetValues("Cache-control"));
|
||||
Assert.Equal("no-store", data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResponseCacheAttribute_OverridesProfileVaryBy_FromAttributeProperty()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("http://localhost/CacheProfileOverrides/PublicCache30SecWithVaryByAcceptToVaryByTest");
|
||||
|
||||
// Assert
|
||||
var cacheControl = Assert.Single(response.Headers.GetValues("Cache-control"));
|
||||
Assert.Equal("public, max-age=30", cacheControl);
|
||||
var vary = Assert.Single(response.Headers.GetValues("Vary"));
|
||||
Assert.Equal("Test", vary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResponseCacheAttribute_OverridesProfileVaryBy_FromAttributeProperty_AndRemovesVaryHeader()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("http://localhost/CacheProfileOverrides/PublicCache30SecWithVaryByAcceptToVaryByNone");
|
||||
|
||||
// Assert
|
||||
var cacheControl = Assert.Single(response.Headers.GetValues("Cache-control"));
|
||||
Assert.Equal("public, max-age=30", cacheControl);
|
||||
Assert.Throws<InvalidOperationException>(() => response.Headers.GetValues("Vary"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
|
||||
namespace ResponseCacheWebSite.Controllers
|
||||
{
|
||||
public class CacheProfilesOverridesController
|
||||
{
|
||||
[HttpGet("/CacheProfileOverrides/PublicCache30SecTo15Sec")]
|
||||
[ResponseCache(CacheProfileName = "PublicCache30Sec", Duration = 15)]
|
||||
public string PublicCache30SecTo15Sec()
|
||||
{
|
||||
return "Hello World!";
|
||||
}
|
||||
|
||||
[HttpGet("/CacheProfileOverrides/PublicCache30SecToPrivateCache")]
|
||||
[ResponseCache(CacheProfileName = "PublicCache30Sec", Location = ResponseCacheLocation.Client)]
|
||||
public string PublicCache30SecToPrivateCache()
|
||||
{
|
||||
return "Hello World!";
|
||||
}
|
||||
|
||||
[HttpGet("/CacheProfileOverrides/PublicCache30SecToNoStore")]
|
||||
[ResponseCache(CacheProfileName = "PublicCache30Sec", NoStore = true)]
|
||||
public string PublicCache30SecToNoStore()
|
||||
{
|
||||
return "Hello World!";
|
||||
}
|
||||
|
||||
[HttpGet("/CacheProfileOverrides/PublicCache30SecWithVaryByAcceptToVaryByTest")]
|
||||
[ResponseCache(CacheProfileName = "PublicCache30Sec", VaryByHeader = "Test")]
|
||||
public string PublicCache30SecWithVaryByAcceptToVaryByTest()
|
||||
{
|
||||
return "Hello World!";
|
||||
}
|
||||
|
||||
[HttpGet("/CacheProfileOverrides/PublicCache30SecWithVaryByAcceptToVaryByNone")]
|
||||
[ResponseCache(CacheProfileName = "PublicCache30Sec", VaryByHeader = null)]
|
||||
public string PublicCache30SecWithVaryByAcceptToVaryByNone()
|
||||
{
|
||||
return "Hello World!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,14 @@ namespace ResponseCacheWebSite
|
|||
Location = ResponseCacheLocation.None
|
||||
});
|
||||
|
||||
options.CacheProfiles.Add(
|
||||
"PublicCache30SecVaryByAcceptHeader", new CacheProfile
|
||||
{
|
||||
Duration = 30,
|
||||
Location = ResponseCacheLocation.Any,
|
||||
VaryByHeader = "Accept"
|
||||
});
|
||||
|
||||
options.Filters.Add(new ResponseCacheFilter(new CacheProfile
|
||||
{
|
||||
NoStore = true,
|
||||
|
|
|
|||
Loading…
Reference in New Issue