The previous article discussed how to add multiple aspects at once using compile-time imperative code instead of declarative custom attributes. It introduced a single type of fabric: ProjectFabric. However, there are several other types of fabrics and many use cases for them.
Even if you don't plan to create your own aspects, understanding fabrics enhances your proficiency with Metalama.
Fabrics are special classes in your code that execute at compile time within the compiler and at design time within your IDE. Unlike aspects, fabrics don't need to be applied to any declaration or called from anywhere. Their primary method is invoked at the appropriate time simply because it exists in your code. Think of fabrics as compile-time entry points.
With fabrics, you can:
- Add aspects programmatically using LINQ-like code queries, instead of marking individual declarations with custom attributes. See Adding many aspects simultaneously.
- Configure aspect libraries. See Configuring aspects with fabrics.
- Implement architecture rules in your code. See Verifying architecture.
In addition to ProjectFabric, there are three more types of fabric:
| Fabric Type | Abstract Class | Purpose |
|---|---|---|
| Project Fabrics | ProjectFabric | Add aspects, architecture rules, or configure aspect libraries in the current project. |
| Transitive Project Fabrics | TransitiveProjectFabric | Add aspects, architecture rules, or configure aspect libraries in projects that reference the current project. |
| Namespace Fabric | NamespaceFabric | Add aspects or architecture rules to the namespace that contains the fabric type. |
| Type Fabric | TypeFabric | Add aspects to different members of the type that contains the nested fabric type. |
Abilities of fabrics
1. Adding aspects programmatically
All fabric types can add aspects to declarations using LINQ-like queries. This is the most common use case for fabrics.
For details, see Adding many aspects simultaneously.
2. Configuring aspect libraries
All fabric types can set options that configure how aspect libraries behave. This lets you customize logging formats, caching policies, and other aspect-specific settings. Configuration scope depends on the fabric type.
For details, see Configuring aspects with fabrics.
3. Reporting and suppressing diagnostics
Fabrics can report custom diagnostics (errors, warnings, or information messages) and suppress diagnostics reported by the compiler or other analyzers.
For details, see Reporting and suppressing diagnostics.
4. Validating architecture
Project fabrics, transitive project fabrics, and namespace fabrics can register architecture validators that enforce coding standards and architectural rules across your codebase.
For details, see Verifying architecture.
5. Adding advice to a type (Type Fabric only)
Type fabrics have a unique ability: they can directly add advice (such as method overrides or member introductions) to their containing type without requiring a separate aspect. This makes type fabrics function like embedded aspects.
For details, see Advising a single type with a fabric.
Fabrics vs aspects
Aspects and fabrics serve different purposes:
Aspects are reusable APIs that must be applied to target declarations. They encapsulate behavior that can be applied to many declarations, shared across projects, and distributed as NuGet packages. Aspects are the building blocks of aspect-oriented programming.
Fabrics are compile-time entry points automatically called by the framework. They don't need to be applied to anything. Fabrics are consumers, not APIs: they're project-specific entry points that use aspects and configure them. Fabrics aren't reusable across projects in the same way aspects are.
To create reusable logic for fabrics, define extension methods operating on IAmender<T> or one of its derived interfaces. These extension methods can then be called from any fabric.
| Use Case | Recommended Approach |
|---|---|
| Apply behavior to specific declarations marked with an attribute | Use an aspect as a custom attribute |
| Apply behavior to many declarations based on a pattern | Use a fabric to add aspects programmatically |
| Create reusable transformation logic | Create an aspect |
| Create reusable logic for adding aspects, configuring, or validating | Create an extension method on IAmender<T> |
| Configure aspect library settings for a project | Use a project fabric |
| Enforce architecture rules across a project | Use a project or namespace fabric |
| Apply policies to all projects referencing a library | Use a transitive project fabric |
| Add advice to a single type without creating a reusable aspect | Use a type fabric |