Exposing configuration (before v2023.4)
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 Metalama.Framework.Options;
4using System.Diagnostics;
5
6namespace Doc.AspectConfiguration
7{
8 // The aspect itself, consuming the configuration.
9 public class LogAttribute : OverrideMethodAspect
10 {
11 public override dynamic? OverrideMethod()
12 {
13 var options = meta.Target.Method.Enhancements().GetOptions<LoggingOptions>();
14
15 var message = $"{options.Category}: Executing {meta.Target.Method}.";
16
17 switch ( options.Level!.Value )
18 {
19 case TraceLevel.Error:
20 Trace.TraceError( message );
21
22 break;
23
24 case TraceLevel.Info:
25 Trace.TraceInformation( message );
26
27 break;
28
29 case TraceLevel.Warning:
30 Trace.TraceWarning( message );
31
32 break;
33
34 case TraceLevel.Verbose:
35 Trace.WriteLine( message );
36
37 break;
38 }
39
40 return meta.Proceed();
41 }
42 }
43}
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.
8 public class Fabric : ProjectFabric
9 {
10 public override void AmendProject( IProjectAmender amender )
11 {
12 amender.Outbound
13 .SetOptions( new LoggingOptions { Category = "GeneralCategory", Level = TraceLevel.Info } );
14
15 amender.Outbound
16 .Select( x => x.GlobalNamespace.GetDescendant( "Doc.AspectConfiguration.ChildNamespace" )! )
17 .SetOptions( new LoggingOptions() { Category = "ChildCategory" } );
18
19 // Adds the aspect to all members.
20 amender.Outbound
21 .SelectMany( c => c.Types.SelectMany( t => t.Methods ) )
22 .AddAspectIfEligible<LogAttribute>();
23 }
24 }
25}
1using Metalama.Framework.Code;
2using Metalama.Framework.Diagnostics;
3using Metalama.Framework.Options;
4using Metalama.Framework.Project;
5using System;
6using System.Diagnostics;
7
8namespace Doc.AspectConfiguration
9{
10 // Options for the [Log] aspects.
11 public class LoggingOptions : IHierarchicalOptions<IMethod>, IHierarchicalOptions<INamedType>,
12 IHierarchicalOptions<INamespace>, IHierarchicalOptions<ICompilation>
13 {
14 public string? Category { get; init; }
15
16 public TraceLevel? Level { get; init; }
17
18 object IIncrementalObject.ApplyChanges( object changes, in ApplyChangesContext context )
19 {
20 var other = (LoggingOptions) changes;
21
22 return new LoggingOptions { Category = other.Category ?? this.Category, Level = other.Level ?? this.Level };
23 }
24 }
25}
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}
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}