Working with Code Generation
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.
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();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.
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.
Embedding Codegen in Docker
This blog post from Oskar Dudycz will apply to Wolverine as well: How to create a Docker image for the Marten application
At this point, the most successful mechanism and sweet spot is to run the codegen as Dynamic at development time, but generating the code artifacts just in time for production deployments. From Wolverine's sibling project Marten, see this section on Application project setup for embedding the code generation directly into your Docker images for deployment.
Troubleshooting Code Generation Issues
WARNING
There's nothing magic about the Auto mode, and Wolverine isn't (yet) doing any file comparisons against the generated code and the current version of the application. At this point, the Wolverine community recommends against using the Auto mode for code generation as it has not added much value and can cause some confusion.
In all cases, don't hesitate to reach out to the Wolverine team in the Discord link at the top right of this page to ask for help with any codegen related issues.
If Wolverine is throwing exceptions in Static mode saying that it cannot find the expected pre-generated types, here's your checklist of things to check:
Are the expected generated types written to files in the main application project before that project is compiled? The pre-generation works by having the source code written into the assembly in the first place.
Is Wolverine really using the correct application assembly when it looks for pre-built handlers or HTTP endpoints? Wolverine will log what it thinks is the application assembly upfront, but it can be fooled in certain project structures. To override the application assembly choice to help Wolverine out, use this syntax:
using var host = Host.CreateDefaultBuilder()
.UseWolverine(opts =>
{
// Override the application assembly to help
// Wolverine find its handlers
// Should not be necessary in most cases
opts.ApplicationAssembly = typeof(Program).Assembly;
}).StartAsync();If the assembly choice is correct, and the expected code files are really in Internal/Generated exactly as you'd expect, make sure there's no accidental <Exclude /> nodes in your project file. Don't laugh, that's actually happened to Wolverine users
WARNING
Actually, while the Wolverine team mostly uses JetBrains Rider that doesn't exhibit this behavior, we found out the hard way interacting with other folks that Visual Studio.Net will add the <Exclude /> into your csproj file when you manually delete the generated code files sometimes.
If you see issues with Marten document providers, make sure that you have registered that document with Marten itself. At this point, Wolverine does not automatically register Saga types with Marten. See Marten's own documentation about document type discovery.
Wolverine Code Generation and IoC 5.0
INFO
Why, you ask, does Wolverine do any of this? Wolverine was originally conceived of as the successor to the FubuMVC & FubuTransportation projects from the early 2010's. A major lesson learned from FubuMVC was that we needed to reduce object allocations, layering, runaway Exception stack traces, and allow for more flexible and streamlined handler or endpoint method signatures. To that end we fully embraced using runtime code generation -- and this was built well before source generators were available.
As for the IoC part of this strategy, we ask you, what's the very fastest IoC tool in .NET? The answer of course, is "no IoC container."
Wolverine's code generation uses the configuration of your IoC tool to create the generated code wrappers around your raw message handlers, HTTP endpoints, and middleware methods. Whenever possible, Wolverine is trying to completely eliminate your application's IoC tool from the runtime code by generating the necessary constructor function invocations to exactly mimic your application's IoC configuration.
INFO
Because you should care about this, Wolverine is absolutely generating using or await using for any objects it creates through constructor calls that implements IDisposable or IAsyncDisposable.
When generating the adapter classes, Wolverine can infer which method arguments or type dependencies can be sourced from your application's IoC container configuration. If Wolverine can determine a way to generate all the necessary constructor calls to create any necessary services registered with a Scoped or Transient lifetime, Wolverine will generate code with the constructors. In this case, any IoC services that are registered with a Singleton lifetime will be "inlined" as constructor arguments into the generated adapter class itself for a little better efficiency.
WARNING
The usage of a service locator within the generated code will naturally be a little less efficient just because there is more runtime overhead. More dangerously, the service locator usage can sometimes foul up the scoping of services like Wolverine's IMessageBus or Marten's IDocumentSession that are normally built outside of the IoC container
If Wolverine cannot determine a path to generate code for raw constructor construction of any registered services for a message handler or HTTP endpoint, Wolverine will fall back to generating code with the service locator pattern using a scoped container (think IServiceScopeFactory).
Here's some facts you do need to know about this whole process:
- The adapter classes generated by Wolverine for both message handlers and HTTP endpoints are effectively singleton scoped and only ever built once
- Wolverine will try to bring
Singletonscoped services through the generated adapter type's constructor function one time - Wolverine will have to fall back to the service locator usage if any service dependency that has a
ScopedorTransientlifetime is either aninternaltype or uses an "opaque" Lambda registration (thinkIServiceCollection.AddScoped(s => {}))
TIP
The code generation using IoC configuration is tested with both the built in .NET ServiceProvider and Lamar. It is theoretically possible to use other IoC tools with Wolverine, but only if you are only using IServiceCollection for your IoC configuration.
As of Wolverine 5.0, you now have the ability to better control the usage of the service locator in Wolverine's code generation to potentially avoid unwanted usage:
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
// This is the default behavior. Wolverine will allow you to utilize
// service location in the codegen, but will warn you through log messages
// when this happens
opts.ServiceLocationPolicy = ServiceLocationPolicy.AllowedButWarn;
// Tell Wolverine to just be quiet about service location and let it
// all go. For any of you with small children, I defy you to get the
// Frozen song out of your head now...
opts.ServiceLocationPolicy = ServiceLocationPolicy.AlwaysAllowed;
// Wolverine will throw exceptions at runtime if it encounters
// a message handler or HTTP endpoint that would require service
// location in the code generation
// Use this option to disallow any undesirably service location
opts.ServiceLocationPolicy = ServiceLocationPolicy.NotAllowed;
});::: note Wolverine.HTTP has some additional control over the service locator to utilize the shared scoped container with the rest of the AspNetCore pipeline. :::
Allow List for Service Location 5.0
Wolverine always reverts to using a service locator when it encounters an "opaque" Lambda registration that has either a Scoped or Transient service lifetime. You can explicitly create an "allow" list of service types that can use a service locator pattern while allowing the rest of the code generation for the message handler or HTTP endpoint to use the more predictable and efficient generated constructor functions with this syntax:
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
// other configuration
// Use a service locator for this service w/o forcing the entire
// message handler adapter to use a service locator for everything
opts.CodeGeneration.AlwaysUseServiceLocationFor<IServiceGatewayUsingRefit>();
});For example, this functionality might be helpful for:
- Refit proxies that are registered in IoC with a Lambda registration, but might not use any other services
- EF Core
DbContexttypes that might require some runtime configuration to construct themselves, but don't use other services (a JasperFx Software client ran into this needing to conditionally opt into read replica usage, so hence, this feature made it into Wolverine 5.0)
Environment Check for Expected Types
As a new option in Wolverine 1.7.0, you can also add an environment check for the existence of the expected pre-built types to fail fast on application startup like this:
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
if (builder.Environment.IsProduction())
{
opts.CodeGeneration.TypeLoadMode = TypeLoadMode.Static;
opts.Services.CritterStackDefaults(cr =>
{
// I'm only going to care about this in production
cr.Production.AssertAllPreGeneratedTypesExist = true;
});
}
});
using var host = builder.Build();
await host.StartAsync();Do note that you would have to opt into using the environment checks on application startup, and maybe even force .NET to make hosted service failures stop the application.
See Oakton's Environment Check functionality for more information (the old Oakton documentation is still relevant for JasperFx).
Previewing the Generated Code
TIP
All of these commands are from the JasperFx.CodeGeneration.Commands library that Wolverine adds as a dependency. This is shared with Marten as well.
To preview the generated source code, use this command line usage from the root directory of your .NET project:
dotnet run -- codegen previewGenerating Code Ahead of Time
To write the source code ahead of time into your project, use:
dotnet run -- codegen writeThis command should write all the source code files for each message handler and/or HTTP endpoint handler to /Internal/Generated/WolverineHandlers directly under the root of your project folder.
Optimized Workflow
Wolverine and Marten both use the shared JasperFx library for their code generation, and you can configure different behavior for production versus development time for both tools (and any future "CritterStack" tools) with this usage:
using var host = await Host.CreateDefaultBuilder()
.UseWolverine(opts =>
{
// Use "Auto" type load mode at development time, but
// "Static" any other time
opts.Services.CritterStackDefaults(x =>
{
x.Production.GeneratedCodeMode = TypeLoadMode.Static;
x.Production.ResourceAutoCreate = AutoCreate.None;
// Little draconian, but this might be helpful
x.Production.AssertAllPreGeneratedTypesExist = true;
// These are defaults, but showing for completeness
x.Development.GeneratedCodeMode = TypeLoadMode.Dynamic;
x.Development.ResourceAutoCreate = AutoCreate.CreateOrUpdate;
});
}).StartAsync();Which will use:
TypeLoadMode.Autowhen the .NET environment is "Development" and try to write new source code to fileTypeLoadMode.Staticfor other .NET environments for optimized cold start times

