Caching with Attributes in .Net Core 5

You had a performance problem, and you solved it by implementing caching:

1
var people = PersonRepository.GetByLastName(personId);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class PersonRepository 
{
private const secondsToCacheResult = 300;

public async Task<IEnumerable<Person>> GetByLastName(string lastName)
{
var cacheKey = buildCacheKey(lastName);
if (_cache.ContainsKey(cacheKey))
{
return _cache[cacheKey] as Person;
}

// This part takes a long time, that's why we
// want to cache the results
var people = SomeLongRunningProcess(lastName);

var options = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = new System.TimeSpan(
hours: 0, minutes: 0, seconds: secondsToCacheResult)
};
_cache.Add(cacheKey, people, options)

return people;
}
}

And it worked! The performance problem is solved. But then you start caching things in several places in your repositories, and you end up with this exact same caching code everywhere. Not only are you violating the DRY principle, but it makes it much harder to understand what’s really going on in the repository method.

This new problem can be solved by using Aspect Oriented Programming in a Dependency Injection framework by using interception with attributes to make your code look like this instead:

1
2
3
4
5
[Cache(Seconds = 30)]
public async Task<IEnumerable<Person>> GetPeopleByLastName(string lastName)
{
return SomeLongRunningProcess(lastName);
}

We could use DI to solve this with Microsoft’s Unity DI, or with AutoFac. Or we could use PostSharp, which ‘weaves’ the IL of your code with the code for caching at compile time. But I wanted to get this working with Microsoft’s DI framework that comes with .Net Core 5.

But there’s a problem - Microsoft’s DI doesn’t natively support interceptors, so we have to bring in some extra help. In this case, we’re going to use the Castle Project’s DynamicProxy library.

Read More