diff --git a/.vs/WebAPI.OutputCache/v15/Server/sqlite3/db.lock b/.vs/WebAPI.OutputCache/v15/Server/sqlite3/db.lock new file mode 100644 index 0000000..e69de29 diff --git a/.vs/WebAPI.OutputCache/v15/Server/sqlite3/storage.ide b/.vs/WebAPI.OutputCache/v15/Server/sqlite3/storage.ide new file mode 100644 index 0000000..1f21d86 Binary files /dev/null and b/.vs/WebAPI.OutputCache/v15/Server/sqlite3/storage.ide differ diff --git a/.vs/WebAPI.OutputCache/v15/Server/sqlite3/storage.ide-shm b/.vs/WebAPI.OutputCache/v15/Server/sqlite3/storage.ide-shm new file mode 100644 index 0000000..d3e015a Binary files /dev/null and b/.vs/WebAPI.OutputCache/v15/Server/sqlite3/storage.ide-shm differ diff --git a/.vs/WebAPI.OutputCache/v15/Server/sqlite3/storage.ide-wal b/.vs/WebAPI.OutputCache/v15/Server/sqlite3/storage.ide-wal new file mode 100644 index 0000000..95aedef Binary files /dev/null and b/.vs/WebAPI.OutputCache/v15/Server/sqlite3/storage.ide-wal differ diff --git a/sample/WebApi.OutputCache.V2.Demo/IgnoreController.cs b/sample/WebApi.OutputCache.V2.Demo/IgnoreController.cs index b9a9e42..45bfb94 100644 --- a/sample/WebApi.OutputCache.V2.Demo/IgnoreController.cs +++ b/sample/WebApi.OutputCache.V2.Demo/IgnoreController.cs @@ -13,6 +13,14 @@ public string GetCached() return DateTime.Now.ToString(); } + //Send the X-Test-Key header with your request and it will cache it based on the value of the X-Test-Key + [CacheOutput(ClientTimeSpan = 50, ServerTimeSpan = 50, IncludeCustomHeaders = "X-Test-Key")] + [Route("cachedwithheaders")] + public string GetCachedWithHeaders() + { + return DateTime.Now.ToString(); + } + [IgnoreCacheOutput] [Route("uncached")] public string GetUnCached() diff --git a/src/WebApi.OutputCache.V2/CacheOutputAttribute.cs b/src/WebApi.OutputCache.V2/CacheOutputAttribute.cs index fc79e71..e55b35c 100644 --- a/src/WebApi.OutputCache.V2/CacheOutputAttribute.cs +++ b/src/WebApi.OutputCache.V2/CacheOutputAttribute.cs @@ -187,7 +187,9 @@ public override void OnActionExecuting(HttpActionContext actionContext) var responseMediaType = GetExpectedMediaType(config, actionContext); actionContext.Request.Properties[CurrentRequestMediaType] = responseMediaType; - var cachekey = cacheKeyGenerator.MakeCacheKey(actionContext, responseMediaType, ExcludeQueryStringFromCacheKey); + var customRequestHeaders = AddCustomRequestHeaders(actionContext); + + var cachekey = cacheKeyGenerator.MakeCacheKey(actionContext, responseMediaType, ExcludeQueryStringFromCacheKey, customRequestHeaders); if (!_webApiCache.Contains(cachekey)) return; @@ -254,9 +256,11 @@ public override async Task OnActionExecutedAsync(HttpActionExecutedContext actio var httpConfig = actionExecutedContext.Request.GetConfiguration(); var config = httpConfig.CacheOutputConfiguration(); var cacheKeyGenerator = config.GetCacheKeyGenerator(actionExecutedContext.Request, CacheKeyGenerator); + var requestHeaders = AddCustomRequestHeaders(actionExecutedContext.Request.Headers); var responseMediaType = actionExecutedContext.Request.Properties[CurrentRequestMediaType] as MediaTypeHeaderValue ?? GetExpectedMediaType(httpConfig, actionExecutedContext.ActionContext); - var cachekey = cacheKeyGenerator.MakeCacheKey(actionExecutedContext.ActionContext, responseMediaType, ExcludeQueryStringFromCacheKey); + var reqestHeaders = AddCustomRequestHeaders(actionExecutedContext.Request.Headers); + var cachekey = cacheKeyGenerator.MakeCacheKey(actionExecutedContext.ActionContext, responseMediaType, ExcludeQueryStringFromCacheKey, requestHeaders); if (!string.IsNullOrWhiteSpace(cachekey) && !(_webApiCache.Contains(cachekey))) { @@ -359,6 +363,23 @@ protected virtual void AddCustomCachedHeaders(HttpResponseMessage response, Dict } } + protected virtual Dictionary> AddCustomRequestHeaders(HttpActionContext actionContext) + { + return AddCustomRequestHeaders(actionContext.Request.Headers); + } + protected virtual Dictionary> AddCustomRequestHeaders(HttpRequestHeaders requestHeaders) + { + Dictionary> headers = new Dictionary>(); + + if (!(string.IsNullOrEmpty(IncludeCustomHeaders))) + { + // convert to dictionary of lists to ensure thread safety if implementation of IEnumerable is changed + headers = requestHeaders.Where(h => IncludeCustomHeaders.ToLower().Contains(h.Key.ToLower())) + .ToDictionary(x => x.Key.ToLower(), x => x.Value.ToList()); + } + return headers; + } + protected virtual string CreateEtag(HttpActionExecutedContext actionExecutedContext, string cachekey, CacheTime cacheTime) { return Guid.NewGuid().ToString(); diff --git a/src/WebApi.OutputCache.V2/DefaultCacheKeyGenerator.cs b/src/WebApi.OutputCache.V2/DefaultCacheKeyGenerator.cs index 2f28ca8..a6ced8e 100644 --- a/src/WebApi.OutputCache.V2/DefaultCacheKeyGenerator.cs +++ b/src/WebApi.OutputCache.V2/DefaultCacheKeyGenerator.cs @@ -9,12 +9,16 @@ namespace WebApi.OutputCache.V2 { public class DefaultCacheKeyGenerator : ICacheKeyGenerator { - public virtual string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString = false) + public virtual string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString, Dictionary> headers) { var key = MakeBaseKey(context); var parameters = FormatParameters(context, excludeQueryString); + string custHeaders = GetCustomHeaders(headers); - return string.Format("{0}{1}:{2}", key, parameters, mediaType); + if (!(string.IsNullOrEmpty(custHeaders))) + return string.Format("{0}{1}:{2}:{3}", key, parameters, custHeaders, mediaType); + else + return string.Format("{0}{1}:{2}", key, parameters, mediaType); } protected virtual string MakeBaseKey(HttpActionContext context) @@ -57,7 +61,34 @@ protected virtual string FormatParameters(HttpActionContext context, bool exclud if (parameters == "-") parameters = string.Empty; return parameters; } + protected virtual string GetCustomHeaders(Dictionary> headers) + { + //string returnValue = string.Empty; + + if (!(headers == null)) + { + //foreach(var item in headers) + //{ + // if (!(returnValue.Length > 0)) + // { + // //first group and needs to not include the ; + // returnValue = item.ToString(); + + // } + // else + // { + // returnValue = string.Format("{0}|{1}={2}", returnValue, item.Key, item.Value); + // } + //} + return string.Join("&", headers.Select(x => x.Key.ToLower() + "=" + GetValue(x.Value))); + } + else + { + return string.Empty; + } + //return returnValue; + } private string GetJsonpCallback(HttpRequestMessage request) { var callback = string.Empty; diff --git a/src/WebApi.OutputCache.V2/ICacheKeyGenerator.cs b/src/WebApi.OutputCache.V2/ICacheKeyGenerator.cs index 5f8ff38..6f496ac 100644 --- a/src/WebApi.OutputCache.V2/ICacheKeyGenerator.cs +++ b/src/WebApi.OutputCache.V2/ICacheKeyGenerator.cs @@ -1,10 +1,11 @@ -using System.Net.Http.Headers; +using System.Collections.Generic; +using System.Net.Http.Headers; using System.Web.Http.Controllers; namespace WebApi.OutputCache.V2 { public interface ICacheKeyGenerator { - string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString = false); + string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString, Dictionary> headers); } } diff --git a/src/WebApi.OutputCache.V2/PerUserCacheKeyGenerator.cs b/src/WebApi.OutputCache.V2/PerUserCacheKeyGenerator.cs index 6f60ef7..16e67a1 100644 --- a/src/WebApi.OutputCache.V2/PerUserCacheKeyGenerator.cs +++ b/src/WebApi.OutputCache.V2/PerUserCacheKeyGenerator.cs @@ -1,17 +1,22 @@ -using System.Net.Http.Headers; +using System.Collections.Generic; +using System.Net.Http.Headers; using System.Web.Http.Controllers; namespace WebApi.OutputCache.V2 { public class PerUserCacheKeyGenerator : DefaultCacheKeyGenerator { - public override string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString = false) + public override string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString, Dictionary> headers) { var baseKey = MakeBaseKey(context); var parameters = FormatParameters(context, excludeQueryString); var userIdentity = FormatUserIdentity(context); + string custHeaders = GetCustomHeaders(headers); - return string.Format("{0}{1}:{2}:{3}", baseKey, parameters, userIdentity, mediaType); + if (!(string.IsNullOrEmpty(custHeaders))) + return string.Format("{0}{1}:{2}:{3}:{4}", baseKey, parameters, custHeaders, userIdentity, mediaType); + else + return string.Format("{0}{1}:{2}:{3}", baseKey, parameters, userIdentity, mediaType); } protected virtual string FormatUserIdentity(HttpActionContext context) diff --git a/test/WebApi.OutputCache.V2.Tests/CacheKeyGenerationTestsBase.cs b/test/WebApi.OutputCache.V2.Tests/CacheKeyGenerationTestsBase.cs index 9b1e6ed..d4f50a5 100644 --- a/test/WebApi.OutputCache.V2.Tests/CacheKeyGenerationTestsBase.cs +++ b/test/WebApi.OutputCache.V2.Tests/CacheKeyGenerationTestsBase.cs @@ -1,5 +1,6 @@ using NUnit.Framework; using System; +using System.Collections.Generic; using System.Net.Http.Headers; using System.Web.Http.Controllers; @@ -14,6 +15,7 @@ public abstract class CacheKeyGenerationTestsBase where TCac private const string ArgumentValue = "val"; protected HttpActionContext context; protected MediaTypeHeaderValue mediaType; + protected Dictionary> headers; protected Uri requestUri; protected TCacheKeyGenerator cacheKeyGenerator; protected string BaseCacheKey; diff --git a/test/WebApi.OutputCache.V2.Tests/CacheKeyGeneratorRegistrationTests.cs b/test/WebApi.OutputCache.V2.Tests/CacheKeyGeneratorRegistrationTests.cs index 46d2ae9..391d298 100644 --- a/test/WebApi.OutputCache.V2.Tests/CacheKeyGeneratorRegistrationTests.cs +++ b/test/WebApi.OutputCache.V2.Tests/CacheKeyGeneratorRegistrationTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; using System.Threading; @@ -104,6 +105,12 @@ public void custom_unregistered_cache_key_generator_called() #region Helper classes private class FailCacheKeyGenerator : ICacheKeyGenerator { + public string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString, Dictionary> headers) + { + Assert.Fail("This cache key generator should never be invoked"); + return "fail"; + } + public string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString = false) { Assert.Fail("This cache key generator should never be invoked"); @@ -120,6 +127,11 @@ public InternalRegisteredCacheKeyGenerator(string key) _key = key; } + public string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString, Dictionary> headers) + { + return _key; + } + public string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString = false) { return _key; diff --git a/test/WebApi.OutputCache.V2.Tests/CacheKeyGeneratorTests.cs b/test/WebApi.OutputCache.V2.Tests/CacheKeyGeneratorTests.cs index ffdfcab..bb69118 100644 --- a/test/WebApi.OutputCache.V2.Tests/CacheKeyGeneratorTests.cs +++ b/test/WebApi.OutputCache.V2.Tests/CacheKeyGeneratorTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; using System.Threading; @@ -17,6 +18,11 @@ class CacheKeyGeneratorTests { public class CustomCacheKeyGenerator : ICacheKeyGenerator { + public string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString, Dictionary> headers) + { + return "custom_key"; + } + public string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString = false) { return "custom_key"; @@ -59,7 +65,7 @@ public void init() public void custom_default_cache_key_generator_called_and_key_used() { var client = new HttpClient(_server); - _keyGeneratorA.Setup(k => k.MakeCacheKey(It.IsAny(), It.IsAny(), It.IsAny())) + _keyGeneratorA.Setup(k => k.MakeCacheKey(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>())) .Returns("keykeykey") .Verifiable("Key generator was never called"); // use the samplecontroller to show that no changes are required to existing code diff --git a/test/WebApi.OutputCache.V2.Tests/DefaultCacheKeyGeneratorTests.cs b/test/WebApi.OutputCache.V2.Tests/DefaultCacheKeyGeneratorTests.cs index 4b7ef6c..51d2b68 100644 --- a/test/WebApi.OutputCache.V2.Tests/DefaultCacheKeyGeneratorTests.cs +++ b/test/WebApi.OutputCache.V2.Tests/DefaultCacheKeyGeneratorTests.cs @@ -14,7 +14,7 @@ protected override DefaultCacheKeyGenerator BuildCacheKeyGenerator() [Test] public void NoParametersIncludeQueryString_ShouldReturnBaseKeyAndQueryStringAndMediaTypeConcatenated() { - var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, false); + var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, false, headers); AssertCacheKeysBasicFormat(cacheKey); Assert.AreEqual(String.Format("{0}-{1}:{2}", BaseCacheKey, requestUri.Query.Substring(1), mediaType), cacheKey, @@ -24,7 +24,7 @@ public void NoParametersIncludeQueryString_ShouldReturnBaseKeyAndQueryStringAndM [Test] public void NoParametersExcludeQueryString_ShouldReturnBaseKeyAndMediaTypeConcatenated() { - var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, true); + var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, true, headers); AssertCacheKeysBasicFormat(cacheKey); Assert.AreEqual(String.Format("{0}:{1}", BaseCacheKey, mediaType), cacheKey, @@ -35,7 +35,7 @@ public void NoParametersExcludeQueryString_ShouldReturnBaseKeyAndMediaTypeConcat public void WithParametersIncludeQueryString_ShouldReturnBaseKeyAndArgumentsAndQueryStringAndMediaTypeConcatenated() { AddActionArgumentsToContext(); - var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, false); + var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, false, headers); AssertCacheKeysBasicFormat(cacheKey); Assert.AreEqual(String.Format("{0}-{1}&{2}:{3}", BaseCacheKey, FormatActionArgumentsForKeyAssertion(), requestUri.Query.Substring(1), mediaType), cacheKey, @@ -46,7 +46,7 @@ public void WithParametersIncludeQueryString_ShouldReturnBaseKeyAndArgumentsAndQ public void WithParametersExcludeQueryString_ShouldReturnBaseKeyAndArgumentsAndMediaTypeConcatenated() { AddActionArgumentsToContext(); - var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, true); + var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, true, headers); AssertCacheKeysBasicFormat(cacheKey); Assert.AreEqual(String.Format("{0}-{1}:{2}", BaseCacheKey, FormatActionArgumentsForKeyAssertion(), mediaType), cacheKey, diff --git a/test/WebApi.OutputCache.V2.Tests/PerUserCacheKeyGeneratorTests.cs b/test/WebApi.OutputCache.V2.Tests/PerUserCacheKeyGeneratorTests.cs index 6ad660c..0baf25c 100644 --- a/test/WebApi.OutputCache.V2.Tests/PerUserCacheKeyGeneratorTests.cs +++ b/test/WebApi.OutputCache.V2.Tests/PerUserCacheKeyGeneratorTests.cs @@ -29,7 +29,7 @@ private string FormatUserIdentityForAssertion() [Test] public void NoParametersIncludeQueryString_ShouldReturnBaseKeyAndQueryStringAndUserIdentityAndMediaTypeConcatenated() { - var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, false); + var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, false, headers); AssertCacheKeysBasicFormat(cacheKey); Assert.AreEqual(String.Format("{0}-{1}:{2}:{3}", BaseCacheKey, requestUri.Query.Substring(1), FormatUserIdentityForAssertion(), mediaType), cacheKey, @@ -39,7 +39,7 @@ public void NoParametersIncludeQueryString_ShouldReturnBaseKeyAndQueryStringAndU [Test] public void NoParametersExcludeQueryString_ShouldReturnBaseKeyAndUserIdentityAndMediaTypeConcatenated() { - var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, true); + var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, true, headers); AssertCacheKeysBasicFormat(cacheKey); Assert.AreEqual(String.Format("{0}:{1}:{2}", BaseCacheKey, FormatUserIdentityForAssertion(), mediaType), cacheKey, @@ -50,7 +50,7 @@ public void NoParametersExcludeQueryString_ShouldReturnBaseKeyAndUserIdentityAnd public void WithParametersIncludeQueryString_ShouldReturnBaseKeyAndArgumentsAndQueryStringAndUserIdentityAndMediaTypeConcatenated() { AddActionArgumentsToContext(); - var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, false); + var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, false, headers); AssertCacheKeysBasicFormat(cacheKey); Assert.AreEqual(String.Format("{0}-{1}&{2}:{3}:{4}", BaseCacheKey, FormatActionArgumentsForKeyAssertion(), requestUri.Query.Substring(1), FormatUserIdentityForAssertion(), mediaType), cacheKey, @@ -61,7 +61,7 @@ public void WithParametersIncludeQueryString_ShouldReturnBaseKeyAndArgumentsAndQ public void WithParametersExcludeQueryString_ShouldReturnBaseKeyAndArgumentsAndUserIdentityAndMediaTypeConcatenated() { AddActionArgumentsToContext(); - var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, true); + var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, true, headers); AssertCacheKeysBasicFormat(cacheKey); Assert.AreEqual(String.Format("{0}-{1}:{2}:{3}", BaseCacheKey, FormatActionArgumentsForKeyAssertion(), FormatUserIdentityForAssertion(), mediaType), cacheKey, diff --git a/test/WebApi.OutputCache.V2.Tests/TestControllers/CacheKeyController.cs b/test/WebApi.OutputCache.V2.Tests/TestControllers/CacheKeyController.cs index 3564ebb..96c62ea 100644 --- a/test/WebApi.OutputCache.V2.Tests/TestControllers/CacheKeyController.cs +++ b/test/WebApi.OutputCache.V2.Tests/TestControllers/CacheKeyController.cs @@ -1,4 +1,5 @@ -using System.Net.Http.Headers; +using System.Collections.Generic; +using System.Net.Http.Headers; using System.Web.Http; using System.Web.Http.Controllers; @@ -8,6 +9,11 @@ public class CacheKeyController : ApiController { private class UnregisteredCacheKeyGenerator : ICacheKeyGenerator { + public string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString, Dictionary> headers) + { + return "unregistered"; + } + public string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString = false) { return "unregistered";