Skip to content

Commit e771b62

Browse files
committed
Support MemoryCacheEntryOptions, fixes #6
Fix unit test
1 parent 906738c commit e771b62

File tree

14 files changed

+480
-46
lines changed

14 files changed

+480
-46
lines changed

src/SpatialFocus.MethodCache.Fody/Extensions/MethodDefinitionExtension.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,23 @@ public static bool IsEligibleForWeaving(this MethodDefinition methodDefinition,
7373

7474
return !isSpecialName && !hasCompilerGeneratedAttribute;
7575
}
76+
77+
public static CustomAttribute TryGetCacheAttribute(this MethodDefinition methodDefinition, References references)
78+
{
79+
if (methodDefinition == null)
80+
{
81+
throw new ArgumentNullException(nameof(methodDefinition));
82+
}
83+
84+
if (references == null)
85+
{
86+
throw new ArgumentNullException(nameof(references));
87+
}
88+
89+
TypeReference cacheAttributeType = references.CacheAttributeType.Resolve();
90+
91+
return methodDefinition.CustomAttributes.SingleOrDefault(classAttribute =>
92+
classAttribute.AttributeType.Resolve().Equals(cacheAttributeType));
93+
}
7694
}
7795
}

src/SpatialFocus.MethodCache.Fody/Extensions/TypeDefinitionExtension.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,23 @@ public static bool IsEligibleForWeaving(this TypeDefinition typeDefinition, Refe
6363
})
6464
.Count() == 1;
6565
}
66+
67+
public static CustomAttribute TryGetCacheAttribute(this TypeDefinition typeDefinition, References references)
68+
{
69+
if (typeDefinition == null)
70+
{
71+
throw new ArgumentNullException(nameof(typeDefinition));
72+
}
73+
74+
if (references == null)
75+
{
76+
throw new ArgumentNullException(nameof(references));
77+
}
78+
79+
TypeReference cacheAttributeType = references.CacheAttributeType.Resolve();
80+
81+
return typeDefinition.CustomAttributes.SingleOrDefault(classAttribute =>
82+
classAttribute.AttributeType.Resolve().Equals(cacheAttributeType));
83+
}
6684
}
6785
}

src/SpatialFocus.MethodCache.Fody/MemoryCache.cs

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,16 @@ public static void WeaveSetBeforeReturns(MethodWeavingContext methodWeavingConte
135135
throw new ArgumentNullException(nameof(methodWeavingContext));
136136
}
137137

138+
CustomAttribute cacheAttribute =
139+
methodWeavingContext.MethodDefinition.TryGetCacheAttribute(methodWeavingContext.ClassWeavingContext.References);
140+
141+
if (cacheAttribute == null)
142+
{
143+
cacheAttribute =
144+
methodWeavingContext.ClassWeavingContext.TypeDefinition.TryGetCacheAttribute(methodWeavingContext.ClassWeavingContext
145+
.References);
146+
}
147+
138148
MethodBody methodDefinitionBody = methodWeavingContext.MethodDefinition.Body;
139149

140150
List<Instruction> returns = methodDefinitionBody.Instructions.Where(x => x.OpCode == OpCodes.Ret).ToList();
@@ -149,10 +159,58 @@ public static void WeaveSetBeforeReturns(MethodWeavingContext methodWeavingConte
149159
processorContext = processorContext.Append(x => x.Create(OpCodes.Ldarg_0))
150160
.Append(x => x.Create(OpCodes.Call, methodWeavingContext.ClassWeavingContext.CacheGetterMethod))
151161
.Append(x => x.Create(OpCodes.Ldloc, methodWeavingContext.CacheKeyVariableIndex.Value))
152-
.Append(x => x.Create(OpCodes.Ldloc, methodWeavingContext.ResultVariableIndex.Value))
153-
.Append(x => x.Create(OpCodes.Call,
162+
.Append(x => x.Create(OpCodes.Ldloc, methodWeavingContext.ResultVariableIndex.Value));
163+
164+
if (cacheAttribute.Properties.Any())
165+
{
166+
processorContext = processorContext.Append(x => x.Create(OpCodes.Newobj,
167+
methodWeavingContext.ClassWeavingContext.References.MemoryCacheEntryOptionsConstructor));
168+
169+
foreach (CustomAttributeNamedArgument customAttributeNamedArgument in cacheAttribute.Properties)
170+
{
171+
switch (customAttributeNamedArgument.Name)
172+
{
173+
case "AbsoluteExpirationRelativeToNow":
174+
processorContext = processorContext.Append(x => x.Create(OpCodes.Dup))
175+
.Append(x => x.Create(OpCodes.Ldc_R8, (double)customAttributeNamedArgument.Argument.Value))
176+
.Append(x => x.Create(OpCodes.Call,
177+
methodWeavingContext.ClassWeavingContext.References.TimeSpanFromSecondsMethod))
178+
.Append(x => x.Create(OpCodes.Newobj,
179+
methodWeavingContext.ClassWeavingContext.References.NullableTimeSpanConstructor))
180+
.Append(x => x.Create(OpCodes.Callvirt,
181+
methodWeavingContext.ClassWeavingContext.References.MemoryCacheEntryOptionsAbsoluteExpirationRelativeToNowSetter));
182+
break;
183+
184+
case "SlidingExpiration":
185+
processorContext = processorContext.Append(x => x.Create(OpCodes.Dup))
186+
.Append(x => x.Create(OpCodes.Ldc_R8, (double)customAttributeNamedArgument.Argument.Value))
187+
.Append(x => x.Create(OpCodes.Call,
188+
methodWeavingContext.ClassWeavingContext.References.TimeSpanFromSecondsMethod))
189+
.Append(x => x.Create(OpCodes.Newobj,
190+
methodWeavingContext.ClassWeavingContext.References.NullableTimeSpanConstructor))
191+
.Append(x => x.Create(OpCodes.Callvirt,
192+
methodWeavingContext.ClassWeavingContext.References.MemoryCacheEntryOptionsSlidingExpirationSetter));
193+
break;
194+
195+
case "Priority":
196+
processorContext = processorContext.Append(x => x.Create(OpCodes.Dup))
197+
.Append(x => x.Create(OpCodes.Ldc_I4, (int)customAttributeNamedArgument.Argument.Value))
198+
.Append(x => x.Create(OpCodes.Callvirt,
199+
methodWeavingContext.ClassWeavingContext.References.MemoryCacheEntryOptionsPrioritySetter));
200+
break;
201+
}
202+
}
203+
204+
processorContext = processorContext.Append(x => x.Create(OpCodes.Call,
205+
methodWeavingContext.ClassWeavingContext.References.GetGenericSetMethodWithMemoryCacheEntryOptions(
206+
methodWeavingContext.MethodDefinition.ReturnType)));
207+
}
208+
else
209+
{
210+
processorContext = processorContext.Append(x => x.Create(OpCodes.Call,
154211
methodWeavingContext.ClassWeavingContext.References.GetGenericSetMethod(methodWeavingContext.MethodDefinition
155212
.ReturnType)));
213+
}
156214

157215
// Not necessary, just return the return value from .Set
158216
////.Append(x => x.Create(OpCodes.Pop))

src/SpatialFocus.MethodCache.Fody/References.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,30 @@ protected References(ModuleWeaver moduleWeaver)
2727

2828
public MethodReference GetTypeFromHandleMethod { get; protected set; }
2929

30+
public MethodReference MemoryCacheEntryOptionsAbsoluteExpirationRelativeToNowSetter { get; set; }
31+
32+
public MethodReference MemoryCacheEntryOptionsConstructor { get; set; }
33+
34+
public MethodReference MemoryCacheEntryOptionsPrioritySetter { get; set; }
35+
36+
public MethodReference MemoryCacheEntryOptionsSlidingExpirationSetter { get; set; }
37+
38+
public TypeReference MemoryCacheEntryOptionsType { get; set; }
39+
3040
public TypeReference MemoryCacheInterface { get; protected set; }
3141

3242
public TypeReference NoCacheAttributeType { get; set; }
3343

44+
public MethodReference NullableTimeSpanConstructor { get; set; }
45+
3446
public MethodReference SetMethod { get; protected set; }
3547

48+
public MethodReference SetMethodWithMemoryCacheEntryOptions { get; protected set; }
49+
50+
public MethodReference TimeSpanFromSecondsMethod { get; set; }
51+
52+
public TypeReference TimeSpanType { get; set; }
53+
3654
public MethodReference TryGetValueMethod { get; protected set; }
3755

3856
public TypeReference TypeType { get; protected set; }
@@ -63,19 +81,52 @@ public static References Init(ModuleWeaver moduleWeaver)
6381
references.GetTypeFromHandleMethod =
6482
moduleWeaver.ModuleDefinition.ImportReference(type.Methods.Single(x => x.Name == nameof(Type.GetTypeFromHandle)));
6583

84+
TypeDefinition timeSpanType = moduleWeaver.FindTypeDefinition(typeof(TimeSpan).FullName);
85+
references.TimeSpanType = moduleWeaver.ModuleDefinition.ImportReference(timeSpanType);
86+
references.TimeSpanFromSecondsMethod =
87+
moduleWeaver.ModuleDefinition.ImportReference(timeSpanType.Methods.Single(x => x.Name == nameof(TimeSpan.FromSeconds)));
88+
89+
TypeDefinition nullableType = moduleWeaver.FindTypeDefinition(typeof(Nullable<>).FullName);
90+
references.NullableTimeSpanConstructor =
91+
moduleWeaver.ModuleDefinition.ImportReference(nullableType.GetConstructors()
92+
.Single()
93+
.MakeHostInstanceGeneric(references.TimeSpanType));
94+
6695
TypeDefinition memoryCacheInterface = moduleWeaver.FindTypeDefinition("Microsoft.Extensions.Caching.Memory.IMemoryCache");
6796
references.MemoryCacheInterface = moduleWeaver.ModuleDefinition.ImportReference(memoryCacheInterface);
6897

6998
TypeDefinition cacheExtensions = moduleWeaver.FindTypeDefinition("Microsoft.Extensions.Caching.Memory.CacheExtensions");
7099
references.CacheExtensionsType = moduleWeaver.ModuleDefinition.ImportReference(cacheExtensions);
71100

101+
TypeDefinition memoryCacheEntryOptions =
102+
moduleWeaver.FindTypeDefinition("Microsoft.Extensions.Caching.Memory.MemoryCacheEntryOptions");
103+
references.MemoryCacheEntryOptionsType = moduleWeaver.ModuleDefinition.ImportReference(memoryCacheEntryOptions);
104+
references.MemoryCacheEntryOptionsConstructor =
105+
moduleWeaver.ModuleDefinition.ImportReference(memoryCacheEntryOptions.GetConstructors().Single(x => !x.Parameters.Any()));
106+
references.MemoryCacheEntryOptionsAbsoluteExpirationRelativeToNowSetter =
107+
moduleWeaver.ModuleDefinition.ImportReference(memoryCacheEntryOptions.Properties
108+
.Single(x => x.Name == "AbsoluteExpirationRelativeToNow")
109+
.SetMethod);
110+
references.MemoryCacheEntryOptionsSlidingExpirationSetter =
111+
moduleWeaver.ModuleDefinition.ImportReference(memoryCacheEntryOptions.Properties.Single(x => x.Name == "SlidingExpiration")
112+
.SetMethod);
113+
references.MemoryCacheEntryOptionsPrioritySetter =
114+
moduleWeaver.ModuleDefinition.ImportReference(
115+
memoryCacheEntryOptions.Properties.Single(x => x.Name == "Priority").SetMethod);
116+
72117
references.TryGetValueMethod =
73118
moduleWeaver.ModuleDefinition.ImportReference(cacheExtensions.Methods.Single(x => x.Name == "TryGetValue"));
74119

75120
references.SetMethod =
76121
moduleWeaver.ModuleDefinition.ImportReference(cacheExtensions.Methods.Single(x =>
77122
x.Name == "Set" && x.HasParameters && x.Parameters.Count == 3));
78123

124+
references.SetMethodWithMemoryCacheEntryOptions = moduleWeaver.ModuleDefinition.ImportReference(
125+
cacheExtensions.Methods.Single(x =>
126+
x.Name == "Set" && x.HasParameters && x.Parameters.Count == 4 &&
127+
moduleWeaver.ModuleDefinition.ImportReference(x.Parameters.Last().ParameterType).Resolve() ==
128+
references.MemoryCacheEntryOptionsType.Resolve()));
129+
79130
return references;
80131
}
81132

@@ -99,6 +150,9 @@ public TypeReference GetFragmentedSystemTupleType(ICollection<TypeReference> typ
99150
public MethodReference GetGenericSetMethod(TypeReference type) =>
100151
ModuleWeaver.ModuleDefinition.ImportReference(SetMethod.MakeGeneric(type));
101152

153+
public MethodReference GetGenericSetMethodWithMemoryCacheEntryOptions(TypeReference type) =>
154+
ModuleWeaver.ModuleDefinition.ImportReference(SetMethodWithMemoryCacheEntryOptions.MakeGeneric(type));
155+
102156
public MethodReference GetSystemTupleConstructor(params TypeReference[] types)
103157
{
104158
if (types.Length > 8)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// <copyright file="MemoryCacheEntryOptionsClassLevelTestClass.cs" company="Spatial Focus GmbH">
2+
// Copyright (c) Spatial Focus GmbH. All rights reserved.
3+
// </copyright>
4+
5+
namespace SpatialFocus.MethodCache.TestAssembly
6+
{
7+
using Microsoft.Extensions.Caching.Memory;
8+
9+
[Cache(AbsoluteExpirationRelativeToNow = 1)]
10+
public class MemoryCacheEntryOptionsClassLevelTestClass
11+
{
12+
public MemoryCacheEntryOptionsClassLevelTestClass(IMemoryCache memoryCache)
13+
{
14+
MemoryCache = memoryCache;
15+
}
16+
17+
public IMemoryCache MemoryCache { get; }
18+
19+
#pragma warning disable CA1822 // Mark members as static
20+
#pragma warning disable IDE0060 // Remove unused parameter
21+
[Cache]
22+
public int WithMethodLevelNoOption(int a)
23+
{
24+
return a;
25+
}
26+
27+
public int WithClassLevelOption(int a)
28+
{
29+
return a;
30+
}
31+
32+
[Cache(SlidingExpiration = 2)]
33+
public int WithMethodLevelOption(int a)
34+
{
35+
return a;
36+
}
37+
#pragma warning restore CA1822 // Mark members as static
38+
#pragma warning restore IDE0060 // Remove unused parameter
39+
}
40+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// <copyright file="MemoryCacheEntryOptionsTestClass.cs" company="Spatial Focus GmbH">
2+
// Copyright (c) Spatial Focus GmbH. All rights reserved.
3+
// </copyright>
4+
5+
namespace SpatialFocus.MethodCache.TestAssembly
6+
{
7+
using Microsoft.Extensions.Caching.Memory;
8+
9+
public class MemoryCacheEntryOptionsTestClass
10+
{
11+
public MemoryCacheEntryOptionsTestClass(IMemoryCache memoryCache)
12+
{
13+
MemoryCache = memoryCache;
14+
}
15+
16+
public IMemoryCache MemoryCache { get; }
17+
18+
#pragma warning disable CA1822 // Mark members as static
19+
#pragma warning disable IDE0060 // Remove unused parameter
20+
[Cache]
21+
public int WithNoOption(int a)
22+
{
23+
return a;
24+
}
25+
26+
[Cache(AbsoluteExpirationRelativeToNow = 1)]
27+
public int WithAbsoluteExpirationRelativeToNowOption(int a)
28+
{
29+
return a;
30+
}
31+
32+
[Cache(SlidingExpiration = 1)]
33+
public int WithSlidingExpirationOption(int a)
34+
{
35+
return a;
36+
}
37+
38+
[Cache(Priority = CacheItemPriority.High)]
39+
public int WithPriorityOption(int a)
40+
{
41+
return a;
42+
}
43+
44+
[Cache(AbsoluteExpirationRelativeToNow = 1, SlidingExpiration = 2, Priority = CacheItemPriority.High)]
45+
public int WithAllOptions(int a)
46+
{
47+
return a;
48+
}
49+
#pragma warning restore CA1822 // Mark members as static
50+
#pragma warning restore IDE0060 // Remove unused parameter
51+
}
52+
}

src/SpatialFocus.MethodCache.Tests/MemoryCacheBasicTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ static MemoryCacheBasicTests()
2626
[Fact]
2727
public void BasicTest1CreateAndGet()
2828
{
29-
using MockMemoryCache mockMemoryCache = new MockMemoryCache();
29+
using MockMemoryCache mockMemoryCache = MockMemoryCache.Default;
3030

3131
dynamic instance = TestHelpers.CreateInstance<BasicTestClass>(MemoryCacheBasicTests.TestResult.Assembly, mockMemoryCache);
3232

@@ -40,7 +40,7 @@ public void BasicTest1CreateAndGet()
4040
[Fact]
4141
public void BasicTest2CreateAndGet2()
4242
{
43-
using MockMemoryCache mockMemoryCache = new MockMemoryCache();
43+
using MockMemoryCache mockMemoryCache = MockMemoryCache.Default;
4444

4545
dynamic instance = TestHelpers.CreateInstance<BasicTestClass>(MemoryCacheBasicTests.TestResult.Assembly, mockMemoryCache);
4646

@@ -55,7 +55,7 @@ public void BasicTest2CreateAndGet2()
5555
[Fact]
5656
public void BasicTest3Create2AndGet2()
5757
{
58-
using MockMemoryCache mockMemoryCache = new MockMemoryCache();
58+
using MockMemoryCache mockMemoryCache = MockMemoryCache.Default;
5959

6060
dynamic instance = TestHelpers.CreateInstance<BasicTestClass>(MemoryCacheBasicTests.TestResult.Assembly, mockMemoryCache);
6161

@@ -71,7 +71,7 @@ public void BasicTest3Create2AndGet2()
7171
[Fact]
7272
public void BasicTest4NoCache()
7373
{
74-
using MockMemoryCache mockMemoryCache = new MockMemoryCache();
74+
using MockMemoryCache mockMemoryCache = MockMemoryCache.Default;
7575

7676
dynamic instance = TestHelpers.CreateInstance<BasicTestClass>(MemoryCacheBasicTests.TestResult.Assembly, mockMemoryCache);
7777

0 commit comments

Comments
 (0)