Status: Current. Tutorial for users. Every step was run with the .NET SDK 10.0.402 and the
2.1.10-previewpackages on 2026-10-19; the output shown is the output observed.
In this tutorial you build three small projects: a library that opens a dialog, a library of substitutes for it, and a console application that uses the first library. You then weave the application so that it uses the substitutes, and finally make the weave part of its build. The result is an application whose Debug build never opens the dialog, while its source and its Release build are unchanged.
You need the .NET SDK 10 or later and a terminal. The
commands use the dotnet command line; an IDE works the same way once the projects exist.
1. Create the library to substitute
Section titled “1. Create the library to substitute”Create a directory for the tutorial and, inside it, the library:
dotnet new classlib -n Desktop -o Desktop -f net10.0Delete Desktop/Class1.cs and add Desktop/Dialog.cs:
namespace Desktop;
public class Dialog{ public string ShowDialog() => "A window appeared on screen";}
public class MessageBox{ public static string Show(string text) => $"A message box showed '{text}'";}This stands in for a real UI library: Dialog is a class an application derives from, and
MessageBox.Show is a static method it calls.
2. Create the substitutes
Section titled “2. Create the substitutes”dotnet new classlib -n Desktop.Testing -o Desktop.Testing -f net10.0dotnet add Desktop.Testing reference Desktop/Desktop.csprojdotnet add Desktop.Testing package Allors.Immersive.Attributes --prereleaseThe packages are published as previews, so --prerelease is required. Delete
Desktop.Testing/Class1.cs and add Desktop.Testing/Substitutes.cs:
namespace Desktop.Testing;
using Allors.Immersive.Attributes;
public class Marker{}
[SubstituteClass]public class Dialog : Desktop.Dialog{ [SubstituteMethod(typeof(Desktop.Dialog), "ShowDialog")] public string ShowDialogSubstitute() => "No window: the substitute answered";}
public class MessageBox{ [SubstituteMethod(typeof(Desktop.MessageBox))] public static string Show(string text) => $"No message box: the substitute received '{text}'";}Three things happen here. Marker is an ordinary empty class; the application will point at it
to name this assembly. [SubstituteClass] on Dialog substitutes its base class,
Desktop.Dialog: in a woven assembly, classes that derived from Desktop.Dialog derive from
this class instead. [SubstituteMethod] names a method of the library; calls to it are
redirected to the attributed method. Show takes its name from the attributed method, while
ShowDialog is named explicitly because the substitute method is called
ShowDialogSubstitute.
3. Create the application
Section titled “3. Create the application”dotnet new console -n App -o App -f net10.0dotnet add App reference Desktop/Desktop.csproj Desktop.Testing/Desktop.Testing.csprojReplace the content of App/Program.cs:
using Desktop;
var form = new MainForm();Console.WriteLine(form.Run());Console.WriteLine(MessageBox.Show("Saved"));Console.WriteLine("Base type: " + typeof(MainForm).BaseType);
public class MainForm : Dialog{ public string Run() => this.ShowDialog();}
#if DEBUGpublic class ImmersiveMarker : Desktop.Testing.Marker{}#endifThe application uses the library as any application would. The only addition is
ImmersiveMarker: a class with exactly that name, whose base type tells the weaver which
assembly holds the substitutes. The #if DEBUG guard keeps the marker, and with it the weave,
out of Release builds.
Build and run it:
dotnet build Appdotnet App/bin/Debug/net10.0/App.dllThe output shows the unwoven behaviour:
A window appeared on screenA message box showed 'Saved'Base type: Desktop.Dialog4. Weave the application
Section titled “4. Weave the application”Install the command-line tool into a local tool manifest, so that its version is recorded with the tutorial:
dotnet new tool-manifestdotnet tool install Allors.Immersive.Tool --prereleaseWeave the compiled application, with -v to see what the weaver finds:
dotnet immerse App/bin/Debug/net10.0/App.dll -vSubstitutables: App, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nullSubstitutable: ProgramSubstitutable: MainFormSubstitutable: ImmersiveMarkerSubstitutes: Desktop.Testing, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nullSubstituteClass: Desktop.DialogSubstituteMethod: Desktop.Dialog.ShowDialogSubstituteMethod: Desktop.MessageBox.ShowDesktop.Testing.dll immersed in App.dll.The weaver listed the classes of the application, found the marker, loaded Desktop.Testing.dll
from the application’s output directory, found one substitute class and two substitute methods,
rewrote App.dll and saved it in place. Run the application again, without rebuilding it:
dotnet App/bin/Debug/net10.0/App.dllNo window: the substitute answeredNo message box: the substitute received 'Saved'Base type: Desktop.Testing.DialogMainForm now derives from the substitute, this.ShowDialog() calls the substitute method, and
the static call goes to the substitute’s Show. The source of App did not change.
5. Weave as part of the build
Section titled “5. Weave as part of the build”A build writes a fresh, unwoven App.dll, so the tool has to run after every build. Add this
target to App/App.csproj, inside the <Project> element:
<Target Name="Immerse" AfterTargets="Build" Condition="'$(IsCrossTargetingBuild)' != 'true'"> <Exec Command="dotnet immerse "$(TargetPath)" -v" /></Target>The condition matters only for a project with several target frameworks; Run the weaver explains it.
dotnet build App now ends with the weaver’s messages, and dotnet run --project App builds,
weaves and prints the substituted output; incremental builds weave again. A Release build has
no marker:
dotnet run --project App -c ReleaseThe weaver reports No immersive assembly found in the build, leaves the assembly unchanged,
and the application prints the original output.
Where to go next
Section titled “Where to go next”How weaving works explains what the weaver rewrites and why the base type changed. Run the weaver covers search directories, global installation of the tool, the weaver’s API and the state of the MSBuild package. For a Windows Forms or Avalonia application the supplied libraries already contain the substitutes: see Test a Windows Forms application and Test an Avalonia application.