MetalamaConceptual documentationUsing Metalama PatternsCachingConfiguring the caching behavior
Open sandboxFocusImprove this doc

Configuring the caching service

The behavior of the CacheAttribute aspect can be configured in several ways, including:

  • Expiration (absolute and sliding)
  • Priority
  • Auto-reload
  • Enabled/disabled

These options can be set in three ways:

This article describes these three approaches.

Configuring caching with custom attributes

The CacheAttribute aspect can be configured by setting the properties of the CacheAttribute custom attribute. The downside of this approach is that you have to repeat the configuration for each cached method.

To configure several methods with a single line of code, add the CachingConfigurationAttribute custom attribute to the declaring type, the base type of the declaring type, the enclosing type of the declaring type (if it is nested), or the declaring assembly.

The configuration of Metalama Caching is based on the configuration framework of Metalama Framework. For more details, see Configuring aspects with fabrics.

Example: Configuring expiration with custom attributes

In the following example, the absolute expiration of cache items is set to 60 minutes for methods of the PricingService class, but to 20 minutes for the GetProducts method.

1using Metalama.Patterns.Caching.Aspects;
2using System;

3

4namespace Doc.AbsoluteExpiration_Attribute

5{
6    [CachingConfiguration( AbsoluteExpiration = 60 )]
7    public class PricingService
8    {
9        [Cache]
10        public decimal GetProductPrice( string productId ) => throw new NotImplementedException();
11
12        [Cache( AbsoluteExpiration = 20 )]


































13        public string[] GetProducts( string productId ) => throw new NotImplementedException();
14    }
15}

Configuring caching with fabrics

Instead of using custom attributes, you can use a fabric to configure the caching aspect at any level of granularity, thanks to the ConfigureCaching fabric extension method.

Example: Configuring expiration with a fabric

The following example sets the absolute expiration to 20 minutes for the namespace MyProduct.MyNamespace.

1using Metalama.Framework.Fabrics;
2using Metalama.Patterns.Caching.Aspects.Configuration;
3using System;
4
5namespace Doc.AbsoluteExpiration_Fabric
6{
7    public class Fabric : ProjectFabric
8    {



9        public override void AmendProject( IProjectAmender amender )
10        {
11            amender.Outbound.Select( x => x.GlobalNamespace.GetDescendant( "MyProduct.MyNamespace" )! )
12                .ConfigureCaching( caching => caching.AbsoluteExpiration = TimeSpan.FromMinutes( 20 ) );
13        }
14    }
15}

Configuring caching at run time with profiles

All compile-time approaches described above have the same drawback: by definition, they cannot be modified at run time. This can be a problem if you want the caching options to be loaded from a configuration file that you can deploy with your application.

Metalama Caching allows you to change caching options at run time with a concept called caching profile. These are sets of options that can be modified at run time and are represented by the CachingProfile class.

To modify run-time caching options with a caching profile:

  1. Set the ProfileName property of the [Cache] aspect, the [CachingConfiguration] attribute, or the CachingOptionsBuilder parameter of the ConfigureCaching fabric extension method.

    Note

    A default profile is assigned to any cached method even if the ProfileName property is not assigned. This profile can be customized at run time like any other profile.

  2. Go back to the code that initialized the Metalama Caching by calling serviceCollection.AddCaching or CachingService.Create, and supply a delegate that calls AddProfile.

Warning

Any configuration property specified through a compile-time mechanism takes precedence over the configuration of the CachingProfile.

Example: Configuring expiration with a fabric

In this example, the ProductCatalogue class uses two caching profiles: the default one, and the hot one, which should refresh content more frequently. However, we don't want to hard-code these expiration values in the source code. The following code in Program.cs configures the logging profiles. While the values are hard-coded here, they could easily be read from a configuration file.

1using Metalama.Documentation.Helpers.ConsoleApp;
2using System;
3using System.Threading;
4
5namespace Doc.Profiles
6{
7    public sealed class ConsoleMain : IConsoleMain
8    {
9        private readonly ProductCatalogue _catalogue;
10
11        public ConsoleMain( ProductCatalogue catalogue )
12        {
13            this._catalogue = catalogue;
14        }
15
16        public void Execute()
17        {
18            for ( var i = 0; i < 5; i++ )
19            {
20                Console.WriteLine( "Printing all prices..." );
21
22                var products = this._catalogue.GetProducts();
23
24                foreach ( var product in products )
25                {
26                    var price = this._catalogue.GetPrice( product );
27                    Console.WriteLine( $"Price of '{product}' is {price}." );
28
29                    Thread.Sleep( 150 );
30                }
31            }
32
33            Console.WriteLine( $"ProductCatalogue performed {this._catalogue.OperationCount} operations in total." );
34        }
35    }
36}
1using Metalama.Documentation.Helpers.ConsoleApp;
2using Metalama.Patterns.Caching;
3using Metalama.Patterns.Caching.Building;
4using Microsoft.Extensions.DependencyInjection;
5using System;
6
7namespace Doc.Profiles
8{
9    public static class Program
10    {
11        public static void Main()
12        {
13            var builder = ConsoleApp.CreateBuilder();
14
15            // Add the caching service.
16            builder.Services.AddCaching(
17                caching => caching
18                    .AddProfile( new CachingProfile { AbsoluteExpiration = TimeSpan.FromMinutes( 60 ) } )
19                    .AddProfile( new CachingProfile( "Hot" ) { AbsoluteExpiration = TimeSpan.FromMilliseconds( 100 ) } ) );
20
21            // Add other components as usual.
22            builder.Services.AddConsoleMain<ConsoleMain>();
23            builder.Services.AddSingleton<ProductCatalogue>();
24
25            // Run the main service.
26            using var app = builder.Build();
27            app.Run();
28        }
29    }
30}
Source Code
1using Metalama.Patterns.Caching.Aspects;
2using System;

3

4namespace Doc.Profiles

5{
6    public sealed class ProductCatalogue
7    {
8        public int OperationCount { get; private set; }
9
10        [Cache( ProfileName = "Hot" )]











11        public decimal GetPrice( string productId )
12        {
13            Console.WriteLine( "Getting the price from database." );
14            this.OperationCount++;
15
16            return 100 + this.OperationCount;
17        }
18
19        [Cache]
20        public string[] GetProducts()
21        {
22            Console.WriteLine( "Getting the product list from database." );
23










24            this.OperationCount++;
25
26            return new[] { "corn" };
27        }
28    }

















29}
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.Profiles
8{
9    public sealed class ProductCatalogue
10    {
11        public int OperationCount { get; private set; }
12
13        [Cache(ProfileName = "Hot")]
14        public decimal GetPrice(string productId)
15        {
16            static object? Invoke(object? instance, object?[] args)
17            {
18                return ((ProductCatalogue)instance).GetPrice_Source((string)args[0]);
19            }
20
21            return _cachingService!.GetFromCacheOrExecute<decimal>(_cacheRegistration_GetPrice!, this, new object[] { productId }, Invoke);
22        }
23
24        private decimal GetPrice_Source(string productId)
25        {
26            Console.WriteLine("Getting the price from database.");
27            this.OperationCount++;

28
29            return 100 + this.OperationCount;
30        }
31
32        [Cache]
33        public string[] GetProducts()
34        {
35            static object? Invoke(object? instance, object?[] args)
36            {
37                return ((ProductCatalogue)instance).GetProducts_Source();
38            }
39
40            return _cachingService!.GetFromCacheOrExecute<string[]>(_cacheRegistration_GetProducts!, this, new object[] { }, Invoke);
41        }
42
43        private string[] GetProducts_Source()
44        {
45            Console.WriteLine("Getting the product list from database.");
46
47            this.OperationCount++;
48
49            return new[] { "corn" };
50        }
51
52        private static readonly CachedMethodMetadata _cacheRegistration_GetPrice;
53        private static readonly CachedMethodMetadata _cacheRegistration_GetProducts;
54        private ICachingService _cachingService;
55
56        static ProductCatalogue
57        ()
58        {
59            ProductCatalogue._cacheRegistration_GetPrice = CachedMethodMetadata.Register(RunTimeHelpers.ThrowIfMissing(typeof(ProductCatalogue).GetMethod("GetPrice", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string) }, null)!, "ProductCatalogue.GetPrice(string)"), new CachedMethodConfiguration() { AbsoluteExpiration = null, AutoReload = null, IgnoreThisParameter = null, Priority = null, ProfileName = "Hot", SlidingExpiration = null }, false);
60            ProductCatalogue._cacheRegistration_GetProducts = CachedMethodMetadata.Register(RunTimeHelpers.ThrowIfMissing(typeof(ProductCatalogue).GetMethod("GetProducts", BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null)!, "ProductCatalogue.GetProducts()"), new CachedMethodConfiguration() { AbsoluteExpiration = null, AutoReload = null, IgnoreThisParameter = null, Priority = null, ProfileName = (string?)null, SlidingExpiration = null }, true);
61        }
62
63        public ProductCatalogue
64        (ICachingService? cachingService = default)
65        {
66            this._cachingService = cachingService ?? throw new System.ArgumentNullException(nameof(cachingService));
67        }
68    }
69}
Printing all prices...
Getting the product list from database.
Getting the price from database.
Price of 'corn' is 102.
Printing all prices...
Getting the price from database.
Price of 'corn' is 103.
Printing all prices...
Getting the price from database.
Price of 'corn' is 104.
Printing all prices...
Getting the price from database.
Price of 'corn' is 105.
Printing all prices...
Getting the price from database.
Price of 'corn' is 106.
ProductCatalogue performed 6 operations in total.