Note
Starting with Metalama 2023.4, this approach is considered obsolete.
To establish a configuration API prior to Metalama 2023.4:
- Construct a class that inherits from ProjectExtension and includes a default constructor.
- If necessary, override the Initialize method, which accepts the IProject.
- In your aspect code, invoke the IProject.Extension<T>() method, where
T
represents your configuration class, to acquire the configuration object. - If desired, devise an extension method for the IProject type to make your configuration API more discoverable. The class must be annotated with
[CompileTime]
. - For users to configure your aspect, they should implement a project fabric and access your configuration API using this extension method.
Example
1using Metalama.Framework.Aspects;
2using Metalama.Framework.Code;
3using System.Diagnostics;
4
5namespace Doc.AspectConfiguration;
6
7// The aspect itself, consuming the configuration.
8public class LogAttribute : OverrideMethodAspect
9{
10 public override dynamic? OverrideMethod()
11 {
12 var options = meta.Target.Method.Enhancements().GetOptions<LoggingOptions>();
13
14 var message = $"{options.Category}: Executing {meta.Target.Method}.";
15
16 switch ( options.Level!.Value )
17 {
18 case TraceLevel.Error:
19 Trace.TraceError( message );
20
21 break;
22
23 case TraceLevel.Info:
24 Trace.TraceInformation( message );
25
26 break;
27
28 case TraceLevel.Warning:
29 Trace.TraceWarning( message );
30
31 break;
32
33 case TraceLevel.Verbose:
34 Trace.WriteLine( message );
35
36 break;
37 }
38
39 return meta.Proceed();
40 }
41}
1using Metalama.Framework.Fabrics;
2using System.Diagnostics;
3using System.Linq;
4
5namespace Doc.AspectConfiguration;
6
7// The project fabric configures the project at compile time.
8public class Fabric : ProjectFabric
9{
10 public override void AmendProject( IProjectAmender amender )
11 {
12 amender.SetOptions(
13 new LoggingOptions { Category = "GeneralCategory", Level = TraceLevel.Info } );
14
15 amender
16 .Select(
17 x => x.GlobalNamespace.GetDescendant( "Doc.AspectConfiguration.ChildNamespace" )! )
18 .SetOptions( new LoggingOptions() { Category = "ChildCategory" } );
19
20 // Adds the aspect to all members.
21 amender
22 .SelectMany( c => c.Types.SelectMany( t => t.Methods ) )
23 .AddAspectIfEligible<LogAttribute>();
24 }
25}
1using Metalama.Framework.Code;
2using Metalama.Framework.Options;
3using System.Diagnostics;
4
5namespace Doc.AspectConfiguration;
6
7// Options for the [Log] aspects.
8public class LoggingOptions : IHierarchicalOptions<IMethod>, IHierarchicalOptions<INamedType>,
9 IHierarchicalOptions<INamespace>, IHierarchicalOptions<ICompilation>
10{
11 public string? Category { get; init; }
12
13 public TraceLevel? Level { get; init; }
14
15 object IIncrementalObject.ApplyChanges( object changes, in ApplyChangesContext context )
16 {
17 var other = (LoggingOptions) changes;
18
19 return new LoggingOptions
20 {
21 Category = other.Category ?? this.Category, Level = other.Level ?? this.Level
22 };
23 }
24}
Source Code
1namespace Doc.AspectConfiguration
2{
3 // Some target code.
4 public class SomeClass
5 {
6 [Log]
7 public void SomeMethod() { }
8 }
9
10 namespace ChildNamespace
11 {
12 public class SomeOtherClass
13 {
14 [Log]
15 public void SomeMethod() { }
16 }
17 }
18}
Transformed Code
1using System.Diagnostics;
2
3namespace Doc.AspectConfiguration
4{
5 // Some target code.
6 public class SomeClass
7 {
8 [Log]
9 public void SomeMethod()
10 {
11 Trace.TraceInformation("GeneralCategory: Executing SomeClass.SomeMethod().");
12 }
13 }
14
15 namespace ChildNamespace
16 {
17 public class SomeOtherClass
18 {
19 [Log]
20 public void SomeMethod()
21 {
22 Trace.TraceInformation("ChildCategory: Executing SomeOtherClass.SomeMethod().");
23 }
24 }
25 }
26}