Skip to content

Commit 19c4789

Browse files
authored
Merge pull request filipw#203 from dmalanij/CacheKeyEnhancements
Cache Key generation enhancements
2 parents 0ce4629 + 2cc68dd commit 19c4789

File tree

8 files changed

+256
-6
lines changed

8 files changed

+256
-6
lines changed

src/WebApi.OutputCache.V2/DefaultCacheKeyGenerator.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,31 @@
11
using System.Collections;
22
using System.Collections.Generic;
3-
using System.Globalization;
43
using System.Linq;
54
using System.Net.Http;
65
using System.Net.Http.Headers;
7-
using System.Text;
86
using System.Web.Http.Controllers;
97

108
namespace WebApi.OutputCache.V2
119
{
1210
public class DefaultCacheKeyGenerator : ICacheKeyGenerator
1311
{
1412
public virtual string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString = false)
13+
{
14+
var key = MakeBaseKey(context);
15+
var parameters = FormatParameters(context, excludeQueryString);
16+
17+
return string.Format("{0}{1}:{2}", key, parameters, mediaType);
18+
}
19+
20+
protected virtual string MakeBaseKey(HttpActionContext context)
1521
{
1622
var controller = context.ControllerContext.ControllerDescriptor.ControllerType.FullName;
1723
var action = context.ActionDescriptor.ActionName;
18-
var key = context.Request.GetConfiguration().CacheOutputConfiguration().MakeBaseCachekey(controller, action);
24+
return context.Request.GetConfiguration().CacheOutputConfiguration().MakeBaseCachekey(controller, action);
25+
}
26+
27+
protected virtual string FormatParameters(HttpActionContext context, bool excludeQueryString)
28+
{
1929
var actionParameters = context.ActionArguments.Where(x => x.Value != null).Select(x => x.Key + "=" + GetValue(x.Value));
2030

2131
string parameters;
@@ -45,9 +55,7 @@ public virtual string MakeCacheKey(HttpActionContext context, MediaTypeHeaderVal
4555
}
4656

4757
if (parameters == "-") parameters = string.Empty;
48-
49-
var cachekey = string.Format("{0}{1}:{2}", key, parameters, mediaType);
50-
return cachekey;
58+
return parameters;
5159
}
5260

5361
private string GetJsonpCallback(HttpRequestMessage request)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System.Net.Http.Headers;
2+
using System.Web.Http.Controllers;
3+
4+
namespace WebApi.OutputCache.V2
5+
{
6+
public class PerUserCacheKeyGenerator : DefaultCacheKeyGenerator
7+
{
8+
public override string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString = false)
9+
{
10+
var baseKey = MakeBaseKey(context);
11+
var parameters = FormatParameters(context, excludeQueryString);
12+
var userIdentity = FormatUserIdentity(context);
13+
14+
return string.Format("{0}{1}:{2}:{3}", baseKey, parameters, userIdentity, mediaType);
15+
}
16+
17+
protected virtual string FormatUserIdentity(HttpActionContext context)
18+
{
19+
return context.RequestContext.Principal.Identity.Name.ToLower();
20+
}
21+
}
22+
}

src/WebApi.OutputCache.V2/WebApi.OutputCache.V2.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
<Compile Include="ICacheKeyGenerator.cs" />
6464
<Compile Include="IgnoreCacheOutputAttribute.cs" />
6565
<Compile Include="InvalidateCacheOutputAttribute.cs" />
66+
<Compile Include="PerUserCacheKeyGenerator.cs" />
6667
<Compile Include="Properties\AssemblyInfo.cs" />
6768
<Compile Include="CacheOutputAttribute.cs" />
6869
<Compile Include="TimeAttributes\CacheOutputUntilCacheAttribute.cs" />
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using NUnit.Framework;
2+
using System;
3+
using System.Net.Http.Headers;
4+
using System.Web.Http.Controllers;
5+
6+
namespace WebApi.OutputCache.V2.Tests
7+
{
8+
/// <summary>
9+
/// Base class for implementing tests for the generation of cache keys (meaning: implementations of the <see cref="ICacheKeyGenerator"/>
10+
/// </summary>
11+
public abstract class CacheKeyGenerationTestsBase<TCacheKeyGenerator> where TCacheKeyGenerator : ICacheKeyGenerator
12+
{
13+
private const string ArgumentKey = "filterExpression";
14+
private const string ArgumentValue = "val";
15+
protected HttpActionContext context;
16+
protected MediaTypeHeaderValue mediaType;
17+
protected Uri requestUri;
18+
protected TCacheKeyGenerator cacheKeyGenerator;
19+
protected string BaseCacheKey;
20+
21+
[SetUp]
22+
public virtual void Setup()
23+
{
24+
requestUri = new Uri("http://localhost:8080/cacheKeyGeneration?filter=val");
25+
var controllerType = typeof(TestControllers.CacheKeyGenerationController);
26+
var actionMethodInfo = controllerType.GetMethod("Get");
27+
var controllerDescriptor = new HttpControllerDescriptor() { ControllerType = controllerType };
28+
var actionDescriptor = new ReflectedHttpActionDescriptor(controllerDescriptor, actionMethodInfo);
29+
var request = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, requestUri.AbsoluteUri);
30+
31+
context = new HttpActionContext(
32+
new HttpControllerContext() { ControllerDescriptor = controllerDescriptor, Request = request },
33+
actionDescriptor
34+
);
35+
mediaType = new MediaTypeHeaderValue("application/json");
36+
37+
BaseCacheKey = new CacheOutputConfiguration(null).MakeBaseCachekey((TestControllers.CacheKeyGenerationController c) => c.Get(String.Empty));
38+
cacheKeyGenerator = BuildCacheKeyGenerator();
39+
}
40+
41+
protected abstract TCacheKeyGenerator BuildCacheKeyGenerator();
42+
43+
protected virtual void AssertCacheKeysBasicFormat(string cacheKey)
44+
{
45+
Assert.IsNotNull(cacheKey);
46+
StringAssert.StartsWith(BaseCacheKey, cacheKey, "Key does not start with BaseKey");
47+
StringAssert.EndsWith(mediaType.ToString(), cacheKey, "Key does not end with MediaType");
48+
}
49+
50+
protected void AddActionArgumentsToContext()
51+
{
52+
context.ActionArguments.Add(ArgumentKey, ArgumentValue);
53+
}
54+
55+
protected string FormatActionArgumentsForKeyAssertion()
56+
{
57+
return String.Format("{0}={1}", ArgumentKey, ArgumentValue);
58+
}
59+
}
60+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using NUnit.Framework;
2+
using System;
3+
4+
namespace WebApi.OutputCache.V2.Tests
5+
{
6+
[TestFixture]
7+
public class DefaultCacheKeyGeneratorTests : CacheKeyGenerationTestsBase<DefaultCacheKeyGenerator>
8+
{
9+
protected override DefaultCacheKeyGenerator BuildCacheKeyGenerator()
10+
{
11+
return new DefaultCacheKeyGenerator();
12+
}
13+
14+
[Test]
15+
public void NoParametersIncludeQueryString_ShouldReturnBaseKeyAndQueryStringAndMediaTypeConcatenated()
16+
{
17+
var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, false);
18+
19+
AssertCacheKeysBasicFormat(cacheKey);
20+
Assert.AreEqual(String.Format("{0}-{1}:{2}", BaseCacheKey, requestUri.Query.Substring(1), mediaType), cacheKey,
21+
"Key does not match expected <BaseKey>-<QueryString>:<MediaType>");
22+
}
23+
24+
[Test]
25+
public void NoParametersExcludeQueryString_ShouldReturnBaseKeyAndMediaTypeConcatenated()
26+
{
27+
var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, true);
28+
29+
AssertCacheKeysBasicFormat(cacheKey);
30+
Assert.AreEqual(String.Format("{0}:{1}", BaseCacheKey, mediaType), cacheKey,
31+
"Key does not match expected <BaseKey>:<MediaType>");
32+
}
33+
34+
[Test]
35+
public void WithParametersIncludeQueryString_ShouldReturnBaseKeyAndArgumentsAndQueryStringAndMediaTypeConcatenated()
36+
{
37+
AddActionArgumentsToContext();
38+
var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, false);
39+
40+
AssertCacheKeysBasicFormat(cacheKey);
41+
Assert.AreEqual(String.Format("{0}-{1}&{2}:{3}", BaseCacheKey, FormatActionArgumentsForKeyAssertion(), requestUri.Query.Substring(1), mediaType), cacheKey,
42+
"Key does not match expected <BaseKey>-<Arguments>&<QueryString>:<MediaType>");
43+
}
44+
45+
[Test]
46+
public void WithParametersExcludeQueryString_ShouldReturnBaseKeyAndArgumentsAndMediaTypeConcatenated()
47+
{
48+
AddActionArgumentsToContext();
49+
var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, true);
50+
51+
AssertCacheKeysBasicFormat(cacheKey);
52+
Assert.AreEqual(String.Format("{0}-{1}:{2}", BaseCacheKey, FormatActionArgumentsForKeyAssertion(), mediaType), cacheKey,
53+
"Key does not match expected <BaseKey>-<Arguments>:<MediaType>");
54+
}
55+
}
56+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using NUnit.Framework;
2+
using System;
3+
using System.Security.Principal;
4+
5+
namespace WebApi.OutputCache.V2.Tests
6+
{
7+
[TestFixture]
8+
public class PerUserCacheKeyGeneratorTests : CacheKeyGenerationTestsBase<PerUserCacheKeyGenerator>
9+
{
10+
private const string UserIdentityName = "SomeUserIDon'tMind";
11+
12+
[SetUp]
13+
public override void Setup()
14+
{
15+
base.Setup();
16+
context.RequestContext.Principal = new GenericPrincipal(new GenericIdentity(UserIdentityName), new string[0]);
17+
}
18+
19+
protected override PerUserCacheKeyGenerator BuildCacheKeyGenerator()
20+
{
21+
return new PerUserCacheKeyGenerator();
22+
}
23+
24+
private string FormatUserIdentityForAssertion()
25+
{
26+
return UserIdentityName.ToLower();
27+
}
28+
29+
[Test]
30+
public void NoParametersIncludeQueryString_ShouldReturnBaseKeyAndQueryStringAndUserIdentityAndMediaTypeConcatenated()
31+
{
32+
var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, false);
33+
34+
AssertCacheKeysBasicFormat(cacheKey);
35+
Assert.AreEqual(String.Format("{0}-{1}:{2}:{3}", BaseCacheKey, requestUri.Query.Substring(1), FormatUserIdentityForAssertion(), mediaType), cacheKey,
36+
"Key does not match expected <BaseKey>-<QueryString>:<UserIdentity>:<MediaType>");
37+
}
38+
39+
[Test]
40+
public void NoParametersExcludeQueryString_ShouldReturnBaseKeyAndUserIdentityAndMediaTypeConcatenated()
41+
{
42+
var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, true);
43+
44+
AssertCacheKeysBasicFormat(cacheKey);
45+
Assert.AreEqual(String.Format("{0}:{1}:{2}", BaseCacheKey, FormatUserIdentityForAssertion(), mediaType), cacheKey,
46+
"Key does not match expected <BaseKey>:<UserIdentity>:<MediaType>");
47+
}
48+
49+
[Test]
50+
public void WithParametersIncludeQueryString_ShouldReturnBaseKeyAndArgumentsAndQueryStringAndUserIdentityAndMediaTypeConcatenated()
51+
{
52+
AddActionArgumentsToContext();
53+
var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, false);
54+
55+
AssertCacheKeysBasicFormat(cacheKey);
56+
Assert.AreEqual(String.Format("{0}-{1}&{2}:{3}:{4}", BaseCacheKey, FormatActionArgumentsForKeyAssertion(), requestUri.Query.Substring(1), FormatUserIdentityForAssertion(), mediaType), cacheKey,
57+
"Key does not match expected <BaseKey>-<Arguments>&<QueryString>:<UserIdentity>:<MediaType>");
58+
}
59+
60+
[Test]
61+
public void WithParametersExcludeQueryString_ShouldReturnBaseKeyAndArgumentsAndUserIdentityAndMediaTypeConcatenated()
62+
{
63+
AddActionArgumentsToContext();
64+
var cacheKey = cacheKeyGenerator.MakeCacheKey(context, mediaType, true);
65+
66+
AssertCacheKeysBasicFormat(cacheKey);
67+
Assert.AreEqual(String.Format("{0}-{1}:{2}:{3}", BaseCacheKey, FormatActionArgumentsForKeyAssertion(), FormatUserIdentityForAssertion(), mediaType), cacheKey,
68+
"Key does not match expected <BaseKey>-<Arguments>:<UserIdentity>:<MediaType>");
69+
}
70+
}
71+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Web.Http;
5+
6+
namespace WebApi.OutputCache.V2.Tests.TestControllers
7+
{
8+
/// <summary>
9+
/// Controller needed for generating the <see cref="System.Web.Http.Controllers.HttpActionContext" /> needed for testing the <see cref="ICacheKeyGenerator"/> implementations
10+
/// </summary>
11+
[RoutePrefix("cacheKeyGeneration")]
12+
public class CacheKeyGenerationController : ApiController
13+
{
14+
private readonly string[] Values = new string[] { "first", "second", "third" };
15+
16+
[Route("")]
17+
public IEnumerable<string> Get([FromUri(Name="filter")]string filterExpression)
18+
{
19+
return String.IsNullOrWhiteSpace(filterExpression) ? Values : Values.Where(x => x.Contains(filterExpression));
20+
}
21+
22+
[Route("{index}")]
23+
public string GetByIndex(int index)
24+
{
25+
return Values[index];
26+
}
27+
}
28+
}

test/WebApi.OutputCache.V2.Tests/WebApi.OutputCache.V2.Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
<ItemGroup>
7575
<Compile Include="CacheKeyGeneratorRegistrationTests.cs" />
7676
<Compile Include="CacheKeyGeneratorTests.cs" />
77+
<Compile Include="CacheKeyGenerationTestsBase.cs" />
7778
<Compile Include="ClientSideTests.cs">
7879
<SubType>Code</SubType>
7980
</Compile>
@@ -83,6 +84,8 @@
8384
<Compile Include="ConnegTests.cs">
8485
<SubType>Code</SubType>
8586
</Compile>
87+
<Compile Include="PerUserCacheKeyGeneratorTests.cs" />
88+
<Compile Include="DefaultCacheKeyGeneratorTests.cs" />
8689
<Compile Include="InlineInvalidateTests.cs">
8790
<SubType>Code</SubType>
8891
</Compile>
@@ -95,6 +98,7 @@
9598
<Compile Include="TestControllers\AutoInvalidateController.cs" />
9699
<Compile Include="TestControllers\AutoInvalidateWithTypeController.cs" />
97100
<Compile Include="TestControllers\CacheKeyController.cs" />
101+
<Compile Include="TestControllers\CacheKeyGenerationController.cs" />
98102
<Compile Include="TestControllers\IgnoreController.cs" />
99103
<Compile Include="TestControllers\InlineInvalidateController.cs" />
100104
<Compile Include="Properties\AssemblyInfo.cs" />

0 commit comments

Comments
 (0)