MetalamaConceptual documentationUsing Metalama PatternsCachingExcluding parameters
Open sandboxFocusImprove this doc

Excluding parameters from the cache key

Some methods may have parameters that do not need to be part of the cache. A typical example is the <xref:System.Threading.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:

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 )]
7    public 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    }
19}

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{
7    public class PricingService
8    {
9        [Cache]
10        public decimal GetProductPrice( string productId, [NotCacheKey] string? correlationId ) => throw new NotImplementedException();
11
12        [Cache]








13        public string[] GetProducts( string productId, [NotCacheKey] string? correlationId ) => throw new NotImplementedException();


14    }
15}

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:

  1. 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.
  2. 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{
6    internal class Fabric : ProjectFabric
7    {
8        public override void AmendProject( IProjectAmender amender )
9            => amender.Outbound.ConfigureCaching( caching => caching.AddParameterClassifier( "ILogger", new LoggerParameterClassifier() ) );
10    }
11}
1using Metalama.Framework.Code;
2using Metalama.Patterns.Caching.Aspects.Configuration;
3using Microsoft.Extensions.Logging;
4
5namespace Doc.ParameterFilter
6{
7    public class LoggerParameterClassifier : ICacheParameterClassifier
8    {
9        public CacheParameterClassification GetClassification( IParameter parameter )
10            => parameter.Type.Is( typeof(ILogger) )
11                ? CacheParameterClassification.ExcludeFromCacheKey
12                : CacheParameterClassification.Default;
13    }
14}
Source Code
1using Metalama.Patterns.Caching;
2using Metalama.Patterns.Caching.Aspects;
3using System;
4

5namespace Doc.ParameterFilter

6{
7    public class PricingService
8    {
9        [Cache]
10        public decimal GetProductPrice( string productId, [NotCacheKey] string? correlationId ) => throw new NotImplementedException();
11
12        [Cache]








13        public string[] GetProducts( string productId, [NotCacheKey] string? correlationId ) => throw new NotImplementedException();


14    }
15}
Transformed Code
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{
9    public class PricingService
10    {
11        [Cache]
12        public decimal GetProductPrice(string productId, [NotCacheKey] string? correlationId)
13        {
14            static object? Invoke(object? instance, object?[] args)
15            {
16                return ((PricingService)instance).GetProductPrice_Source((string)args[0], (string?)args[1]);
17            }
18
19            return _cachingService!.GetFromCacheOrExecute<decimal>(_cacheRegistration_GetProductPrice!, this, new object[] { productId, correlationId }, Invoke);
20        }
21
22        private decimal GetProductPrice_Source(string productId, string? correlationId) => throw new NotImplementedException();
23
24        [Cache]
25        public string[] GetProducts(string productId, [NotCacheKey] string? correlationId)
26        {
27            static object? Invoke(object? instance, object?[] args)
28            {
29                return ((PricingService)instance).GetProducts_Source((string)args[0], (string?)args[1]);
30            }
31
32            return _cachingService!.GetFromCacheOrExecute<string[]>(_cacheRegistration_GetProducts!, this, new object[] { productId, correlationId }, Invoke);
33        }
34
35        private string[] GetProducts_Source(string productId, string? correlationId) => throw new NotImplementedException();
36
37        private static readonly CachedMethodMetadata _cacheRegistration_GetProductPrice;
38        private static readonly CachedMethodMetadata _cacheRegistration_GetProducts;
39        private ICachingService _cachingService;
40
41        static PricingService
42        ()
43        {
44            PricingService._cacheRegistration_GetProductPrice = CachedMethodMetadata.Register(RunTimeHelpers.ThrowIfMissing(typeof(PricingService).GetMethod("GetProductPrice", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string), typeof(string) }, null)!, "PricingService.GetProductPrice(string, string?)"), new CachedMethodConfiguration() { AbsoluteExpiration = null, AutoReload = null, IgnoreThisParameter = null, Priority = null, ProfileName = (string?)null, SlidingExpiration = null }, false);
45            PricingService._cacheRegistration_GetProducts = CachedMethodMetadata.Register(RunTimeHelpers.ThrowIfMissing(typeof(PricingService).GetMethod("GetProducts", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string), typeof(string) }, null)!, "PricingService.GetProducts(string, string?)"), new CachedMethodConfiguration() { AbsoluteExpiration = null, AutoReload = null, IgnoreThisParameter = null, Priority = null, ProfileName = (string?)null, SlidingExpiration = null }, true);
46        }
47
48        public PricingService
49        (ICachingService? cachingService = default)
50        {
51            this._cachingService = cachingService ?? throw new System.ArgumentNullException(nameof(cachingService));
52        }
53    }
54}