Add support for VaryByQueryKey #2894
This commit is contained in:
parent
3004fb822b
commit
d8c6c4ab34
|
|
@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
/// <summary>
|
||||
/// Gets or sets the duration in seconds for which the response is cached.
|
||||
/// If this property is set to a non null value,
|
||||
/// the "max-age" in "Cache-control" header is set in the
|
||||
/// the "max-age" in "Cache-control" header is set in the
|
||||
/// <see cref="Microsoft.AspNetCore.Http.HttpContext.Response" />.
|
||||
/// </summary>
|
||||
public int? Duration { get; set; }
|
||||
|
|
@ -36,5 +36,13 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
/// Gets or sets the value for the Vary header in <see cref="Microsoft.AspNetCore.Http.HttpContext.Response" />.
|
||||
/// </summary>
|
||||
public string VaryByHeader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the query keys to vary by.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="VaryByQueryKeys"/> requires the response cache middleware.
|
||||
/// </remarks>
|
||||
public string[] VaryByQueryKeys { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.ResponseCaching;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||
|
|
@ -21,6 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
private ResponseCacheLocation? _cacheLocation;
|
||||
private bool? _cacheNoStore;
|
||||
private string _cacheVaryByHeader;
|
||||
private string[] _cacheVaryByQueryKeys;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="ResponseCacheFilter"/>
|
||||
|
|
@ -73,6 +75,18 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
set { _cacheVaryByHeader = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the query keys to vary by.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="VaryByQueryKeys"/> requires the response cache middleware.
|
||||
/// </remarks>
|
||||
public string[] VaryByQueryKeys
|
||||
{
|
||||
get { return _cacheVaryByQueryKeys ?? _cacheProfile.VaryByQueryKeys; }
|
||||
set { _cacheVaryByQueryKeys = value; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
|
|
@ -110,6 +124,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
headers[HeaderNames.Vary] = VaryByHeader;
|
||||
}
|
||||
|
||||
if (VaryByQueryKeys != null)
|
||||
{
|
||||
var responseCachingFeature = context.HttpContext.Features.Get<IResponseCachingFeature>();
|
||||
if (responseCachingFeature == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatVaryByQueryKeys_Requires_ResponseCachingMiddleware(nameof(VaryByQueryKeys)));
|
||||
}
|
||||
responseCachingFeature.VaryByQueryKeys = VaryByQueryKeys;
|
||||
}
|
||||
|
||||
if (NoStore)
|
||||
{
|
||||
headers[HeaderNames.CacheControl] = "no-store";
|
||||
|
|
|
|||
|
|
@ -1338,6 +1338,22 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("FormCollectionModelBinder_CannotBindToFormCollection"), p0, p1, p2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// VaryByQueryKeys requires the response cache middleware.
|
||||
/// </summary>
|
||||
internal static string VaryByQueryKeys_Requires_ResponseCachingMiddleware
|
||||
{
|
||||
get { return GetString("VaryByQueryKeys_Requires_ResponseCachingMiddleware"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// VaryByQueryKeys requires the response cache middleware.
|
||||
/// </summary>
|
||||
internal static string FormatVaryByQueryKeys_Requires_ResponseCachingMiddleware(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("VaryByQueryKeys_Requires_ResponseCachingMiddleware"), p0);
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -376,4 +376,7 @@
|
|||
<data name="FormCollectionModelBinder_CannotBindToFormCollection" xml:space="preserve">
|
||||
<value>The '{0}' cannot bind to a model of type '{1}'. Change the model type to '{2}' instead.</value>
|
||||
</data>
|
||||
<data name="VaryByQueryKeys_Requires_ResponseCachingMiddleware" xml:space="preserve">
|
||||
<value>'{0}' requires the response cache middleware.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -77,6 +77,14 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
/// </summary>
|
||||
public string VaryByHeader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the query keys to vary by.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="VaryByQueryKeys"/> requires the response cache middleware.
|
||||
/// </remarks>
|
||||
public string[] VaryByQueryKeys { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value of the cache profile name.
|
||||
/// </summary>
|
||||
|
|
@ -117,6 +125,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
_noStore = _noStore ?? selectedProfile?.NoStore;
|
||||
_location = _location ?? selectedProfile?.Location;
|
||||
VaryByHeader = VaryByHeader ?? selectedProfile?.VaryByHeader;
|
||||
VaryByQueryKeys = VaryByQueryKeys ?? selectedProfile?.VaryByQueryKeys;
|
||||
|
||||
// ResponseCacheFilter cannot take any null values. Hence, if there are any null values,
|
||||
// the properties convert them to their defaults and are passed on.
|
||||
|
|
@ -125,7 +134,8 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
Duration = _duration,
|
||||
Location = _location,
|
||||
NoStore = _noStore,
|
||||
VaryByHeader = VaryByHeader
|
||||
VaryByHeader = VaryByHeader,
|
||||
VaryByQueryKeys = VaryByQueryKeys,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
"Microsoft.AspNetCore.Hosting.Abstractions": "1.1.0-*",
|
||||
"Microsoft.AspNetCore.Http": "1.1.0-*",
|
||||
"Microsoft.AspNetCore.Mvc.Abstractions": "1.1.0-*",
|
||||
"Microsoft.AspNetCore.ResponseCaching.Abstractions": "1.0.0-*",
|
||||
"Microsoft.AspNetCore.Routing": "1.1.0-*",
|
||||
"Microsoft.AspNetCore.Routing.DecisionTree.Sources": {
|
||||
"type": "build",
|
||||
|
|
|
|||
|
|
@ -3,10 +3,13 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.ResponseCaching;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||
|
|
@ -64,86 +67,113 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ex.Message);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> CacheControlData
|
||||
public static TheoryData<ResponseCacheFilter, string> CacheControlData
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new object[] {
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 0, Location = ResponseCacheLocation.Any, NoStore = true, VaryByHeader = null
|
||||
}),
|
||||
"no-store"
|
||||
};
|
||||
// If no-store is set, then location is ignored.
|
||||
yield return new object[] {
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 0, Location = ResponseCacheLocation.Client, NoStore = true, VaryByHeader = null
|
||||
}),
|
||||
"no-store"
|
||||
};
|
||||
yield return new object[] {
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 0, Location = ResponseCacheLocation.Any, NoStore = true, VaryByHeader = null
|
||||
}),
|
||||
"no-store"
|
||||
};
|
||||
// If no-store is set, then duration is ignored.
|
||||
yield return new object[] {
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 100, Location = ResponseCacheLocation.Any, NoStore = true, VaryByHeader = null
|
||||
}),
|
||||
"no-store"
|
||||
};
|
||||
|
||||
yield return new object[] {
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 10, Location = ResponseCacheLocation.Client,
|
||||
NoStore = false, VaryByHeader = null
|
||||
}),
|
||||
"private,max-age=10"
|
||||
};
|
||||
yield return new object[] {
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 10, Location = ResponseCacheLocation.Any, NoStore = false, VaryByHeader = null
|
||||
}),
|
||||
"public,max-age=10"
|
||||
};
|
||||
yield return new object[] {
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 10, Location = ResponseCacheLocation.None, NoStore = false, VaryByHeader = null
|
||||
}),
|
||||
"no-cache,max-age=10"
|
||||
};
|
||||
yield return new object[] {
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 31536000, Location = ResponseCacheLocation.Any,
|
||||
NoStore = false, VaryByHeader = null
|
||||
}),
|
||||
"public,max-age=31536000"
|
||||
};
|
||||
yield return new object[] {
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 20, Location = ResponseCacheLocation.Any, NoStore = false, VaryByHeader = null
|
||||
}),
|
||||
"public,max-age=20"
|
||||
return new TheoryData<ResponseCacheFilter, string>
|
||||
{
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 0,
|
||||
Location = ResponseCacheLocation.Any,
|
||||
NoStore = true,
|
||||
VaryByHeader = null
|
||||
}),
|
||||
"no-store"
|
||||
},
|
||||
// If no-store is set, then location is ignored.
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 0,
|
||||
Location = ResponseCacheLocation.Client,
|
||||
NoStore = true,
|
||||
VaryByHeader = null
|
||||
}),
|
||||
"no-store"
|
||||
},
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 0,
|
||||
Location = ResponseCacheLocation.Any,
|
||||
NoStore = true,
|
||||
VaryByHeader = null
|
||||
}),
|
||||
"no-store"
|
||||
},
|
||||
// If no-store is set, then duration is ignored.
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 100,
|
||||
Location = ResponseCacheLocation.Any,
|
||||
NoStore = true,
|
||||
VaryByHeader = null
|
||||
}),
|
||||
"no-store"
|
||||
},
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 10,
|
||||
Location = ResponseCacheLocation.Client,
|
||||
NoStore = false,
|
||||
VaryByHeader = null
|
||||
}),
|
||||
"private,max-age=10"
|
||||
},
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 10,
|
||||
Location = ResponseCacheLocation.Any,
|
||||
NoStore = false,
|
||||
VaryByHeader = null
|
||||
}),
|
||||
"public,max-age=10"
|
||||
},
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 10,
|
||||
Location = ResponseCacheLocation.None,
|
||||
NoStore = false,
|
||||
VaryByHeader = null
|
||||
}),
|
||||
"no-cache,max-age=10"
|
||||
},
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 31536000,
|
||||
Location = ResponseCacheLocation.Any,
|
||||
NoStore = false,
|
||||
VaryByHeader = null
|
||||
}),
|
||||
"public,max-age=31536000"
|
||||
},
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 20,
|
||||
Location = ResponseCacheLocation.Any,
|
||||
NoStore = false,
|
||||
VaryByHeader = null
|
||||
}),
|
||||
"public,max-age=20"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -162,35 +192,47 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Assert.Equal(output, context.HttpContext.Response.Headers["Cache-control"]);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> NoStoreData
|
||||
public static TheoryData<ResponseCacheFilter, string> NoStoreData
|
||||
{
|
||||
get
|
||||
{
|
||||
// If no-store is set, then location is ignored.
|
||||
yield return new object[] {
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 0, Location = ResponseCacheLocation.Client, NoStore = true, VaryByHeader = null
|
||||
}),
|
||||
"no-store"
|
||||
};
|
||||
yield return new object[] {
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 0, Location = ResponseCacheLocation.Any, NoStore = true, VaryByHeader = null
|
||||
}),
|
||||
"no-store"
|
||||
};
|
||||
// If no-store is set, then duration is ignored.
|
||||
yield return new object[] {
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 100, Location = ResponseCacheLocation.Any, NoStore = true, VaryByHeader = null
|
||||
}),
|
||||
"no-store"
|
||||
return new TheoryData<ResponseCacheFilter, string>
|
||||
{
|
||||
// If no-store is set, then location is ignored.
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 0,
|
||||
Location = ResponseCacheLocation.Client,
|
||||
NoStore = true,
|
||||
VaryByHeader = null
|
||||
}),
|
||||
"no-store"
|
||||
},
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 0,
|
||||
Location = ResponseCacheLocation.Any,
|
||||
NoStore = true,
|
||||
VaryByHeader = null
|
||||
}),
|
||||
"no-store"
|
||||
},
|
||||
// If no-store is set, then duration is ignored.
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 100,
|
||||
Location = ResponseCacheLocation.Any,
|
||||
NoStore = true,
|
||||
VaryByHeader = null
|
||||
}),
|
||||
"no-store"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -210,65 +252,79 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Assert.Equal(output, context.HttpContext.Response.Headers["Cache-control"]);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> VaryData
|
||||
public static TheoryData<ResponseCacheFilter, string, string> VaryByHeaderData
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new object[] {
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 10, Location = ResponseCacheLocation.Any,
|
||||
NoStore = false, VaryByHeader = "Accept"
|
||||
}),
|
||||
"Accept",
|
||||
"public,max-age=10" };
|
||||
yield return new object[] {
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 0, Location= ResponseCacheLocation.Any,
|
||||
NoStore = true, VaryByHeader = "Accept"
|
||||
}),
|
||||
"Accept",
|
||||
"no-store"
|
||||
};
|
||||
yield return new object[] {
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 10, Location = ResponseCacheLocation.Client,
|
||||
NoStore = false, VaryByHeader = "Accept"
|
||||
}),
|
||||
"Accept",
|
||||
"private,max-age=10"
|
||||
};
|
||||
yield return new object[] {
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 10, Location = ResponseCacheLocation.Client,
|
||||
NoStore = false, VaryByHeader = "Test"
|
||||
}),
|
||||
"Test",
|
||||
"private,max-age=10"
|
||||
};
|
||||
yield return new object[] {
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 31536000, Location = ResponseCacheLocation.Any,
|
||||
NoStore = false, VaryByHeader = "Test"
|
||||
}),
|
||||
"Test",
|
||||
"public,max-age=31536000"
|
||||
return new TheoryData<ResponseCacheFilter, string, string>
|
||||
{
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 10,
|
||||
Location = ResponseCacheLocation.Any,
|
||||
NoStore = false,
|
||||
VaryByHeader = "Accept"
|
||||
}),
|
||||
"Accept",
|
||||
"public,max-age=10"
|
||||
},
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 0,
|
||||
Location = ResponseCacheLocation.Any,
|
||||
NoStore = true,
|
||||
VaryByHeader = "Accept"
|
||||
}),
|
||||
"Accept",
|
||||
"no-store"
|
||||
},
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 10,
|
||||
Location = ResponseCacheLocation.Client,
|
||||
NoStore = false,
|
||||
VaryByHeader = "Accept"
|
||||
}),
|
||||
"Accept",
|
||||
"private,max-age=10"
|
||||
},
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 10,
|
||||
Location = ResponseCacheLocation.Client,
|
||||
NoStore = false,
|
||||
VaryByHeader = "Test"
|
||||
}),
|
||||
"Test",
|
||||
"private,max-age=10"
|
||||
},
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 31536000,
|
||||
Location = ResponseCacheLocation.Any,
|
||||
NoStore = false,
|
||||
VaryByHeader = "Test"
|
||||
}),
|
||||
"Test",
|
||||
"public,max-age=31536000"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(VaryData))]
|
||||
public void ResponseCacheCanSetVary(ResponseCacheFilter cache, string varyOutput, string cacheControlOutput)
|
||||
[MemberData(nameof(VaryByHeaderData))]
|
||||
public void ResponseCacheCanSetVaryByHeader(ResponseCacheFilter cache, string varyOutput, string cacheControlOutput)
|
||||
{
|
||||
// Arrange
|
||||
var context = GetActionExecutingContext(new List<IFilterMetadata> { cache });
|
||||
|
|
@ -281,6 +337,112 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Assert.Equal(cacheControlOutput, context.HttpContext.Response.Headers["Cache-control"]);
|
||||
}
|
||||
|
||||
public static TheoryData<ResponseCacheFilter, string[], string> VaryByQueryKeyData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<ResponseCacheFilter, string[], string>
|
||||
{
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 10,
|
||||
Location = ResponseCacheLocation.Any,
|
||||
NoStore = false,
|
||||
VaryByQueryKeys = new[] { "Accept" }
|
||||
}),
|
||||
new[] { "Accept" },
|
||||
"public,max-age=10"
|
||||
},
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 0,
|
||||
Location = ResponseCacheLocation.Any,
|
||||
NoStore = true,
|
||||
VaryByQueryKeys = new[] { "Accept" }
|
||||
}),
|
||||
new[] { "Accept" },
|
||||
"no-store"
|
||||
},
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 10,
|
||||
Location = ResponseCacheLocation.Client,
|
||||
NoStore = false,
|
||||
VaryByQueryKeys = new[] { "Accept" }
|
||||
}),
|
||||
new[] { "Accept" },
|
||||
"private,max-age=10"
|
||||
},
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 10,
|
||||
Location = ResponseCacheLocation.Client,
|
||||
NoStore = false,
|
||||
VaryByQueryKeys = new[] { "Accept", "Test" }
|
||||
}),
|
||||
new[] { "Accept", "Test" },
|
||||
"private,max-age=10"
|
||||
},
|
||||
{
|
||||
new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 31536000,
|
||||
Location = ResponseCacheLocation.Any,
|
||||
NoStore = false,
|
||||
VaryByQueryKeys = new[] { "Accept", "Test" }
|
||||
}),
|
||||
new[] { "Accept", "Test" },
|
||||
"public,max-age=31536000"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(VaryByQueryKeyData))]
|
||||
public void ResponseCacheCanSetVaryByQueryKeys(ResponseCacheFilter cache, string[] varyOutput, string cacheControlOutput)
|
||||
{
|
||||
// Arrange
|
||||
var context = GetActionExecutingContext(new List<IFilterMetadata> { cache });
|
||||
context.HttpContext.Features.Set<IResponseCachingFeature>(new ResponseCachingFeature());
|
||||
|
||||
// Act
|
||||
cache.OnActionExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(varyOutput, context.HttpContext.Features.Get<IResponseCachingFeature>().VaryByQueryKeys);
|
||||
Assert.Equal(cacheControlOutput, context.HttpContext.Response.Headers[HeaderNames.CacheControl]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NonEmptyVaryByQueryKeys_WithoutConfiguringMiddleware_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var cache = new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 0,
|
||||
Location = ResponseCacheLocation.None,
|
||||
NoStore = true,
|
||||
VaryByHeader = null,
|
||||
VaryByQueryKeys = new[] { "Test" }
|
||||
});
|
||||
var context = GetActionExecutingContext(new List<IFilterMetadata> { cache });
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => cache.OnActionExecuting(context));
|
||||
Assert.Equal("'VaryByQueryKeys' requires the response cache middleware.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetsPragmaOnNoCache()
|
||||
{
|
||||
|
|
@ -288,7 +450,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var cache = new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 0, Location = ResponseCacheLocation.None, NoStore = true, VaryByHeader = null
|
||||
Duration = 0,
|
||||
Location = ResponseCacheLocation.None,
|
||||
NoStore = true,
|
||||
VaryByHeader = null
|
||||
});
|
||||
var context = GetActionExecutingContext(new List<IFilterMetadata> { cache });
|
||||
|
||||
|
|
@ -308,12 +473,18 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
caches.Add(new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 0, Location = ResponseCacheLocation.Any, NoStore = false, VaryByHeader = null
|
||||
Duration = 0,
|
||||
Location = ResponseCacheLocation.Any,
|
||||
NoStore = false,
|
||||
VaryByHeader = null
|
||||
}));
|
||||
caches.Add(new ResponseCacheFilter(
|
||||
new CacheProfile
|
||||
{
|
||||
Duration = 0, Location = ResponseCacheLocation.Any, NoStore = false, VaryByHeader = null
|
||||
Duration = 0,
|
||||
Location = ResponseCacheLocation.Any,
|
||||
NoStore = false,
|
||||
VaryByHeader = null
|
||||
}));
|
||||
|
||||
var context = GetActionExecutingContext(caches);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.ResponseCaching;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
|
@ -66,18 +67,18 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
// When there are no cache profiles then the passed in data is returned unchanged
|
||||
yield return new object[] {
|
||||
new ResponseCacheAttribute()
|
||||
{ Duration = 20, Location = ResponseCacheLocation.Any, NoStore = false, VaryByHeader = "Accept" },
|
||||
{ Duration = 20, Location = ResponseCacheLocation.Any, NoStore = false, VaryByHeader = "Accept", VaryByQueryKeys = new[] { "QueryKey" } },
|
||||
null,
|
||||
new CacheProfile
|
||||
{ Duration = 20, Location = ResponseCacheLocation.Any, NoStore = false, VaryByHeader = "Accept" }
|
||||
{ Duration = 20, Location = ResponseCacheLocation.Any, NoStore = false, VaryByHeader = "Accept", VaryByQueryKeys = new[] { "QueryKey" } }
|
||||
};
|
||||
|
||||
yield return new object[] {
|
||||
new ResponseCacheAttribute()
|
||||
{ Duration = 0, Location = ResponseCacheLocation.None, NoStore = true, VaryByHeader = null },
|
||||
{ Duration = 0, Location = ResponseCacheLocation.None, NoStore = true, VaryByHeader = null, VaryByQueryKeys = null },
|
||||
null,
|
||||
new CacheProfile
|
||||
{ Duration = 0, Location = ResponseCacheLocation.None, NoStore = true, VaryByHeader = null }
|
||||
{ Duration = 0, Location = ResponseCacheLocation.None, NoStore = true, VaryByHeader = null, VaryByQueryKeys = null }
|
||||
};
|
||||
|
||||
// Everything gets overriden if attribute parameters are present,
|
||||
|
|
@ -89,6 +90,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
Location = ResponseCacheLocation.Any,
|
||||
NoStore = false,
|
||||
VaryByHeader = "Accept",
|
||||
VaryByQueryKeys = new[] { "QueryKey" },
|
||||
CacheProfileName = "TestCacheProfile"
|
||||
},
|
||||
new Dictionary<string, CacheProfile> { { "TestCacheProfile", new CacheProfile
|
||||
|
|
@ -96,10 +98,11 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
Duration = 10,
|
||||
Location = ResponseCacheLocation.Client,
|
||||
NoStore = true,
|
||||
VaryByHeader = "Test"
|
||||
VaryByHeader = "Test",
|
||||
VaryByQueryKeys = new[] { "ProfileQueryKey" }
|
||||
} } },
|
||||
new CacheProfile
|
||||
{ Duration = 20, Location = ResponseCacheLocation.Any, NoStore = false, VaryByHeader = "Accept" }
|
||||
{ Duration = 20, Location = ResponseCacheLocation.Any, NoStore = false, VaryByHeader = "Accept", VaryByQueryKeys = new[] { "QueryKey" } }
|
||||
};
|
||||
|
||||
// Select parameters override the selected profile.
|
||||
|
|
@ -114,10 +117,11 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
Duration = 10,
|
||||
Location = ResponseCacheLocation.Client,
|
||||
NoStore = false,
|
||||
VaryByHeader = "Test"
|
||||
VaryByHeader = "Test",
|
||||
VaryByQueryKeys = new[] { "ProfileQueryKey" }
|
||||
} } },
|
||||
new CacheProfile
|
||||
{ Duration = 534, Location = ResponseCacheLocation.Client, NoStore = false, VaryByHeader = "Test" }
|
||||
{ Duration = 534, Location = ResponseCacheLocation.Client, NoStore = false, VaryByHeader = "Test", VaryByQueryKeys = new[] { "ProfileQueryKey" } }
|
||||
};
|
||||
|
||||
// Duration parameter gets added to the selected profile.
|
||||
|
|
@ -131,10 +135,11 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
{
|
||||
Location = ResponseCacheLocation.Client,
|
||||
NoStore = false,
|
||||
VaryByHeader = "Test"
|
||||
VaryByHeader = "Test",
|
||||
VaryByQueryKeys = new[] { "ProfileQueryKey" }
|
||||
} } },
|
||||
new CacheProfile
|
||||
{ Duration = 534, Location = ResponseCacheLocation.Client, NoStore = false, VaryByHeader = "Test" }
|
||||
{ Duration = 534, Location = ResponseCacheLocation.Client, NoStore = false, VaryByHeader = "Test", VaryByQueryKeys = new[] { "ProfileQueryKey" } }
|
||||
};
|
||||
|
||||
// Default values gets added for parameters which are absent
|
||||
|
|
@ -146,7 +151,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
},
|
||||
new Dictionary<string, CacheProfile>() { { "TestCacheProfile", new CacheProfile() } },
|
||||
new CacheProfile
|
||||
{ Duration = 5234, Location = ResponseCacheLocation.Any, NoStore = false, VaryByHeader = null }
|
||||
{ Duration = 5234, Location = ResponseCacheLocation.Any, NoStore = false, VaryByHeader = null, VaryByQueryKeys = null }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -167,6 +172,14 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
Assert.Equal(expectedProfile.Location, responseCacheFilter.Location);
|
||||
Assert.Equal(expectedProfile.NoStore, responseCacheFilter.NoStore);
|
||||
Assert.Equal(expectedProfile.VaryByHeader, responseCacheFilter.VaryByHeader);
|
||||
if (expectedProfile.VaryByQueryKeys == null)
|
||||
{
|
||||
Assert.Null(responseCacheFilter.VaryByQueryKeys);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(expectedProfile.VaryByQueryKeys, responseCacheFilter.VaryByQueryKeys);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -191,14 +204,17 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
public void ResponseCache_SetsAllHeaders()
|
||||
{
|
||||
// Arrange
|
||||
var varyByQueryKeys = new[] { "QueryKey" };
|
||||
var responseCache = new ResponseCacheAttribute()
|
||||
{
|
||||
Duration = 100,
|
||||
Location = ResponseCacheLocation.Any,
|
||||
VaryByHeader = "Accept"
|
||||
VaryByHeader = "Accept",
|
||||
VaryByQueryKeys = varyByQueryKeys
|
||||
};
|
||||
var filter = (ResponseCacheFilter)responseCache.CreateInstance(GetServiceProvider(cacheProfiles: null));
|
||||
var context = GetActionExecutingContext(filter);
|
||||
context.HttpContext.Features.Set<IResponseCachingFeature>(new ResponseCachingFeature());
|
||||
|
||||
// Act
|
||||
filter.OnActionExecuting(context);
|
||||
|
|
@ -212,6 +228,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
Assert.True(response.Headers.TryGetValue("Vary", out values));
|
||||
data = Assert.Single(values);
|
||||
Assert.Equal("Accept", data);
|
||||
Assert.Equal(varyByQueryKeys, context.HttpContext.Features.Get<IResponseCachingFeature>().VaryByQueryKeys);
|
||||
}
|
||||
|
||||
public static TheoryData<ResponseCacheAttribute, string> CacheControlData
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"version": "1.1.0-*",
|
||||
"type": "build"
|
||||
},
|
||||
"Microsoft.AspNetCore.ResponseCaching": "1.0.0-*",
|
||||
"Microsoft.AspNetCore.Testing": "1.1.0-*",
|
||||
"Microsoft.DotNet.InternalAbstractions": "1.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection": "1.1.0-*",
|
||||
|
|
|
|||
Loading…
Reference in New Issue