MetalamaConceptual documentationUsing MetalamaConfiguring aspect libraries
Open sandboxFocusImprove this doc

Configuring aspects with fabrics

Certain aspect libraries may expose a compile-time, imperative configuration API that influences the behavior of aspects.

For instance, the Metalama.Patterns.Caching library has a ConfigureCaching method that allows changing various settings such as the expiration of cache items. Similarly, Metalama.Extensions.DependencyInjection provides a ConfigureDependencyInjection method for registering new frameworks.

Alternatively, an aspect library may expose its configuration API as a class implementing the IHierarchicalOptions interface.

The process to access these configuration APIs is similar to adding aspects in bulk using fabrics:

  1. Create a fabric class and derive it from one of the following: ProjectFabric, NamespaceFabric, TransitiveProjectFabric, or, less commonly, TypeFabric.

  2. Override the appropriate method: AmendProject, AmendNamespace, or AmendType.

  3. Call one of the following methods:

    • To select the current project, namespace, or type itself, simply use the amender.Outbound property.
    • To select child members (methods, fields, types, sub-namespaces, etc.), call the Select, SelectMany or Where method and provide a lambda expression that selects the relevant type members.
  4. The next step depends on the kind of configuration API exposed by the aspect library:

Example: configuring caching

The following example demonstrates how to configure caching. It sets the absolute expiration for the MyProduct.MyNamespace namespace.

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}

About the incremental nature of compile-time configuration

All compile-time configuration APIs are incremental. In other words, any call to a Configure or SetOptions method represents a change in the configuration. These changes are merged on a per-property basis.

For instance, let's say you configure caching at the project level, setting an absolute expiration of 30 minutes and a sliding expiration of 10 minutes. However, for a sub-namespace named ColdNs, you increase the absolute expiration to 60 minutes and leave the sliding expiration unchanged. As expected, the sliding expiration in ColdNs remains 10 minutes.

Interestingly, the order of configuration operations does not matter when they affect different declarations. Whether you first configure ColdNs and then the project, or perform these operations in the reverse order, the result is the same.

Therefore, the two following code snippets are equivalent:

amender.Outbound
  .ConfigureCaching( caching => {
        caching.SlidingExpiration = TimeSpan.FromMinutes( 10 );
        caching.AbsoluteExpiration = TimeSpan.FromMinutes( 30 );
    });

amender.Outbound
  .Select( x => x.GlobalNamespace.GetDescendant( "ColdNs" )! )
  .ConfigureCaching( caching => caching.AbsoluteExpiration = TimeSpan.FromMinutes( 60 ) );

And in reverse order:

amender.Outbound
  .Select( x => x.GlobalNamespace.GetDescendant( "ColdNs" )! )
  .ConfigureCaching( caching => caching.AbsoluteExpiration = TimeSpan.FromMinutes( 60 ) );

amender.Outbound
  .ConfigureCaching( caching => {
        caching.SlidingExpiration = TimeSpan.FromMinutes( 10 );
        caching.AbsoluteExpiration = TimeSpan.FromMinutes( 30 );
    });

The order of operations only matters when they are applied to the same declaration. In this case, the last operation always wins on a per-property basis.

Configuration with custom attributes

Some libraries may provide configuration custom attributes in addition to an imperative one.

It's important to note that both mechanisms -- fabrics and custom attributes -- are equivalent. Configuration custom attributes, when they exist, are simply easier ways to call the configuration API. The configuration properties they provide are merged with the ones provided by the fabrics.

Inheritance of configuration options

Unless specified otherwise by the aspect library, all configuration options are inherited from the base type or from the overridden member. However, implemented interfaces are not taken into account.

So, when you want to set some options for an entire class family, it's sufficient to set these options for the base class, and they will automatically apply to child classes.

Options inherited from the base class take precedence over the options that come from the enclosing type (including that of nested types), the enclosing namespace, or the project.