The Metalama.Framework.Workspaces API allows you to query and analyze source code programmatically from any .NET application. You can load projects or solutions, inspect declarations, analyze dependencies, and examine the results of Metalama transformations.
Step 1. Add the NuGet package
Add the Metalama.Framework.Workspaces package to your project:
<PackageReference Include="Metalama.Framework.Workspaces" Version="$(MetalamaVersion)" />
Step 2. Load a project or solution
Use Load to load a project or solution:
using Metalama.Framework.Workspaces;
var workspace = WorkspaceCollection.Default.Load( @"C:\src\MyProject\MyProject.csproj" );
You can also use Load directly:
var workspace = Workspace.Load( @"C:\src\MySolution.sln" );
Step 3. Query the code model
The Workspace object exposes the IProjectSet interface with the following properties:
SourceCode: Access source code before Metalama executes. For instance,
workspace.SourceCode.Typeslists all types in the workspace.TransformedCode: Code after Metalama executes, including introduced declarations.
Diagnostics: Errors, warnings, and messages reported by the C# compiler, Metalama, or aspects.
AspectClasses, AspectLayers, AspectInstances, Advice, and Transformations: Different steps of the Metalama pipeline.
Note
If your projects target multiple frameworks, declarations appear multiple times in queries—once per target framework.
Filtering projects
Querying a single project
Use GetProject to query a single project by name (without extension):
workspace
.GetProject( "MyProject" )
.SourceCode
.Fields
.Where( f => f.IsStatic )
Getting a project subset
Use GetSubset with a predicate to filter projects:
workspace
.GetSubset( p => p.TargetFramework == "net8.0" )
.SourceCode
.Types
Applying workspace filters
Use ApplyFilter to apply filters directly to the workspace object:
workspace.ApplyFilter( p => p.TargetFramework == "net8.0" );
// All subsequent queries use the filter
var types = workspace.SourceCode.Types;
// Clear filters when done
workspace.ClearFilters();
Inspecting code references
Query inbound and outbound references using GetInboundReferences and GetOutboundReferences:
- Inbound references: References to the declaration.
- Outbound references: References from the declaration.
var field = workspace.SourceCode.Types
.SelectMany( t => t.Fields )
.First( f => f.Name == "_myField" );
// Get all code referencing this field
var references = field.GetInboundReferences();
Using declaration permalinks
Declarations can be uniquely identified using SerializableDeclarationId. Use GetDeclaration to retrieve a declaration by its ID:
var declaration = workspace.GetDeclaration(
"MyProject",
"net8.0",
"F:MyNamespace.MyClass._myField",
false );
Reporting diagnostics
The Report extension method allows you to report diagnostics directly from LINQ queries. Use it with DiagnosticReporter to track reported warnings and errors:
// Report violations and track counts
workspace.SourceCode.Types
.Where( t => t.Name.StartsWith( "_" ) )
.Report( Severity.Warning, "NAMING001", "Type names should not start with underscore." );
Console.WriteLine( $"Found {DiagnosticReporter.ReportedWarnings} warnings." );
Example: architecture verification
The following example demonstrates an architecture verification tool that checks naming conventions and namespace dependencies:
// This is public domain Metalama sample code.
using Metalama.Framework.Code;
using Metalama.Framework.Diagnostics;
using Metalama.Framework.Workspaces;
if ( args.Length != 1 )
{
Console.Error.WriteLine( "Usage: ArchitectureVerifier <csproj>" );
return 1;
}
var workspace = WorkspaceCollection.Default.Load( args[0] );
// Types implementing IFactory must be named *Factory
workspace.SourceCode.Types
.Where( t => t.Name == "IDocumentFactory" )
.SelectMany( t => t.GetDerivedTypes() )
.Where( t => !t.Name.EndsWith( "Factory", StringComparison.Ordinal ) )
.Report(
Severity.Warning,
"ARCH001",
"Type name must end with Factory because it implements IDocumentFactory." );
// Types implementing IDocument must be in the Documents namespace
workspace.SourceCode.Types
.Where( t => t.Name == "IDocument" )
.SelectMany( t => t.GetDerivedTypes() )
.Where( t => t.ContainingNamespace.FullName != "MyApp.Documents" )
.Report(
Severity.Warning,
"ARCH002",
"Type must be in the MyApp.Documents namespace because it implements IDocument." );
// Abstractions namespace cannot reference other application namespaces
workspace.SourceCode.Types
.Where( t => t.ContainingNamespace.FullName == "MyApp.Abstractions" )
.SelectMany( t => t.GetOutboundReferences() )
.Where( r =>
{
var ns = r.DestinationDeclaration.GetNamespace()?.FullName ?? "";
return ns.StartsWith( "MyApp", StringComparison.Ordinal )
&& ns != "MyApp.Abstractions";
} )
.Report(
Severity.Warning,
"ARCH003",
"Abstractions namespace must not depend on other application namespaces." );
Console.WriteLine(
$"{DiagnosticReporter.ReportedWarnings + DiagnosticReporter.ReportedErrors} violations found." );
return DiagnosticReporter.ReportedErrors > 0 ? 2
: DiagnosticReporter.ReportedWarnings > 0 ? 1
: 0;