Unknown Messages
When Wolverine receives a message from the outside world, it’s keying off the message type name from the Envelope to
“know” what message type it’s receiving and therefore, which handler(s) to execute. It’s an imperfect world of course,
so it’s perfectly possible that your system will receive a message from the outside world with a message type name that
your system does not recognize.
Out of the box Wolverine will simply log that it received an unknown message type and discard the message, but there are means to take additional actions on “missing handler” messages where Wolverine does not recognize the message type.
Move to the Dead Letter Queue
You can declaratively tell Wolverine to persist every message received with an unknown message type name to the dead letter queue with this flag:
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
var connectionString = builder.Configuration.GetConnectionString("rabbit");
opts.UseRabbitMq(connectionString!).UseConventionalRouting();
// All unknown message types received should be placed into
// the proper dead letter queue mechanism
opts.UnknownMessageBehavior = UnknownMessageBehavior.DeadLetterQueue;
});snippet source | anchor
The message will be moved to the dead letter queue mechanism for the listening endpoint where the message was received.
Custom Actions
::: note The missing handlers are additive, meaning that you can provide more than one and Wolverine will try to execute each one that is registered for the missing handler behavior. :::
You can direct Wolverine to take custom actions on messages received with unknown message type names by providing a custom implementation of this interface:
namespace Wolverine;
/// <summary>
/// Hook interface to receive notifications of envelopes received
/// that do not match any known handlers within the system
/// </summary>
public interface IMissingHandler
{
/// <summary>
/// Executes for unhandled envelopes
/// </summary>
/// <param name="context"></param>
/// <param name="root"></param>
/// <returns></returns>
ValueTask HandleAsync(IEnvelopeLifecycle context, IWolverineRuntime root);
}snippet source | anchor
Here’s a made up sample that theoretically posts a message to a Slack room by sending a Wolverine message in response:
public class MyCustomActionForMissingHandlers : IMissingHandler
{
public ValueTask HandleAsync(IEnvelopeLifecycle context, IWolverineRuntime root)
{
var bus = new MessageBus(root);
return bus.PublishAsync(new PostInSlack("Incidents",
$"Got an unknown message with type '{context.Envelope!.MessageType}' and id {context.Envelope.Id}"));
}
}snippet source | anchor
And simply registering that with your application’s IoC container against the IMissingHandler interface like this:
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
// configuration
opts.UnknownMessageBehavior = UnknownMessageBehavior.DeadLetterQueue;
});
builder.Services.AddSingleton<IMissingHandler, MyCustomActionForMissingHandlers>();snippet source | anchor
Tracked Session Testing
Just know that the Tracked Session subsystem for integration
testing exposes a separate record collection for NoHandlers and reports when that happens through its output for hopefully
easy troubleshooting on test failures.
url: /guide/http/policies.md
HTTP Policies
Custom policies can be created for HTTP endpoints through either creating your own implementation of IHttpPolicy
shown below:
/// <summary>
/// Use to apply your own conventions or policies to HTTP endpoint handlers
/// </summary>
public interface IHttpPolicy
{
/// <summary>
/// Called during bootstrapping to alter how the message handlers are configured
/// </summary>
/// <param name="chains"></param>
/// <param name="rules"></param>
/// <param name="container">The application's underlying IoC Container</param>
void Apply(IReadOnlyList<HttpChain> chains, GenerationRules rules, IServiceContainer container);
}snippet source | anchor
And then adding a policy to the WolverineHttpOptions like this code from the Fluent Validation extension for HTTP:
/// <summary>
/// Apply Fluent Validation middleware to all Wolverine HTTP endpoints with a known Fluent Validation
/// validator for the request type
/// </summary>
/// <param name="httpOptions"></param>
public static void UseFluentValidationProblemDetailMiddleware(this WolverineHttpOptions httpOptions)
{
httpOptions.AddPolicy<HttpChainFluentValidationPolicy>();
}snippet source | anchor
Or lastly through lambdas (which creates an IHttpPolicy object behind the scenes):
app.MapWolverineEndpoints(opts =>
{
opts.UseApiVersioning(v =>
{
// Existing unversioned endpoints are left unchanged
v.UnversionedPolicy = UnversionedPolicy.PassThrough;
v.Sunset("3.0").On(DateTimeOffset.Parse("2027-01-01T00:00:00Z"))
.WithLink(new Uri("https://example.com/migrate-to-v2"), "Migration guide", "text/html");
v.Deprecate("1.0").On(DateTimeOffset.Parse("2026-12-31T00:00:00Z"))
.WithLink(new Uri("https://example.com/sunset-v1"));
});
// This is strictly to test the endpoint policy
opts.ConfigureEndpoints(httpChain =>
{
// The HttpChain model is a configuration time
// model of how the HTTP endpoint handles requests
// This adds metadata for OpenAPI
httpChain.WithMetadata(new CustomMetadata());
});
// more configuration for HTTP...
// Opting into the Fluent Validation middleware from
// Wolverine.Http.FluentValidation
opts.UseFluentValidationProblemDetailMiddleware();
// Or instead, you could use Data Annotations that are built
// into the Wolverine.HTTP library
opts.UseDataAnnotationsValidationProblemDetailMiddleware();snippet source | anchor
The HttpChain model is a configuration time structure that Wolverine.Http will use at runtime to create the full
HTTP handler (RequestDelegate and RoutePattern for ASP.Net Core). But at bootstrapping / configuration time, we have
the option to add — or remove — any number of middleware, post processors, and custom metadata (OpenAPI or otherwise)
for the endpoint.
Here’s an example from the Wolverine.Http tests of using a policy to add custom metadata:
app.MapWolverineEndpoints(opts =>
{
opts.UseApiVersioning(v =>
{
// Existing unversioned endpoints are left unchanged
v.UnversionedPolicy = UnversionedPolicy.PassThrough;
v.Sunset("3.0").On(DateTimeOffset.Parse("2027-01-01T00:00:00Z"))
.WithLink(new Uri("https://example.com/migrate-to-v2"), "Migration guide", "text/html");
v.Deprecate("1.0").On(DateTimeOffset.Parse("2026-12-31T00:00:00Z"))
.WithLink(new Uri("https://example.com/sunset-v1"));
});
// This is strictly to test the endpoint policy
opts.ConfigureEndpoints(httpChain =>
{
// The HttpChain model is a configuration time
// model of how the HTTP endpoint handles requests
// This adds metadata for OpenAPI
httpChain.WithMetadata(new CustomMetadata());
});
// more configuration for HTTP...
// Opting into the Fluent Validation middleware from
// Wolverine.Http.FluentValidation
opts.UseFluentValidationProblemDetailMiddleware();
// Or instead, you could use Data Annotations that are built
// into the Wolverine.HTTP library
opts.UseDataAnnotationsValidationProblemDetailMiddleware();snippet source | anchor
Resource Writer Policies
Wolverine has an additional type of policy that deals with how an endpoints primary result is handled.
/// <summary>
/// Use to apply custom handling to the primary result of an HTTP endpoint handler
/// </summary>
public interface IResourceWriterPolicy
{
/// <summary>
/// Called during bootstrapping to see whether this policy can handle the chain. If yes no further policies are tried.
/// </summary>
/// <param name="chain"> The chain to test against</param>
/// <returns>True if it applies to the chain, false otherwise</returns>
bool TryApply(HttpChain chain);
}snippet source | anchor
Only one of these so called resource writer policies can apply to each endpoint and there are a couple of built in policies already.
If you need special handling of a primary return type you can implement IResourceWriterPolicy and register it in WolverineHttpOptions
opts.AddResourceWriterPolicy<CustomResourceWriterPolicy>();snippet source | anchor
Resource writer policies registered this way will be applied in order before all built in policies.
url: /guide/http/problemdetails.md
Using ProblemDetails
Wolverine has some first class support for the ProblemDetails specification in its HTTP middleware model. Wolverine also has a Fluent Validation middleware package for HTTP endpoints, but it’s frequently valuable to write one off, explicit validation for certain endpoints.
Consider this contrived sample endpoint with explicit validation being done in a “Before” middleware method:
public class ProblemDetailsUsageEndpoint
{
public ProblemDetails Validate(NumberMessage message)
{
// If the number is greater than 5, fail with a
// validation message
if (message.Number > 5)
return new ProblemDetails
{
Detail = "Number is bigger than 5",
Status = 400
};
// All good, keep on going!
return WolverineContinue.NoProblems;
}
[WolverinePost("/problems")]
public static string Post(NumberMessage message)
{
return "Ok";
}
}
public record NumberMessage(int Number);snippet source | anchor
Wolverine.Http now (as of 1.2.0) has a convention that sees a return value of ProblemDetails and looks at that as a
“continuation” to tell the http handler code what to do next. One of two things will happen:
- If the
ProblemDetailsreturn value is the same instance asWolverineContinue.NoProblems, just keep going - Otherwise, write the
ProblemDetailsout to the HTTP response and exit the HTTP request handling
To make that clearer, here’s the generated code:
public class POST_problems : Wolverine.Http.HttpHandler
{
private readonly Wolverine.Http.WolverineHttpOptions _wolverineHttpOptions;
public POST_problems(Wolverine.Http.WolverineHttpOptions wolverineHttpOptions) : base(wolverineHttpOptions)
{
_wolverineHttpOptions = wolverineHttpOptions;
}
public override async System.Threading.Tasks.Task Handle(Microsoft.AspNetCore.Http.HttpContext httpContext)
{
var problemDetailsUsageEndpoint = new WolverineWebApi.ProblemDetailsUsageEndpoint();
var (message, jsonContinue) = await ReadJsonAsync<WolverineWebApi.NumberMessage>(httpContext);
if (jsonContinue == Wolverine.HandlerContinuation.Stop) return;
var problemDetails = problemDetailsUsageEndpoint.Before(message);
if (!(ReferenceEquals(problemDetails, Wolverine.Http.WolverineContinue.NoProblems)))
{
await Microsoft.AspNetCore.Http.Results.Problem(problemDetails).ExecuteAsync(httpContext).ConfigureAwait(false);
return;
}
var result_of_Post = WolverineWebApi.ProblemDetailsUsageEndpoint.Post(message);
await WriteString(httpContext, result_of_Post);
}
}And for more context, here’s the matching “happy path” and “sad path” tests for the endpoint above:
[Fact]
public async Task continue_happy_path()
{
// Should be good
await Scenario(x =>
{
x.Post.Json(new NumberMessage(3)).ToUrl("/problems");
});
}
[Fact]
public async Task stop_with_problems_if_middleware_trips_off()
{
// This is the "sad path" that should spawn a ProblemDetails
// object
var result = await Scenario(x =>
{
x.Post.Json(new NumberMessage(10)).ToUrl("/problems");
x.StatusCodeShouldBe(400);
x.ContentTypeShouldBe("application/problem+json");
});
}snippet source | anchor
Lastly, if Wolverine sees the existence of a ProblemDetails return value in any middleware, Wolverine will fill in OpenAPI
metadata for the “application/problem+json” content type and a status code of 400. This behavior can be easily overridden
with your own metadata if you need to use a different status code like this:
// Use 418 as the status code instead
[ProducesResponseType(typeof(ProblemDetails), 418)]Using ProblemDetails with Marten aggregates
Of course, if you are using Marten’s aggregates within your Wolverine http handlers, you also want to be able to validation using the aggregate’s details in your middleware and this is perfectly possible like this:
[AggregateHandler]
public static ProblemDetails Before(IShipOrder command, Order order)
{
if (order.IsShipped())
{
return new ProblemDetails
{
Detail = "Order already shipped",
Status = 428
};
}
return WolverineContinue.NoProblems;
}snippet source | anchor
Within Message Handlers
ProblemDetails can be used within message handlers as well with similar rules. See this example
from the tests:
public static class NumberMessageHandler
{
public static ProblemDetails Validate(NumberMessage message)
{
if (message.Number > 5)
{
return new ProblemDetails
{
Detail = "Number is bigger than 5",
Status = 400
};
}
// All good, keep on going!
return WolverineContinue.NoProblems;
}
// This "Before" method would only be utilized as
// an HTTP endpoint
[WolverineBefore(MiddlewareScoping.HttpEndpoints)]
public static void BeforeButOnlyOnHttp(HttpContext context)
{
Debug.WriteLine("Got an HTTP request for " + context.TraceIdentifier);
CalledBeforeOnlyOnHttpEndpoints = true;
}
// This "Before" method would only be utilized as
// a message handler
[WolverineBefore(MiddlewareScoping.MessageHandlers)]
public static void BeforeButOnlyOnMessageHandlers()
{
CalledBeforeOnlyOnMessageHandlers = true;
}
// Look at this! You can use this as an HTTP endpoint too!
[WolverinePost("/problems2")]
public static void Handle(NumberMessage message)
{
Debug.WriteLine("Handled " + message);
Handled = true;
}
// These properties are just a cheap trick in Wolverine internal tests
public static bool Handled { get; set; }
public static bool CalledBeforeOnlyOnMessageHandlers { get; set; }
public static bool CalledBeforeOnlyOnHttpEndpoints { get; set; }
}snippet source | anchor
This functionality was added so that some handlers could be both an endpoint and message handler without having to duplicate code or delegate to the handler through an endpoint.