Note
If you don't plan to create your own aspects but just use existing ones, start with Using Metalama.
1. Add Metalama to Your Project
Add the Metalama.Framework package to your project.
Note
If your project targets the .NET Framework or .NET Standard, you may also need to add PolySharp, which updates the language version even if it's officially unsupported.
Optionally, install Visual Studio Tools for Metalama and PostSharp. This extension offers the following features:
- AspectDiff: Displays a side-by-side comparison of source code with the generated code.
- CodeLens: Displays which aspects are applied to your code.
- Syntax highlighting of aspects: This is particularly useful when you are getting started.
2. Create an Aspect Class
Let's start with logging, the traditional Hello, world example of aspect-oriented programming.
Type the following code:
1using Metalama.Framework.Aspects;
2using System;
3
4namespace Doc.GettingStarted;
5
6public class LogAttribute : OverrideMethodAspect
7{
8 public override dynamic? OverrideMethod()
9 {
10 Console.WriteLine( $"Entering {meta.Target.Method}" );
11
12 try
13 {
14 return meta.Proceed();
15 }
16 finally
17 {
18 Console.WriteLine( $"Leaving {meta.Target.Method}" );
19 }
20 }
21}
As you can infer from its name, the LogAttribute
class is a custom attribute. You can think of an aspect as a template. When you apply it to some code (in this case, to a method), it transforms it. Indeed, the code of the target method will be replaced by the implementation of OverrideMethod
. This method is very special. Some parts execute at run time, while others, which typically start with the meta
keyword, execute at compile time. If you installed Visual Studio Tools for Metalama and PostSharp, you will notice that compile-part segments are displayed with a different background color.
Let's examine two meta
expressions:
meta.Proceed()
is replaced by the code of the target method.meta.Target.Method
gives you access to the IMethod code model. In this case, we are implicitly callingToString()
.
4. Apply the custom attribute to a method
Remember that an aspect is a template and that it doesn't do anything until it's applied to some target code.
So, let's add the [Log]
attribute to some method:
1using System;
2
3namespace Doc.GettingStarted;
4
5internal class Foo
6{
7 [Log]
8 public void Method1()
9 {
10 Console.WriteLine( "Hello, world." );
11 }
12}
Now, if you execute the method, the following output is printed:
Entering Foo.Method1()
Hello, world.
Leaving Foo.Method1()
5. See what happened to your code
You can see that Metalama did not modify anything in your source code. It's still yours. Instead, Metalama applied the logging aspect during compilation. So, it's no longer your source code that's being executed, but your source code enhanced by the logging aspect.
If you installed Visual Studio Tools for Metalama and PostSharp, you can compare your source code with the transformed (executed) code using the "Diff preview" feature accessible from the source file context menu in Visual Studio.
It will show you something like this:
1using System;
2
3namespace Doc.GettingStarted;
4
5internal class Foo
6{
7 [Log]
8 public void Method1()
9 {
10 Console.WriteLine( "Hello, world." );
11 }
12}
1using System;
2
3namespace Doc.GettingStarted;
4
5internal class Foo
6{
7 [Log]
8 public void Method1()
9 {
10 Console.WriteLine("Entering Foo.Method1()");
11 try
12 {
13 Console.WriteLine("Hello, world.");
14 return;
15 }
16 finally
17 {
18 Console.WriteLine("Leaving Foo.Method1()");
19 }
20 }
21}
6. Add aspects in bulk using fabrics
With aspects like logging, it's frequently applied to a large number of methods. It would be cumbersome to add a custom attribute to each of them. Instead, let's see how we can add the aspect programmatically using fabrics.
Use the following code:
1using Metalama.Framework.Code;
2using Metalama.Framework.Fabrics;
3
4namespace Doc.GettingStarted_Fabric;
5
6public class Fabric : ProjectFabric
7{
8 public override void AmendProject( IProjectAmender amender )
9 {
10 amender
11 .SelectMany( compilation => compilation.AllTypes )
12 .Where( type => type.Accessibility is Accessibility.Public )
13 .SelectMany( type => type.Methods )
14 .Where( method => method.Accessibility is Accessibility.Public )
15 .AddAspectIfEligible<LogAttribute>();
16 }
17}
This class derives from ProjectFabric and acts as a compile-time entry point for the project. As you can see, it adds the logging aspect to all public methods of all public types.
6. Add architecture validation
Now that you know about aspects and fabrics, it's easy to understand how to validate your codebase against some architectural rules. In this example, we will show how to report a warning when internals of a namespace are used outside of this namespace.
First, reference the Metalama package from your project.
Then, add a fabric with the validation logic. We can use a ProjectFabric as above:
1using Metalama.Extensions.Architecture;
2using Metalama.Extensions.Architecture.Predicates;
3using Metalama.Framework.Fabrics;
4
5namespace Doc.GettingStarted_Architecture;
6
7internal class Fabric : ProjectFabric
8{
9 public override void AmendProject( IProjectAmender amender )
10 {
11 const string ns = "Doc.GettingStarted_Architecture.VerifiedNamespace";
12
13 amender
14 .Select( compilation => compilation.GlobalNamespace.GetDescendant( ns )! )
15 .CanOnlyBeUsedFrom( r => r.Namespace( ns ) );
16 }
17}
Alternatively, we can achieve the same with a NamespaceFabric, which acts within the scope of their namespace instead of their project:
1using Metalama.Extensions.Architecture;
2using Metalama.Extensions.Architecture.Predicates;
3using Metalama.Framework.Fabrics;
4
5namespace Doc.GettingStarted_Architecture_Ns
6{
7 namespace VerifiedNamespace
8 {
9 internal class Fabric : NamespaceFabric
10 {
11 public override void AmendNamespace( INamespaceAmender amender )
12 {
13 amender
14 .CanOnlyBeUsedFrom( r => r.CurrentNamespace() );
15 }
16 }
17 }
18}
Fabrics not only run at compile time, but also at design time within the IDE. After the first build, or after you click on the I am done with compile-time changes link if you have installed Metaslama Tools for Visual Studio, you will see warnings in the IDE if your code violates the rule.
In this case, when we try to access any class of VerifiedNamespace
from a different namespace, we get a warning:
1using Doc.GettingStarted_Architecture.VerifiedNamespace;
2
3namespace Doc.GettingStarted_Architecture
4{
5 namespace VerifiedNamespace
6 {
7 internal class Foo { }
8
9 internal class AllowedInheritor : Foo { }
10 }
11
12 namespace OtherNamespace
13 {
Warning LAMA0905: The 'Doc.GettingStarted_Architecture.VerifiedNamespace' namespace cannot be referenced by the 'Doc.GettingStarted_Architecture.OtherNamespace' namespace.
14 internal class ForbiddenInheritor : Foo { }
15 }
16}
Conclusion
Congratulations! In this short tutorial, you have discovered two key concepts of Metalama: aspects and fabrics. You have learned how to transparently add behaviors to your code during compilation, and add validation rules that get enforced in real time in the editor.
There are three paths you can take from here according to your learning style: