Some methods may have parameters that do not need to be part of the cache. A typical example is the CancellationToken type, which is automatically skipped. Another example could be a request correlation ID. Often, the current instance (this
) represents a service instance and should also be skipped.
This article presents mechanisms to exclude any parameter from the cache key.
Ignoring the this
parameter
To exclude the this
parameter, consider one of the following approaches:
- Set the IgnoreThisParameter parameter of the [Cache] aspect to
true
. - Add a [CacheConfiguration] attribute to the type or assembly and set its IgnoreThisParameter to
true
. - Using a fabric, call the ConfigureCaching method and set the IgnoreThisParameter to
true
.
For more details on configuration, see Configuring the caching service.
Example: ignoring the this
parameter
In the following example, the PricingService
class exposes two instance methods. Both methods are cached. The PricingService
class has a unique id
field, and its ToString
implementation includes this field because it is useful for troubleshooting. However, we want several instances of the PricingService
to reuse the cached results. Therefore, we exclude the this
instance from the cache key. Since this decision must apply to all cached methods of this type, we apply the [CacheConfiguration] to the type.
1using Metalama.Patterns.Caching.Aspects;
2using System;
3
4namespace Doc.ExcludeThisParameter;
5
6[CachingConfiguration( IgnoreThisParameter = true )]
7public class PricingService
8{
9 private readonly Guid _id = Guid.NewGuid();
10
11 [Cache]
12 public decimal GetProductPrice( string productId ) => throw new NotImplementedException();
13
14 [Cache]
15 public string[] GetProducts( string productId ) => throw new NotImplementedException();
16
17 public override string ToString() => $"CurrencyService {this._id}";
18}
Excluding parameters using [NotCacheKey]
To exclude a parameter other than the current instance (this
), simply add the NotCacheKeyAttribute custom attribute to this parameter.
Example: Using [NotCacheKey]
In the following example, both methods of the PricingService
class have a correlationId
field. This field is used for troubleshooting; it has a unique value for each web API request and therefore must be excluded from the cache.
1using Metalama.Patterns.Caching;
2using Metalama.Patterns.Caching.Aspects;
3using System;
4
5namespace Doc.NotCacheKey;
6
7public class PricingService
8{
9 [Cache]
10 public decimal GetProductPrice( string productId, [NotCacheKey] string? correlationId )
11 => throw new NotImplementedException();
12
13 [Cache]
14 public string[] GetProducts( string productId, [NotCacheKey] string? correlationId )
15 => throw new NotImplementedException();
16}
Excluding parameters by rule using classifiers
The inconvenience of using the NotCacheKeyAttribute custom attribute is that it must be added to every single parameter. This can be cumbersome and subject to human errors when many parameters must be excluded according to the same rules.
In this case, it is preferable to implement and register a programmatic parameter classifier. Follow these steps:
- Create a class that implements the ICacheParameterClassifier interface. It has a single method, GetClassification, which receives a parameter and returns a value indicating whether the parameter should be excluded from the cache key.
- Using a fabric for the desired scope (typically the current project, a namespace, or all referencing projects), call the ConfigureCaching method and supply a delegate that calls the AddParameterClassifier method.
Warning
It may be tempting to classify parameters based on naming conventions, for instance to exclude all parameters named correlationId
, but this is dangerous because naming conventions are easily broken. Instead, it is preferable to use a fabric to report a warning when a method is cached and one of its parameter matches a naming pattern but is not annotated with the NotCacheKeyAttribute attribute.
Example: parameter classifier
The following example demonstrates a parameter classifier that prevents any parameter of type ILogger
from being included in a cache key. The classifier itself is implemented by the LoggerParameterClassifier
class. It is registered using a project fabric.
1using Metalama.Framework.Fabrics;
2using Metalama.Patterns.Caching.Aspects.Configuration;
3
4namespace Doc.ParameterFilter;
5
6internal class Fabric : ProjectFabric
7{
8 public override void AmendProject( IProjectAmender amender )
9 => amender.ConfigureCaching(
10 caching => caching.AddParameterClassifier(
11 "ILogger",
12 new LoggerParameterClassifier() ) );
13}
1using Metalama.Framework.Code;
2using Metalama.Patterns.Caching.Aspects.Configuration;
3using Microsoft.Extensions.Logging;
4
5namespace Doc.ParameterFilter;
6
7public class LoggerParameterClassifier : ICacheParameterClassifier
8{
9 public CacheParameterClassification GetClassification( IParameter parameter )
10 => parameter.Type.IsConvertibleTo( typeof(ILogger) )
11 ? CacheParameterClassification.ExcludeFromCacheKey
12 : CacheParameterClassification.Default;
13}
1using Metalama.Patterns.Caching;
2using Metalama.Patterns.Caching.Aspects;
3using System;
4
5namespace Doc.ParameterFilter;
6
7public class PricingService
8{
9 [Cache]
10 public decimal GetProductPrice( string productId, [NotCacheKey] string? correlationId )
11 => throw new NotImplementedException();
12
13 [Cache]
14 public string[] GetProducts( string productId, [NotCacheKey] string? correlationId )
15 => throw new NotImplementedException();
16}
1using Metalama.Patterns.Caching;
2using Metalama.Patterns.Caching.Aspects;
3using Metalama.Patterns.Caching.Aspects.Helpers;
4using System;
5using System.Reflection;
6
7namespace Doc.ParameterFilter;
8
9public class PricingService
10{
11 [Cache]
12 public decimal GetProductPrice(string productId, [NotCacheKey] string? correlationId)
13
14 {
15 static object? Invoke(object? instance, object?[] args)
16 {
17 return ((PricingService)instance).GetProductPrice_Source((string)args[0], (string?)args[1]);
18 }
19
20 return _cachingService.GetFromCacheOrExecute<decimal>(_cacheRegistration_GetProductPrice, this, new object[] { productId, correlationId }, Invoke);
21 }
22
23 private decimal GetProductPrice_Source(string productId, string? correlationId) => throw new NotImplementedException();
24
25 [Cache]
26 public string[] GetProducts(string productId, [NotCacheKey] string? correlationId)
27
28 {
29 static object? Invoke(object? instance, object?[] args)
30 {
31 return ((PricingService)instance).GetProducts_Source((string)args[0], (string?)args[1]);
32 }
33
34 return _cachingService.GetFromCacheOrExecute<string[]>(_cacheRegistration_GetProducts, this, new object[] { productId, correlationId }, Invoke);
35 }
36
37 private string[] GetProducts_Source(string productId, string? correlationId) => throw new NotImplementedException();
38
39 private static readonly CachedMethodMetadata _cacheRegistration_GetProductPrice;
40 private static readonly CachedMethodMetadata _cacheRegistration_GetProducts;
41 private ICachingService _cachingService;
42
43 static PricingService()
44 {
45 _cacheRegistration_GetProductPrice = CachedMethodMetadata.Register(typeof(PricingService).GetMethod("GetProductPrice", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string), typeof(string) }, null).ThrowIfMissing("PricingService.GetProductPrice(string, string?)"), new CachedMethodConfiguration() { AbsoluteExpiration = null, AutoReload = null, IgnoreThisParameter = null, Priority = null, ProfileName = (string?)null, SlidingExpiration = null }, false);
46 _cacheRegistration_GetProducts = CachedMethodMetadata.Register(typeof(PricingService).GetMethod("GetProducts", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string), typeof(string) }, null).ThrowIfMissing("PricingService.GetProducts(string, string?)"), new CachedMethodConfiguration() { AbsoluteExpiration = null, AutoReload = null, IgnoreThisParameter = null, Priority = null, ProfileName = (string?)null, SlidingExpiration = null }, true);
47 }
48
49 public PricingService(ICachingService? cachingService = null)
50 {
51 this._cachingService = cachingService ?? throw new System.ArgumentNullException(nameof(cachingService));
52 }
53}