Skip to content
m4bwav edited this page Oct 1, 2026 · 1 revision

Install

dotnet add package CachingServiceWithAOPSupport

or, in the project file:

<PackageReference Include="CachingServiceWithAOPSupport" Version="2.0.0" />

The package id is CachingServiceWithAOPSupport; the assembly and the namespaces are CachingServiceWithAOP, CachingServiceWithAOP.CachingServices and CachingServiceWithAOP.Extensions.

It depends on Autofac 6.5.0 or later, Autofac.Extras.DynamicProxy 7.1.0 or later, Castle.Core 5.1.1 or later and System.Runtime.Caching 10.0.12 or later. NuGet takes the lowest version that satisfies a range, so a project that references only this package gets exactly those versions. A project that already references a newer Autofac keeps it. Three sets were run for this wiki, on every runtime below:

Set Autofac Autofac.Extras.DynamicProxy Castle.Core
lowest (only this package referenced) 6.5.0 7.1.0 5.1.1
newest on 2026-09-30 9.3.4 8.1.0 5.2.1
Autofac 9 with the DynamicProxy floor (.NET 10 only) 9.3.4 7.1.0 5.1.1

This program prints the versions a project loaded:

using System;
using System.Reflection;
using Autofac;
using Castle.DynamicProxy;
using CachingServiceWithAOP;

var types = new[] { typeof(CacheAttribute), typeof(ContainerBuilder), typeof(Autofac.Extras.DynamicProxy.RegistrationExtensions), typeof(IInvocation) };
foreach (var type in types)
{
    var assembly = type.Assembly;
    var version = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion.Split('+')[0];
    Console.WriteLine(assembly.GetName().Name + " " + version);
}

With only this package referenced (.NET 10, .NET 8 and .NET Framework 4.8 alike):

CachingServiceWithAOP 2.0.0
Autofac 6.5.0
Autofac.Extras.DynamicProxy 7.1.0
Castle.Core 5.1.1

With Autofac 9.3.4, Autofac.Extras.DynamicProxy 8.1.0 and Castle.Core 5.2.1 referenced as well:

CachingServiceWithAOP 2.0.0
Autofac 9.3.4
Autofac.Extras.DynamicProxy 8.1.0
Castle.Core 5.2.1

The informational version is printed because Castle.Core keeps its assembly version at 5.0.0.0 for every 5.x release. Autofac versions between 6.5 and 9.3.4 other than those two ends were not run.

Which build each runtime loads

using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using CachingServiceWithAOP;

var assembly = typeof(CacheAttribute).Assembly;
Console.WriteLine(RuntimeInformation.FrameworkDescription);
Console.WriteLine(assembly.GetCustomAttribute<TargetFrameworkAttribute>()!.FrameworkName);

On .NET 10 (Windows):

.NET 10.0.12
.NETCoreApp,Version=v10.0

On .NET 8 (Windows):

.NET 8.0.23
.NETStandard,Version=v2.0

On .NET Framework 4.8 (Windows):

.NET Framework 4.8.9345.0
.NETStandard,Version=v2.0
Runtime Build Tested
.NET 10 lib/net10.0 Windows 11; Ubuntu 26.04 in WSL
.NET 8 lib/netstandard2.0 Windows 11
.NET Framework 4.8 lib/netstandard2.0 Windows 11
.NET Framework 4.6.2 to 4.7.2, .NET 9 lib/netstandard2.0 not tested
Mono, Unity, Xamarin not tested

On Linux every example on this wiki was run on .NET 10 with the three dependency sets, and printed exactly what it printed on Windows. That was Ubuntu 26.04 in WSL with SDK 10.0.401, in invariant globalization mode because the system has no ICU library. .NET 8 and .NET Framework were run on Windows only.

On .NET Framework the MemoryCache behind the package is the Framework's own System.Runtime.Caching (assembly 4.0.0.0, loaded from the GAC); on .NET 8 and .NET 10 it is the System.Runtime.Caching 10.0.12 package.

The package is not trim or AOT compatible: DynamicProxy emits its proxy types at run time.

The first cached call

Four things make a call cached:

  1. [Cache] on the method of the class. On the interface alone it does nothing (Errors and edge cases).
  2. builder.RegisterCachingModule(), which registers MemoryCacheService as ICacheService and the AOPCachingInterceptor.
  3. EnableCacheInterception() on the class's registration.
  4. Resolving the component as its interface and calling it there.

Home has the whole program. How results are cached says what the key is made of and how long an entry lives.

The cache without Autofac

MemoryCacheService works on its own, for values you name by key:

using System;
using CachingServiceWithAOP.CachingServices;

var cache = new MemoryCacheService(TimeSpan.FromMinutes(1));

string BuildReport()
{
    Console.WriteLine("building the report");
    return "42 rows";
}

Console.WriteLine(cache.Get("report", BuildReport));
Console.WriteLine(cache.Get("report", BuildReport));

On every runtime and dependency set above:

building the report
42 rows
42 rows

Other languages

Every example on this wiki is C#. F# and Visual Basic callers were not tested.

Clone this wiki locally