Issue #1785 - Changes to add CacheProfiles for response caching.

This commit is contained in:
sornaks 2015-01-29 14:50:01 -08:00
parent 6e8cc6ba74
commit 4691823a50
16 changed files with 1048 additions and 350 deletions

View File

@ -0,0 +1,39 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Defines a set of settings which can be used for response caching.
/// </summary>
public class CacheProfile
{
/// <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 <see cref="HttpContext.Response" />.
/// </summary>
public int? Duration { get; set; }
/// <summary>
/// Gets or sets the location where the data from a particular URL must be cached.
/// If this property is set to a non null value,
/// the "Cache-control" header is set in the <see cref="HttpContext.Response" />.
/// </summary>
public ResponseCacheLocation? Location { get; set; }
/// <summary>
/// Gets or sets the value which determines whether the data should be stored or not.
/// When set to <see langword="true"/>, it sets "Cache-control" header in
/// <see cref="HttpContext.Response" /> to "no-store".
/// Ignores the "Location" parameter for values other than "None".
/// Ignores the "Duration" parameter.
/// </summary>
public bool? NoStore { get; set; }
/// <summary>
/// Gets or sets the value for the Vary header in <see cref="HttpContext.Response" />.
/// </summary>
public string VaryByHeader { get; set; }
}
}

View File

@ -1,26 +1,28 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
using System.Linq;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// An <see cref="ActionFilterAttribute"/> which sets the appropriate headers related to Response caching.
/// Specifies the parameters necessary for setting appropriate headers in response caching.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ResponseCacheAttribute : ActionFilterAttribute, IResponseCacheFilter
public class ResponseCacheAttribute : Attribute, IFilterFactory, IOrderedFilter
{
// A nullable-int cannot be used as an Attribute parameter.
// Hence this nullable-int is present to back the Duration property.
// The same goes for nullable-ResponseCacheLocation and nullable-bool.
private int? _duration;
private ResponseCacheLocation? _location;
private bool? _noStore;
/// <summary>
/// Gets or sets the duration in seconds for which the response is cached.
/// This is a required parameter.
/// This sets "max-age" in "Cache-control" header.
/// </summary>
public int Duration
@ -38,97 +40,85 @@ namespace Microsoft.AspNet.Mvc
/// <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 _location ?? ResponseCacheLocation.Any;
}
set
{
_location = value;
}
}
/// <summary>
/// Gets or sets the value which determines whether the data should be stored or not.
/// When set to true, it sets "Cache-control" header to "no-store".
/// When set to <see langword="true"/>, it sets "Cache-control" header to "no-store".
/// Ignores the "Location" parameter for values other than "None".
/// Ignores the "duration" parameter.
/// </summary>
public bool NoStore { get; set; }
public bool NoStore
{
get
{
return _noStore ?? false;
}
set
{
_noStore = value;
}
}
/// <summary>
/// Gets or sets the value for the Vary response header.
/// </summary>
public string VaryByHeader { get; set; }
// <inheritdoc />
public override void OnActionExecuting([NotNull] ActionExecutingContext context)
/// <summary>
/// Gets or sets the value of the cache profile name.
/// </summary>
public string CacheProfileName { get; set; }
/// <summary>
/// The order of the filter.
/// </summary>
public int Order { get; set; }
public IFilter CreateInstance([NotNull] IServiceProvider serviceProvider)
{
// If there are more filters which can override the values written by this filter,
// then skip execution of this filter.
if (IsOverridden(context))
var optionsAccessor = serviceProvider.GetRequiredService<IOptions<MvcOptions>>();
CacheProfile selectedProfile = null;
if (CacheProfileName != null)
{
return;
}
var headers = context.HttpContext.Response.Headers;
// Clear all headers
headers.Remove("Vary");
headers.Remove("Cache-control");
headers.Remove("Pragma");
if (!string.IsNullOrEmpty(VaryByHeader))
{
headers.Set("Vary", VaryByHeader);
}
if (NoStore)
{
headers.Set("Cache-control", "no-store");
// Cache-control: no-store, no-cache is valid.
if (Location == ResponseCacheLocation.None)
optionsAccessor.Options.CacheProfiles.TryGetValue(CacheProfileName, out selectedProfile);
if (selectedProfile == null)
{
headers.Append("Cache-control", "no-cache");
headers.Set("Pragma", "no-cache");
throw new InvalidOperationException(Resources.FormatCacheProfileNotFound(CacheProfileName));
}
}
else
{
if (_duration == null)
// If the ResponseCacheAttribute parameters are set,
// then it must override the values from the Cache Profile.
// The below expression first checks if the duration is set by the attribute's parameter.
// If absent, it checks the selected cache profile (Note: There can be no cache profile as well)
// The same is the case for other properties.
_duration = _duration ?? selectedProfile?.Duration;
_noStore = _noStore ?? selectedProfile?.NoStore;
_location = _location ?? selectedProfile?.Location;
VaryByHeader = VaryByHeader ?? selectedProfile?.VaryByHeader;
// ResponseCacheFilter cannot take any null values. Hence, if there are any null values,
// the properties convert them to their defaults and are passed on.
return new ResponseCacheFilter(
new CacheProfile
{
throw new InvalidOperationException(
Resources.FormatResponseCache_SpecifyDuration(nameof(NoStore), nameof(Duration)));
}
string cacheControlValue = null;
switch (Location)
{
case ResponseCacheLocation.Any:
cacheControlValue = "public";
break;
case ResponseCacheLocation.Client:
cacheControlValue = "private";
break;
case ResponseCacheLocation.None:
cacheControlValue = "no-cache";
headers.Set("Pragma", "no-cache");
break;
}
cacheControlValue = string.Format(
CultureInfo.InvariantCulture,
"{0}{1}max-age={2}",
cacheControlValue,
cacheControlValue != null? "," : null,
Duration);
if (cacheControlValue != null)
{
headers.Set("Cache-control", cacheControlValue);
}
}
}
// internal for Unit Testing purposes.
internal bool IsOverridden([NotNull] ActionExecutingContext context)
{
// Return true if there are any filters which are after the current filter. In which case the current
// filter should be skipped.
return context.Filters.OfType<IResponseCacheFilter>().Last() != this;
Duration = _duration,
Location = _location,
NoStore = _noStore,
VaryByHeader = VaryByHeader
});
}
}
}

View File

@ -0,0 +1,141 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
using System.Linq;
using Microsoft.AspNet.Mvc.Core;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// An <see cref="ActionFilterAttribute"/> which sets the appropriate headers related to response caching.
/// </summary>
public class ResponseCacheFilter : IActionFilter, IResponseCacheFilter
{
/// <summary>
/// Creates a new instance of <see cref="ResponseCacheFilter"/>
/// </summary>
/// <param name="cacheProfile">The profile which contains the settings for
/// <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;
}
/// <summary>
/// Gets or sets the duration in seconds for which the response is cached.
/// This is a required parameter.
/// This sets "max-age" in "Cache-control" header.
/// </summary>
public int Duration { get; set; }
/// <summary>
/// Gets or sets the location where the data from a particular URL must be cached.
/// </summary>
public ResponseCacheLocation Location { get; set; }
/// <summary>
/// Gets or sets the value which determines whether the data should be stored or not.
/// When set to <see langword="true"/>, it sets "Cache-control" header to "no-store".
/// Ignores the "Location" parameter for values other than "None".
/// Ignores the "duration" parameter.
/// </summary>
public bool NoStore { get; set; }
/// <summary>
/// Gets or sets the value for the Vary response header.
/// </summary>
public string VaryByHeader { get; set; }
// <inheritdoc />
public void OnActionExecuting([NotNull] ActionExecutingContext context)
{
// If there are more filters which can override the values written by this filter,
// then skip execution of this filter.
if (IsOverridden(context))
{
return;
}
var headers = context.HttpContext.Response.Headers;
// Clear all headers
headers.Remove("Vary");
headers.Remove("Cache-control");
headers.Remove("Pragma");
if (!string.IsNullOrEmpty(VaryByHeader))
{
headers.Set("Vary", VaryByHeader);
}
if (NoStore)
{
headers.Set("Cache-control", "no-store");
// Cache-control: no-store, no-cache is valid.
if (Location == ResponseCacheLocation.None)
{
headers.Append("Cache-control", "no-cache");
headers.Set("Pragma", "no-cache");
}
}
else
{
string cacheControlValue = null;
switch (Location)
{
case ResponseCacheLocation.Any:
cacheControlValue = "public";
break;
case ResponseCacheLocation.Client:
cacheControlValue = "private";
break;
case ResponseCacheLocation.None:
cacheControlValue = "no-cache";
headers.Set("Pragma", "no-cache");
break;
}
cacheControlValue = string.Format(
CultureInfo.InvariantCulture,
"{0}{1}max-age={2}",
cacheControlValue,
cacheControlValue != null? "," : null,
Duration);
if (cacheControlValue != null)
{
headers.Set("Cache-control", cacheControlValue);
}
}
}
// <inheritdoc />
public void OnActionExecuted([NotNull]ActionExecutedContext context)
{
}
// internal for Unit Testing purposes.
internal bool IsOverridden([NotNull] ActionExecutingContext context)
{
// Return true if there are any filters which are after the current filter. In which case the current
// filter should be skipped.
return context.Filters.OfType<IResponseCacheFilter>().Last() != this;
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Mvc.ApplicationModels;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.OptionDescriptors;
@ -79,11 +80,11 @@ namespace Microsoft.AspNet.Mvc
/// <summary>
/// Gets a list of <see cref="ExcludeValidationDescriptor"/> which are used to construct a list
/// of exclude filters by <see cref="IValidationExcludeFiltersProvider"/>,
/// of exclude filters by <see cref="IValidationExcludeFiltersProvider"/>.
/// </summary>
public List<ExcludeValidationDescriptor> ValidationExcludeFilters { get; }
= new List<ExcludeValidationDescriptor>();
/// <summary>
/// Gets or sets the maximum number of validation errors that are allowed by this application before further
/// errors are ignored.
@ -139,5 +140,12 @@ namespace Microsoft.AspNet.Mvc
/// when it contains the media type */*. <see langword="false"/> by default.
/// </summary>
public bool RespectBrowserAcceptHeader { get; set; } = false;
/// <summary>
/// Gets a Dictionary of CacheProfile Names, <see cref="CacheProfile"/> which are pre-defined settings for
/// <see cref="ResponseCacheFilter"/>.
/// </summary>
public Dictionary<string, CacheProfile> CacheProfiles { get; }
= new Dictionary<string, CacheProfile>(StringComparer.OrdinalIgnoreCase);
}
}

View File

@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Mvc.Core
= new ResourceManager("Microsoft.AspNet.Mvc.Core.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// The argument '{0}' is invalid. Media types containing wildcards (*) are not supported.
/// The argument '{0}' is invalid. Media types which match all types or match all subtypes are not supported.
/// </summary>
internal static string MatchAllContentTypeIsNotAllowed
{
@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc.Core
}
/// <summary>
/// The argument '{0}' is invalid. Media types containing wildcards (*) are not supported.
/// The argument '{0}' is invalid. Media types which match all types or match all subtypes are not supported.
/// </summary>
internal static string FormatMatchAllContentTypeIsNotAllowed(object p0)
{
@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.Core
}
/// <summary>
/// The content-type '{0}' added in the '{1}' property is invalid. Media types containing wildcards (*) are not supported.
/// The content-type '{0}' added in the '{1}' property is invalid. Media types which match all types or match all subtypes are not supported.
/// </summary>
internal static string ObjectResult_MatchAllContentType
{
@ -35,7 +35,7 @@ namespace Microsoft.AspNet.Mvc.Core
}
/// <summary>
/// The content-type '{0}' added in the '{1}' property is invalid. Media types containing wildcards (*) are not supported.
/// The content-type '{0}' added in the '{1}' property is invalid. Media types which match all types or match all subtypes are not supported.
/// </summary>
internal static string FormatObjectResult_MatchAllContentType(object p0, object p1)
{
@ -562,24 +562,6 @@ namespace Microsoft.AspNet.Mvc.Core
get { return GetString("Common_ValueNotValidForProperty"); }
}
/// <summary>
/// The media type "{0}" is not valid. MediaTypes containing wildcards (*) are not allowed in formatter
/// mappings.
/// </summary>
internal static string FormatterMappings_NotValidMediaType
{
get { return GetString("FormatterMappings_NotValidMediaType"); }
}
/// <summary>
/// The format provided is invalid '{0}'. A format must be a non-empty file-extension, optionally prefixed
/// with a '.' character.
/// </summary>
internal static string Format_NotValid
{
get { return GetString("Format_NotValid"); }
}
/// <summary>
/// The value '{0}' is invalid.
/// </summary>
@ -1756,6 +1738,38 @@ namespace Microsoft.AspNet.Mvc.Core
return string.Format(CultureInfo.CurrentCulture, GetString("ApiExplorer_UnsupportedAction"), p0);
}
/// <summary>
/// The media type "{0}" is not valid. MediaTypes containing wildcards (*) are not allowed in formatter mappings.
/// </summary>
internal static string FormatterMappings_NotValidMediaType
{
get { return GetString("FormatterMappings_NotValidMediaType"); }
}
/// <summary>
/// The media type "{0}" is not valid. MediaTypes containing wildcards (*) are not allowed in formatter mappings.
/// </summary>
internal static string FormatFormatterMappings_NotValidMediaType(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("FormatterMappings_NotValidMediaType"), p0);
}
/// <summary>
/// The format provided is invalid '{0}'. A format must be a non-empty file-extension, optionally prefixed with a '.' character.
/// </summary>
internal static string Format_NotValid
{
get { return GetString("Format_NotValid"); }
}
/// <summary>
/// The format provided is invalid '{0}'. A format must be a non-empty file-extension, optionally prefixed with a '.' character.
/// </summary>
internal static string FormatFormat_NotValid(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Format_NotValid"), p0);
}
/// <summary>
/// No URL for remote validation could be found.
/// </summary>
@ -1788,6 +1802,22 @@ namespace Microsoft.AspNet.Mvc.Core
return string.Format(CultureInfo.CurrentCulture, GetString("RemoteAttribute_RemoteValidationFailed"), p0);
}
/// <summary>
/// The '{0}' cache profile is not defined.
/// </summary>
internal static string CacheProfileNotFound
{
get { return GetString("CacheProfileNotFound"); }
}
/// <summary>
/// The '{0}' cache profile is not defined.
/// </summary>
internal static string FormatCacheProfileNotFound(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("CacheProfileNotFound"), p0);
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -450,7 +450,7 @@
</data>
<data name="ApiExplorer_UnsupportedAction" xml:space="preserve">
<value>The action '{0}' has ApiExplorer enabled, but is using conventional routing. Only actions which use attribute routing support ApiExplorer.</value>
</data>
</data>
<data name="FormatterMappings_NotValidMediaType" xml:space="preserve">
<value>The media type "{0}" is not valid. MediaTypes containing wildcards (*) are not allowed in formatter mappings.</value>
</data>
@ -463,4 +463,7 @@
<data name="RemoteAttribute_RemoteValidationFailed" xml:space="preserve">
<value>'{0}' is invalid.</value>
</data>
<data name="CacheProfileNotFound" xml:space="preserve">
<value>The '{0}' cache profile is not defined.</value>
</data>
</root>

View File

@ -0,0 +1,204 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// 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 Microsoft.Framework.OptionsModel;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc
{
public class ResponseCacheAttributeTest
{
[Theory]
[InlineData("Cache20Sec")]
// To verify case-insensitive lookup.
[InlineData("cache20sec")]
public void CreateInstance_SelectsTheAppropriateCacheProfile(string profileName)
{
// Arrange
var responseCache = new ResponseCacheAttribute() {
CacheProfileName = profileName
};
var cacheProfiles = new Dictionary<string, CacheProfile>();
cacheProfiles.Add("Cache20Sec", new CacheProfile { NoStore = true });
cacheProfiles.Add("Test", new CacheProfile { Duration = 20 });
// Act
var createdFilter = responseCache.CreateInstance(GetServiceProvider(cacheProfiles));
// Assert
var responseCacheFilter = Assert.IsType<ResponseCacheFilter>(createdFilter);
Assert.True(responseCacheFilter.NoStore);
}
[Fact]
public void CreateInstance_ThrowsIfThereAreNoMatchingCacheProfiles()
{
// Arrange
var responseCache = new ResponseCacheAttribute()
{
CacheProfileName = "HelloWorld"
};
var cacheProfiles = new Dictionary<string, CacheProfile>();
cacheProfiles.Add("Cache20Sec", new CacheProfile { NoStore = true });
cacheProfiles.Add("Test", new CacheProfile { Duration = 20 });
// Act
var ex = Assert.Throws<InvalidOperationException>(
() => responseCache.CreateInstance(GetServiceProvider(cacheProfiles)));
Assert.Equal("The 'HelloWorld' cache profile is not defined.", ex.Message);
}
public static IEnumerable<object[]> OverrideData
{
get
{
// 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" },
null,
new CacheProfile
{ Duration = 20, Location = ResponseCacheLocation.Any, NoStore = false, VaryByHeader = "Accept" }
};
yield return new object[] {
new ResponseCacheAttribute()
{ Duration = 0, Location = ResponseCacheLocation.None, NoStore = true, VaryByHeader = null },
null,
new CacheProfile
{ Duration = 0, Location = ResponseCacheLocation.None, NoStore = true, VaryByHeader = null }
};
// Everything gets overriden if attribute parameters are present,
// when a particular cache profile is chosen.
yield return new object[] {
new ResponseCacheAttribute()
{
Duration = 20,
Location = ResponseCacheLocation.Any,
NoStore = false,
VaryByHeader = "Accept",
CacheProfileName = "TestCacheProfile"
},
new Dictionary<string, CacheProfile> { { "TestCacheProfile", new CacheProfile
{
Duration = 10,
Location = ResponseCacheLocation.Client,
NoStore = true,
VaryByHeader = "Test"
} } },
new CacheProfile
{ Duration = 20, Location = ResponseCacheLocation.Any, NoStore = false, VaryByHeader = "Accept" }
};
// Select parameters override the selected profile.
yield return new object[] {
new ResponseCacheAttribute()
{
Duration = 534,
CacheProfileName = "TestCacheProfile"
},
new Dictionary<string, CacheProfile>() { { "TestCacheProfile", new CacheProfile
{
Duration = 10,
Location = ResponseCacheLocation.Client,
NoStore = false,
VaryByHeader = "Test"
} } },
new CacheProfile
{ Duration = 534, Location = ResponseCacheLocation.Client, NoStore = false, VaryByHeader = "Test" }
};
// Duration parameter gets added to the selected profile.
yield return new object[] {
new ResponseCacheAttribute()
{
Duration = 534,
CacheProfileName = "TestCacheProfile"
},
new Dictionary<string, CacheProfile>() { { "TestCacheProfile", new CacheProfile
{
Location = ResponseCacheLocation.Client,
NoStore = false,
VaryByHeader = "Test"
} } },
new CacheProfile
{ Duration = 534, Location = ResponseCacheLocation.Client, NoStore = false, VaryByHeader = "Test" }
};
// Default values gets added for parameters which are absent
yield return new object[] {
new ResponseCacheAttribute()
{
Duration = 5234,
CacheProfileName = "TestCacheProfile"
},
new Dictionary<string, CacheProfile>() { { "TestCacheProfile", new CacheProfile() } },
new CacheProfile
{ Duration = 5234, Location = ResponseCacheLocation.Any, NoStore = false, VaryByHeader = null }
};
}
}
[Theory]
[MemberData(nameof(OverrideData))]
public void CreateInstance_HonorsOverrides(
ResponseCacheAttribute responseCache,
Dictionary<string, CacheProfile> cacheProfiles,
CacheProfile expectedProfile)
{
// Arrange & Act
var createdFilter = responseCache.CreateInstance(GetServiceProvider(cacheProfiles));
// Assert
var responseCacheFilter = Assert.IsType<ResponseCacheFilter>(createdFilter);
Assert.Equal(expectedProfile.Duration, responseCacheFilter.Duration);
Assert.Equal(expectedProfile.Location, responseCacheFilter.Location);
Assert.Equal(expectedProfile.NoStore, responseCacheFilter.NoStore);
Assert.Equal(expectedProfile.VaryByHeader, responseCacheFilter.VaryByHeader);
}
[Fact]
public void CreateInstance_ThrowsWhenTheDurationIsNotSet_WithNoStoreFalse()
{
// Arrange
var responseCache = new ResponseCacheAttribute()
{
CacheProfileName = "Test"
};
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);
}
private IServiceProvider GetServiceProvider(Dictionary<string, CacheProfile> cacheProfiles)
{
var serviceProvider = new Mock<IServiceProvider>();
var optionsAccessor = new Mock<IOptions<MvcOptions>>();
var options = new MvcOptions();
if (cacheProfiles != null)
{
foreach (var p in cacheProfiles)
{
options.CacheProfiles.Add(p.Key, p.Value);
}
}
optionsAccessor.SetupGet(o => o.Options).Returns(options);
serviceProvider
.Setup(s => s.GetService(typeof(IOptions<MvcOptions>)))
.Returns(optionsAccessor.Object);
return serviceProvider.Object;
}
}
}

View File

@ -0,0 +1,318 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// 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 Microsoft.AspNet.Http.Core;
using Microsoft.AspNet.Routing;
using Xunit;
namespace Microsoft.AspNet.Mvc
{
public class ResponseCacheFilterTest
{
[Fact]
public void ResponseCacheFilter_DoesNotThrow_WhenNoStoreIsTrue()
{
// Arrange
var cache = new ResponseCacheFilter(
new CacheProfile
{
NoStore = true,
Duration = null
});
var context = GetActionExecutingContext(new List<IFilter> { cache });
// Act
cache.OnActionExecuting(context);
// Assert
Assert.Equal("no-store", context.HttpContext.Response.Headers.Get("Cache-control"));
}
[Fact]
public void ResponseCacheFilter_ThrowsIfDurationIsNotSet_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.",
ex.Message);
}
public static IEnumerable<object[]> 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"
};
}
}
[Theory]
[MemberData(nameof(CacheControlData))]
public void OnActionExecuting_CanSetCacheControlHeaders(ResponseCacheFilter cache, string output)
{
// Arrange
var context = GetActionExecutingContext(new List<IFilter> { cache });
// Act
cache.OnActionExecuting(context);
// Assert
Assert.Equal(output, context.HttpContext.Response.Headers.Get("Cache-control"));
}
public static IEnumerable<object[]> 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"
};
}
}
[Theory]
[MemberData(nameof(NoStoreData))]
public void OnActionExecuting_DoesNotSetLocationOrDuration_IfNoStoreIsSet(
ResponseCacheFilter cache, string output)
{
// Arrange
var context = GetActionExecutingContext(new List<IFilter> { cache });
// Act
cache.OnActionExecuting(context);
// Assert
Assert.Equal(output, context.HttpContext.Response.Headers.Get("Cache-control"));
}
public static IEnumerable<object[]> VaryData
{
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"
};
}
}
[Theory]
[MemberData(nameof(VaryData))]
public void ResponseCacheCanSetVary(ResponseCacheFilter cache, string varyOutput, string cacheControlOutput)
{
// Arrange
var context = GetActionExecutingContext(new List<IFilter> { cache });
// Act
cache.OnActionExecuting(context);
// Assert
Assert.Equal(varyOutput, context.HttpContext.Response.Headers.Get("Vary"));
Assert.Equal(cacheControlOutput, context.HttpContext.Response.Headers.Get("Cache-control"));
}
[Fact]
public void SetsPragmaOnNoCache()
{
// Arrange
var cache = new ResponseCacheFilter(
new CacheProfile
{
Duration = 0, Location = ResponseCacheLocation.None, NoStore = true, VaryByHeader = null
});
var context = GetActionExecutingContext(new List<IFilter> { cache });
// Act
cache.OnActionExecuting(context);
// Assert
Assert.Equal("no-store,no-cache", context.HttpContext.Response.Headers.Get("Cache-control"));
Assert.Equal("no-cache", context.HttpContext.Response.Headers.Get("Pragma"));
}
[Fact]
public void IsOverridden_ReturnsTrueForAllButLastFilter()
{
// Arrange
var caches = new List<IFilter>();
caches.Add(new ResponseCacheFilter(
new CacheProfile
{
Duration = 0, Location = ResponseCacheLocation.Any, NoStore = false, VaryByHeader = null
}));
caches.Add(new ResponseCacheFilter(
new CacheProfile
{
Duration = 0, Location = ResponseCacheLocation.Any, NoStore = false, VaryByHeader = null
}));
var context = GetActionExecutingContext(caches);
// Act & Assert
var cache = Assert.IsType<ResponseCacheFilter>(caches[0]);
Assert.True(cache.IsOverridden(context));
cache = Assert.IsType<ResponseCacheFilter>(caches[1]);
Assert.False(cache.IsOverridden(context));
}
private ActionExecutingContext GetActionExecutingContext(List<IFilter> filters = null)
{
return new ActionExecutingContext(
new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()),
filters ?? new List<IFilter>(),
new Dictionary<string, object>(),
new object());
}
}
}

View File

@ -30,5 +30,18 @@ namespace Microsoft.AspNet.Mvc.Core.Test
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => options.MaxModelValidationErrors = -1);
Assert.Equal("value", ex.ParamName);
}
[Fact]
public void ThrowsWhenMultipleCacheProfilesWithSameNameAreAdded()
{
// Arrange
var options = new MvcOptions();
options.CacheProfiles.Add("HelloWorld", new CacheProfile { Duration = 10 });
// Act & Assert
var ex = Assert.Throws<ArgumentException>(
() => options.CacheProfiles.Add("HelloWorld", new CacheProfile { Duration = 5 }));
Assert.Equal("An item with the same key has already been added.", ex.Message);
}
}
}

View File

@ -1,243 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// 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 Microsoft.AspNet.Http.Core;
using Microsoft.AspNet.Routing;
using Xunit;
namespace Microsoft.AspNet.Mvc
{
public class ResponseCacheAttributeTest
{
[Fact]
public void OnActionExecuting_DoesNotThrow_WhenNoStoreIsTrue()
{
// Arrange
var cache = new ResponseCacheAttribute() { NoStore = true };
var context = GetActionExecutingContext(new List<IFilter> { cache });
// Act
cache.OnActionExecuting(context);
// Assert
Assert.Equal("no-store", context.HttpContext.Response.Headers.Get("Cache-control"));
}
[Fact]
public void OnActionExecuting_ThrowsIfDurationIsNotSet_WhenNoStoreIsFalse()
{
// Arrange
var cache = new ResponseCacheAttribute() { NoStore = false };
var context = GetActionExecutingContext(new List<IFilter> { cache });
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => { cache.OnActionExecuting(context); });
Assert.Equal("If the 'NoStore' property is not set to true, 'Duration' property must be specified.",
exception.Message);
}
public static IEnumerable<object[]> CacheControlData
{
get
{
yield return new object[] { new ResponseCacheAttribute { NoStore = true, Duration = 0 }, "no-store" };
// If no-store is set, then location is ignored.
yield return new object[] {
new ResponseCacheAttribute
{ NoStore = true, Duration = 0, Location = ResponseCacheLocation.Client },
"no-store"
};
yield return new object[] {
new ResponseCacheAttribute { NoStore = true, Duration = 0, Location = ResponseCacheLocation.Any },
"no-store"
};
// If no-store is set, then duration is ignored.
yield return new object[] {
new ResponseCacheAttribute { NoStore = true, Duration = 100 }, "no-store"
};
yield return new object[] {
new ResponseCacheAttribute { Location = ResponseCacheLocation.Client, Duration = 10 },
"private,max-age=10"
};
yield return new object[] {
new ResponseCacheAttribute { Location = ResponseCacheLocation.Any, Duration = 10 },
"public,max-age=10"
};
yield return new object[] {
new ResponseCacheAttribute { Location = ResponseCacheLocation.None, Duration = 10 },
"no-cache,max-age=10"
};
yield return new object[] {
new ResponseCacheAttribute { Location = ResponseCacheLocation.Client, Duration = 10 },
"private,max-age=10"
};
yield return new object[] {
new ResponseCacheAttribute { Location = ResponseCacheLocation.Any, Duration = 31536000 },
"public,max-age=31536000"
};
yield return new object[] {
new ResponseCacheAttribute { Duration = 20 },
"public,max-age=20"
};
}
}
[Theory]
[MemberData(nameof(CacheControlData))]
public void OnActionExecuting_CanSetCacheControlHeaders(ResponseCacheAttribute cache, string output)
{
// Arrange
var context = GetActionExecutingContext(new List<IFilter> { cache });
// Act
cache.OnActionExecuting(context);
// Assert
Assert.Equal(output, context.HttpContext.Response.Headers.Get("Cache-control"));
}
public static IEnumerable<object[]> NoStoreData
{
get
{
// If no-store is set, then location is ignored.
yield return new object[] {
new ResponseCacheAttribute
{ NoStore = true, Location = ResponseCacheLocation.Client, Duration = 0 },
"no-store"
};
yield return new object[] {
new ResponseCacheAttribute { NoStore = true, Location = ResponseCacheLocation.Any, Duration = 0 },
"no-store"
};
// If no-store is set, then duration is ignored.
yield return new object[] {
new ResponseCacheAttribute { NoStore = true, Duration = 100 }, "no-store"
};
}
}
[Theory]
[MemberData(nameof(NoStoreData))]
public void OnActionExecuting_DoesNotSetLocationOrDuration_IfNoStoreIsSet(
ResponseCacheAttribute cache, string output)
{
// Arrange
var context = GetActionExecutingContext(new List<IFilter> { cache });
// Act
cache.OnActionExecuting(context);
// Assert
Assert.Equal(output, context.HttpContext.Response.Headers.Get("Cache-control"));
}
public static IEnumerable<object[]> VaryData
{
get
{
yield return new object[] {
new ResponseCacheAttribute { VaryByHeader = "Accept", Duration = 10 },
"Accept",
"public,max-age=10" };
yield return new object[] {
new ResponseCacheAttribute { VaryByHeader = "Accept", NoStore = true, Duration = 0 },
"Accept",
"no-store"
};
yield return new object[] {
new ResponseCacheAttribute {
Location = ResponseCacheLocation.Client, Duration = 10, VaryByHeader = "Accept"
},
"Accept",
"private,max-age=10"
};
yield return new object[] {
new ResponseCacheAttribute {
Location = ResponseCacheLocation.Any, Duration = 10, VaryByHeader = "Test"
},
"Test",
"public,max-age=10"
};
yield return new object[] {
new ResponseCacheAttribute {
Location = ResponseCacheLocation.Client, Duration = 10, VaryByHeader = "Test"
},
"Test",
"private,max-age=10"
};
yield return new object[] {
new ResponseCacheAttribute {
Location = ResponseCacheLocation.Any,
Duration = 31536000,
VaryByHeader = "Test"
},
"Test",
"public,max-age=31536000"
};
}
}
[Theory]
[MemberData(nameof(VaryData))]
public void ResponseCacheCanSetVary(ResponseCacheAttribute cache, string varyOutput, string cacheControlOutput)
{
// Arrange
var context = GetActionExecutingContext(new List<IFilter> { cache });
// Act
cache.OnActionExecuting(context);
// Assert
Assert.Equal(varyOutput, context.HttpContext.Response.Headers.Get("Vary"));
Assert.Equal(cacheControlOutput, context.HttpContext.Response.Headers.Get("Cache-control"));
}
[Fact]
public void SetsPragmaOnNoCache()
{
// Arrange
var cache = new ResponseCacheAttribute()
{
NoStore = true,
Location = ResponseCacheLocation.None,
Duration = 0
};
var context = GetActionExecutingContext(new List<IFilter> { cache });
// Act
cache.OnActionExecuting(context);
// Assert
Assert.Equal("no-store,no-cache", context.HttpContext.Response.Headers.Get("Cache-control"));
Assert.Equal("no-cache", context.HttpContext.Response.Headers.Get("Pragma"));
}
[Fact]
public void IsOverridden_ReturnsTrueForAllButLastFilter()
{
// Arrange
var caches = new List<IFilter>();
caches.Add(new ResponseCacheAttribute());
caches.Add(new ResponseCacheAttribute());
var context = GetActionExecutingContext(caches);
// Act & Assert
Assert.True((caches[0] as ResponseCacheAttribute).IsOverridden(context));
Assert.False((caches[1] as ResponseCacheAttribute).IsOverridden(context));
}
private ActionExecutingContext GetActionExecutingContext(List<IFilter> filters = null)
{
return new ActionExecutingContext(
new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()),
filters ?? new List<IFilter>(),
new Dictionary<string, object>(),
new object());
}
}
}

View File

@ -128,7 +128,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal("Accept", data);
data = Assert.Single(response.Headers.GetValues("Cache-control"));
Assert.Equal("public, max-age=10", data);
Assert.Throws<InvalidOperationException>(() => response.Headers.GetValues("Pragma"));
IEnumerable<string> pragmaValues;
response.Headers.TryGetValues("Pragma", out pragmaValues);
Assert.Null(pragmaValues);
}
[Fact]
@ -154,8 +156,111 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var client = server.CreateClient();
// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => client.GetAsync("http://localhost/CacheHeaders/ThrowsWhenDurationIsNotSet"));
Assert.Equal(
"If the 'NoStore' property is not set to true, 'Duration' property must be specified.",
ex.Message);
}
// Cache Profiles
[Fact]
public async Task ResponseCache_SetsAllHeaders_FromCacheProfile()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/CacheProfiles/PublicCache30Sec");
// Assert
var data = Assert.Single(response.Headers.GetValues("Cache-control"));
Assert.Equal("public, max-age=30", data);
}
[Fact]
public async Task ResponseCache_SetsAllHeaders_ChosesTheRightProfile()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/CacheProfiles/PrivateCache30Sec");
// Assert
var data = Assert.Single(response.Headers.GetValues("Cache-control"));
Assert.Equal("max-age=30, private", data);
}
[Fact]
public async Task ResponseCache_SetsNoCacheHeaders()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/CacheProfiles/NoCache");
// Assert
var data = Assert.Single(response.Headers.GetValues("Cache-control"));
Assert.Equal("no-store, no-cache", data);
data = Assert.Single(response.Headers.GetValues("Pragma"));
Assert.Equal("no-cache", data);
}
[Fact]
public async Task ResponseCache_AddsHeaders()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/CacheProfiles/CacheProfileAddParameter");
// Assert
var data = Assert.Single(response.Headers.GetValues("Cache-control"));
Assert.Equal("public, max-age=30", data);
data = Assert.Single(response.Headers.GetValues("Vary"));
Assert.Equal("Accept", data);
}
[Fact]
public async Task ResponseCache_ModifiesHeaders()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/CacheProfiles/CacheProfileOverride");
// Assert
var data = Assert.Single(response.Headers.GetValues("Cache-control"));
Assert.Equal("public, max-age=10", data);
}
[Fact]
public async Task ResponseCacheAttribute_OnAction_OverridesTheValuesOnClass()
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/ClassLevelNoStore/CacheThisActionWithProfileSettings");
// Assert
var data = Assert.Single(response.Headers.GetValues("Vary"));
Assert.Equal("Accept", data);
data = Assert.Single(response.Headers.GetValues("Cache-control"));
Assert.Equal("public, max-age=30", data);
IEnumerable<string> pragmaValues;
response.Headers.TryGetValues("Pragma", out pragmaValues);
Assert.Null(pragmaValues);
}
}
}

View File

@ -7,36 +7,42 @@ namespace ResponseCacheWebSite
{
public class CacheHeadersController : Controller
{
[HttpGet("/CacheHeaders/Index")]
[ResponseCache(Duration = 100, Location = ResponseCacheLocation.Any, VaryByHeader = "Accept")]
public IActionResult Index()
{
return Content("Hello World!");
}
[HttpGet("/CacheHeaders/PublicCache")]
[ResponseCache(Duration = 100, Location = ResponseCacheLocation.Any)]
public IActionResult PublicCache()
{
return Content("Hello World!");
}
[HttpGet("/CacheHeaders/ClientCache")]
[ResponseCache(Duration = 100, Location = ResponseCacheLocation.Client)]
public IActionResult ClientCache()
{
return Content("Hello World!");
}
[HttpGet("/CacheHeaders/NoStore")]
[ResponseCache(NoStore = true, Duration = 0)]
public IActionResult NoStore()
{
return Content("Hello World!");
}
[HttpGet("/CacheHeaders/NoCacheAtAll")]
[ResponseCache(NoStore = true, Duration = 0, Location = ResponseCacheLocation.None)]
public IActionResult NoCacheAtAll()
{
return Content("Hello World!");
}
[HttpGet("/CacheHeaders/SetHeadersInAction")]
[ResponseCache(Duration = 40)]
public IActionResult SetHeadersInAction()
{
@ -44,12 +50,14 @@ namespace ResponseCacheWebSite
return Content("Hello World!");
}
[HttpGet("/CacheHeaders/SetsCacheControlPublicByDefault")]
[ResponseCache(Duration = 40)]
public IActionResult SetsCacheControlPublicByDefault()
{
return Content("Hello World!");
}
[HttpGet("/CacheHeaders/ThrowsWhenDurationIsNotSet")]
[ResponseCache(VaryByHeader = "Accept")]
public IActionResult ThrowsWhenDurationIsNotSet()
{

View File

@ -0,0 +1,45 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
namespace ResponseCacheWebSite.Controllers
{
public class CacheProfilesController
{
[HttpGet("/CacheProfiles/PublicCache30Sec")]
[ResponseCache(CacheProfileName = "PublicCache30Sec")]
public string PublicCache30Sec()
{
return "Hello World!";
}
[HttpGet("/CacheProfiles/PrivateCache30Sec")]
[ResponseCache(CacheProfileName = "PrivateCache30Sec")]
public string PrivateCache30Sec()
{
return "Hello World!";
}
[HttpGet("/CacheProfiles/NoCache")]
[ResponseCache(CacheProfileName = "NoCache")]
public string NoCache()
{
return "Hello World!";
}
[HttpGet("/CacheProfiles/CacheProfileAddParameter")]
[ResponseCache(CacheProfileName = "PublicCache30Sec", VaryByHeader = "Accept")]
public string CacheProfileAddParameter()
{
return "Hello World!";
}
[HttpGet("/CacheProfiles/CacheProfileOverride")]
[ResponseCache(CacheProfileName = "PublicCache30Sec", Duration = 10)]
public string CacheProfileOverride()
{
return "Hello World!";
}
}
}

View File

@ -8,22 +8,26 @@ namespace ResponseCacheWebSite
[ResponseCache(Duration = 100, Location = ResponseCacheLocation.Any, VaryByHeader = "Accept")]
public class ClassLevelCacheController
{
[HttpGet("/ClassLevelCache/GetHelloWorld")]
public string GetHelloWorld()
{
return "Hello, World!";
}
[HttpGet("/ClassLevelCache/GetFooBar")]
public string GetFooBar()
{
return "Foo Bar!";
}
[HttpGet("/ClassLevelCache/ConflictExistingHeader")]
[ResponseCache(Duration = 20)]
public string ConflictExistingHeader()
{
return "Conflict";
}
[HttpGet("/ClassLevelCache/DoNotCacheThisAction")]
[ResponseCache(NoStore = true, Duration = 0, Location = ResponseCacheLocation.None)]
public string DoNotCacheThisAction()
{

View File

@ -8,15 +8,24 @@ namespace ResponseCacheWebSite
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None, Duration = 0)]
public class ClassLevelNoStoreController
{
[HttpGet("/ClassLevelNoStore/GetHelloWorld")]
public string GetHelloWorld()
{
return "Hello, World!";
}
[HttpGet("/ClassLevelNoStore/CacheThisAction")]
[ResponseCache(VaryByHeader = "Accept", Duration = 10)]
public string CacheThisAction()
{
return "Conflict";
}
[HttpGet("/ClassLevelNoStore/CacheThisActionWithProfileSettings")]
[ResponseCache(CacheProfileName = "PublicCache30Sec", VaryByHeader = "Accept")]
public string CacheThisActionWithProfileSettings()
{
return "Conflict";
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Mvc;
using Microsoft.Framework.DependencyInjection;
namespace ResponseCacheWebSite
@ -11,10 +12,33 @@ namespace ResponseCacheWebSite
public void Configure(IApplicationBuilder app)
{
var configuration = app.GetTestConfiguration();
app.UseServices(services =>
{
services.AddMvc(configuration);
services.Configure<MvcOptions>(options =>
{
options.CacheProfiles.Add(
"PublicCache30Sec", new CacheProfile
{
Duration = 30,
Location = ResponseCacheLocation.Any
});
options.CacheProfiles.Add(
"PrivateCache30Sec", new CacheProfile
{
Duration = 30,
Location = ResponseCacheLocation.Client
});
options.CacheProfiles.Add(
"NoCache", new CacheProfile
{
NoStore = true,
Duration = 0,
Location = ResponseCacheLocation.None
});
});
});
app.UseMvc(routes =>