Working with Code Generation
::: danger Wolverine 6.0 changes the IoC integration default
ServiceLocationPolicy.NotAllowed is now the default. If your DI registrations are opaque to Wolverine’s
codegen (lambda factories with Scoped or Transient lifetime, mixed-scope IEnumerable<T>, etc.), the host
now throws InvalidServiceLocationException at startup instead of silently emitting a service-locator call.
Two paths forward:
- Preferred — change the registration to a form Wolverine can see through:
AddScoped<TInterface, TImpl>()instead ofAddScoped<TInterface>(sp => new TImpl(...)), or constructor injection into the handler. - Opt-in escape hatch — list specific types Wolverine should service-locate, via
opts.CodeGeneration.AlwaysUseServiceLocationFor<TService>(). This keeps the rest of the codegen constructor-inlined and only routes the listed types through the service locator.
If you want the 5.x behaviour back wholesale, set opts.ServiceLocationPolicy = ServiceLocationPolicy.AllowedButWarn
(or AlwaysAllowed to silence the warnings too). See the IoC + Service Location section below for the full story
and the 5 → 6 migration guide for the rationale.
:::
::: warning
If you are experiencing noticeable startup lags or seeing spikes in memory utilization with an application using
Wolverine, you will want to pursue using either the Auto or Static modes for code generation as explained in this guide.
:::
Wolverine uses runtime code generation to create the “adaptor” code that Wolverine uses to call into your message handlers. Wolverine’s middleware strategy also uses this strategy to “weave” calls to middleware directly into the runtime pipeline without requiring the copious usage of adapter interfaces that is prevalent in most other .NET frameworks.
::: info
This page covers Wolverine-specific use of code generation. The shared JasperFx code-generation library that backs it — frames, variables, MethodCall, generated types, and the codegen CLI command — is documented at shared-libs.jasperfx.net/codegen. Reach for it when you’re authoring a custom IVariableSource or middleware frame.
:::
That’s great when everything is working as it should, but there’s a couple issues:
- The usage of the Roslyn compiler at runtime can sometimes be slow on its first usage. This can lead to sluggish cold start times in your application that might be problematic in serverless scenarios for examples.
- There’s a little bit of conventional magic in how Wolverine finds and applies middleware or passed arguments to your message handlers or HTTP endpoint handlers.
Not to worry though, Wolverine has several facilities to either preview the generated code for diagnostic purposes to really understand how Wolverine is interacting with your code and to optimize the “cold start” by generating the dynamic code ahead of time so that it can be embedded directly into your application’s main assembly and discovered from there.
By default, Wolverine runs with “dynamic” code generation where all the necessary generated types are built on demand the first time they are needed. This is perfect for a quick start to Wolverine, and might be fine in smaller projects even at production time.
::: warning Note that you may need to delete the existing source code when you change handler signatures or add or remove middleware. Nothing in Wolverine is able to detect that the generated source code needs to be rewritten :::
Lastly, you have a couple options about how Wolverine handles the dynamic code generation as shown below:
using var host = await Host.CreateDefaultBuilder()
.UseWolverine(opts =>
{
// The default behavior. Dynamically generate the
// types on the first usage
opts.CodeGeneration.TypeLoadMode = TypeLoadMode.Dynamic;
// Never generate types at runtime, but instead try to locate
// the generated types from the main application assembly
opts.CodeGeneration.TypeLoadMode = TypeLoadMode.Static;
// Hybrid approach that first tries to locate the types
// from the application assembly, but falls back to
// generating the code and dynamic type. Also writes the
// generated source code file to disk
opts.CodeGeneration.TypeLoadMode = TypeLoadMode.Auto;
}).StartAsync();snippet source | anchor
At development time, use the Dynamic mode if you are actively changing handler
signatures or the application of middleware that might be changing the generated code.
Even at development time, if the handler signatures are relatively stable, you can use
the Auto mode to use pre-generated types locally. This may help you have a quicker
development cycle — especially if you like to lean heavily on integration testing where
you’re quickly starting and stopping your application. The Auto mode will write the generated
source code for missing types to the Internal/Generated folder under your main application
project.
::: tip
If you’re using the Auto mode in combination with dotnet watch you need to disable the watching of
the Internal/Generated folder to avoid application restarts each time codegen writes a new file.
You can do this by adding the following to the .csproj file of your app project.
<ItemGroup>
<Compile Update="Internal\Generated\**\*.cs" Watch="false" />
</ItemGroup>:::
At production time, if there is any issue whatsoever with resource utilization, the Wolverine team
recommends using the Static mode where all types are assumed to be pre-generated into what Wolverine
thinks is the application assembly (more on this in the troubleshooting guide below).
::: tip Most of the facilities shown here will require the Oakton command line integration. :::
The WolverineFx.RuntimeCompilation Package
Runtime code generation requires a Roslyn-backed IAssemblyGenerator to compile the generated C# source into an assembly that Wolverine can load. That implementation ships in JasperFx.RuntimeCompiler, which pulls in roughly 100 MB of Roslyn assemblies. For applications that pre-generate all of their handler / middleware code with TypeLoadMode.Static and never recompile at runtime, those assemblies are dead weight — bigger deployment, slower cold start, and incompatible with Native AOT.
The WolverineFx.RuntimeCompilation package isolates that runtime-compilation dependency in a separate package, so production deployments that don’t need it ship without Roslyn.
::: warning Changed in 6.0
As of Wolverine 6.0, core WolverineFx no longer references JasperFx.RuntimeCompiler or registers a default IAssemblyGenerator. Applications running TypeLoadMode.Dynamic (the default) or Auto now must reference WolverineFx.RuntimeCompilation — simply referencing the package is enough (it auto-registers the Roslyn IAssemblyGenerator via a [WolverineModule]). TypeLoadMode.Static apps that pre-generate all of their code need nothing and ship without Roslyn. See Migrating from 5 to 6. On 5.x this package was an optional, forward-looking opt-in; in 6.0 it is required for runtime code generation. Part of the cold-start / AOT-readiness work in #1577 / #2876.
:::
When you need it
| Scenario | Need the package? |
|---|---|
Local development with TypeLoadMode.Dynamic (the default) | Yes — runtime compilation runs every time. |
TypeLoadMode.Auto with the source-code fall-back | Yes — compilation runs the first time a missing handler is invoked. |
Production with TypeLoadMode.Static and all generated code pre-built into the application assembly | No — runtime compilation is never invoked. In 6.0 core WolverineFx no longer carries Roslyn, so a Static-mode deployment ships without it automatically. |
Running dotnet run -- codegen write from a CI build agent | Yes — that command emits the generated source by compiling it once. |
Installation
dotnet add package WolverineFx.RuntimeCompilationThe package depends on JasperFx.RuntimeCompiler. It does not pull in any other Wolverine concerns.
Configuration
In 6.0, referencing the package is usually all you need: WolverineFx.RuntimeCompilation ships a [WolverineModule] that Wolverine auto-discovers at startup and which registers the Roslyn IAssemblyGenerator for you. With the default extension auto-discovery on, TypeLoadMode.Dynamic/Auto “just works” once the package is referenced — no code change required.
You can also register it explicitly inside UseWolverine(...) via the UseRuntimeCompilation() extension method. This is required if you’ve turned extension auto-discovery off (ExtensionDiscovery.ManualOnly), and is handy when you want to gate it by environment (see below):
using Wolverine;
builder.Host.UseWolverine(opts =>
{
opts.UseRuntimeCompilation();
// ...the rest of your Wolverine configuration
});UseRuntimeCompilation() is idempotent — calling it twice (or alongside the auto-registering module) does nothing on the second call. It registers IAssemblyGenerator as a singleton in DI using TryAddSingleton, so a custom registration you’ve already added wins.
For advanced scenarios where you need to register the runtime compiler outside of UseWolverine(...) (e.g., from a hosted-service registration ordering), the IServiceCollection overload is available:
using Wolverine;
builder.Services.AddWolverineRuntimeCompilation();Recommended pattern: dev-time-only
The cleanest deployment shape is to take the package as a <PackageReference> for the whole project but only register it in development:
builder.Host.UseWolverine(opts =>
{
if (builder.Environment.IsDevelopment())
{
opts.UseRuntimeCompilation();
}
// ...the rest of your Wolverine configuration
});Combine that with TypeLoadMode.Static in production and the dotnet run -- codegen write step in your build pipeline (see Embedding Codegen in Docker below), and your production deployment never invokes Roslyn.
Dropping Roslyn from the production image entirely
The dev-time-only pattern above keeps WolverineFx.RuntimeCompilation referenced for the whole project, so the ~100 MB of Roslyn assemblies still ship in the production image — they’re simply never registered. To actually remove Roslyn from production builds, make the package reference conditional on the build configuration and let production run pre-generated code in Static mode:
<!-- Referenced only in Debug, so a Release publish ships without Roslyn. -->
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<PackageReference Include="WolverineFx.RuntimeCompilation" Version="6.0.0" />
</ItemGroup>using JasperFx;
using JasperFx.CodeGeneration;
// Production runs the pre-generated code with no runtime compilation.
// AssertAllPreGeneratedTypesExist (default: false) makes a missing or stale
// generated type fail fast at startup instead of silently misbehaving.
builder.Services.CritterStackDefaults(x =>
{
x.Production.GeneratedCodeMode = TypeLoadMode.Static;
x.Production.AssertAllPreGeneratedTypesExist = true;
});Because WolverineFx.RuntimeCompilation auto-registers when referenced (via its [WolverineModule]), this pattern needs no UseRuntimeCompilation() call — and that’s exactly what lets you drop the package in Release without a compile error. In Debug the package is present and Dynamic mode compiles at runtime; in a Release/Production build the package is gone and Static mode runs the pre-generated code.
::: warning
The condition must use $(Configuration) with the parentheses. A malformed condition such as '$Configuration)' != 'Release' silently never matches, so the assembly keeps shipping anyway — a common cause of “I excluded the package but Roslyn is still in my image.”
:::
A complete, tested end-to-end example — Program.cs, the conditional .csproj, committed Internal/Generated/, a multi-stage Dockerfile, a verify-production-build.sh script that asserts the Release publish is Roslyn-free, and an Alba test that boots the app in the Production environment — lives in the CqrsMinimalApi sample.
What happens if you forget?
In 6.0, core WolverineFx no longer registers a default IAssemblyGenerator. A TypeLoadMode.Dynamic/Auto app that starts without the runtime compiler available fails fast at startup with:
Wolverine is running in TypeLoadMode.Dynamic, which compiles handler/middleware code at runtime, but no IAssemblyGenerator (Roslyn) is registered. Core WolverineFx no longer ships the runtime compiler. Either add the ‘WolverineFx.RuntimeCompilation’ NuGet package (it auto-registers when referenced, or call opts.UseRuntimeCompilation() in UseWolverine(…)), or pre-generate code with ‘dotnet run — codegen write’ and set opts.CodeGeneration.TypeLoadMode = TypeLoadMode.Static.
To resolve it: reference WolverineFx.RuntimeCompilation (Dynamic/Auto mode), or move to TypeLoadMode.Static with pre-generated code (production, Roslyn-free).
(On 5.x, WolverineFx registered a default IAssemblyGenerator, so the package was an optional forward-looking opt-in. 6.0 removed that default — hence the requirement above.)
Embedding Codegen in Docker
The sweet spot for production deployments is Dynamic codegen at development time, then pre-generated code artifacts baked into the production image so cold start never pays the runtime-codegen cost. A multi-stage Dockerfile that runs dotnet run -- codegen write in the build stage gets you there.
Wire up the CLI command
dotnet run -- codegen write is provided by the JasperFx command-line integration that ships with Wolverine. The last line of your Program.cs needs to hand control to it:
return await app.RunJasperFxCommands(args);Without this, the codegen write verb is unreachable and the build-stage step below will fail.
A multi-stage Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build
WORKDIR /src
COPY ["Application/Application.csproj", "Application/"]