Open sandboxFocusImprove this doc

Using the Workspaces API

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:

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();

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;

See also