Skip to content

Middleware

Wolverine supports the "Russian Doll" model of middleware, similar in concept to ASP.NET Core but very different in implementation. Wolverine's middleware uses runtime code generation and compilation with JasperFx.CodeGeneration (which is also used by Marten). What this means is that "middleware" in Wolverine is code that is woven right into the message and route handlers. The end result is a much more efficient runtime pipeline than most other frameworks that adopt the "Russian Doll" middleware approach that suffer performance issues because of the sheer number of object allocations. It also hopefully means that the exception stack traces from failures in Wolverine message handlers will be far less noisy than competitor tools and Wolverine's own predecessors.

TIP

Wolverine has performance metrics around message execution out of the box, so this whole "stopwatch" sample is unnecessary. But it was an easy way to illustrate the middleware approach.

As an example, let's say you want to build some custom middleware that is a simple performance timing of either HTTP route execution or message execution. In essence, you want to inject code like this:

cs
var stopwatch = new Stopwatch();
stopwatch.Start();
try
{
    // execute the HTTP request
    // or message
}
finally
{
    stopwatch.Stop();
    logger.LogInformation("Ran something in " + stopwatch.ElapsedMilliseconds);
}

snippet source | anchor

You've got a couple different options, but the easiest by far is to use Wolverine's conventional middleware approach.

Conventional Middleware

INFO

Conventional application of middleware is done separately between HTTP endpoints and message handlers. To apply global middleware to HTTP endpoints, see HTTP endpoint middleware.

As an example middleware using Wolverine's conventional approach, here's the stopwatch functionality from above:

cs
public class StopwatchMiddleware
{
    private readonly Stopwatch _stopwatch = new();

    public void Before()
    {
        _stopwatch.Start();
    }

    public void Finally(ILogger logger, Envelope envelope)
    {
        _stopwatch.Stop();
        logger.LogDebug("Envelope {Id} / {MessageType} ran in {Duration} milliseconds",
            envelope.Id, envelope.MessageType, _stopwatch.ElapsedMilliseconds);
    }
}

snippet source | anchor

and that can be added to our application at bootstrapping time like this:

cs
using var host = await Host.CreateDefaultBuilder()
    .UseWolverine(opts =>
    {
        // Apply our new middleware to message handlers, but optionally 
        // filter it to only messages from a certain namespace
        opts.Policies
            .AddMiddleware<StopwatchMiddleware>(chain =>
                chain.MessageType.IsInNamespace("MyApp.Messages.Important"));
    }).StartAsync();

snippet source | anchor

And just for the sake of completeness, here's another version of the same functionality, but this time using a static class just to save on object allocations:

cs
public static class StopwatchMiddleware2
{
    // The Stopwatch being returned from this method will
    // be passed back into the later method
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Stopwatch Before()
    {
        var stopwatch = new Stopwatch();
        stopwatch.Start();

        return stopwatch;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Finally(Stopwatch stopwatch, ILogger logger, Envelope envelope)
    {
        stopwatch.Stop();
        logger.LogDebug("Envelope {Id} / {MessageType} ran in {Duration} milliseconds",
            envelope.Id, envelope.MessageType, stopwatch.ElapsedMilliseconds);
    }
}

snippet source | anchor

Alright, let's talk about what's happening in the code samples above:

  • You'll notice that I took in ILogger instead of any specific ILogger<T>. Wolverine is quietly using the ILogger<Message Type> for the current handler when it generates the code.
  • Wolverine places the Before() method to be called in front of the actual message handler method
  • Because there is a Finally() method, Wolverine wraps a try/finally block around the code running after the Before() method and calls Finally() within that finally block

TIP

Note that the method name matching is case sensitive.

Here's the conventions:

LifecycleMethod Names
Before the Handler(s)Before, BeforeAsync, Load, LoadAsync
After the Handler(s)After, AfterAsync, PostProcess, PostProcessAsync
In finally blocks after the Handlers & "After" methodsFinally, FinallyAsync

The generated code for the conventionally applied methods would look like this basic structure:

cs
middleware.Before();
try
{
    // call the actual handler methods
    middleware.After();
}
finally
{
    middleware.Finally();
}

snippet source | anchor

Here's the rules for these conventional middleware classes:

  • Can optionally be static classes, and that maybe advantageous when possible from a performance standpoint
  • If the middleware class is not static, Wolverine can inject constructor arguments with the same rules as for handler methods
  • Objects returned from the Before / BeforeAsync methods can be used as arguments to the inner handler methods or the later "after" or "finally" methods
  • A middleware class can have any mix of zero to many "befores", "afters", or "finallys."

Conditionally Stopping the Message Handling

A "before" method in middleware can be used to stop further message handler by either directly returning HandlerContinuation or returning that value as part of a tuple. If the value Stop is returned, Wolverine will stop all of the further message processing (it's done by generating an if (continuation == HandlerContinuation.Stop) return; line of code).

Here's an example from the custom middleware tutorial that tries to load a matching Account entity referenced by the incoming message and aborts the message processing if it is not found:

cs
// This is *a* way to build middleware in Wolverine by basically just
// writing functions/methods. There's a naming convention that
// looks for Before/BeforeAsync or After/AfterAsync
public static class AccountLookupMiddleware
{
    // The message *has* to be first in the parameter list
    // Before or BeforeAsync tells Wolverine this method should be called before the actual action
    public static async Task<(HandlerContinuation, Account?)> LoadAsync(
        IAccountCommand command, 
        ILogger logger, 
        
        // This app is using Marten for persistence
        IDocumentSession session, 
        
        CancellationToken cancellation)
    {
        var account = await session.LoadAsync<Account>(command.AccountId, cancellation);
        if (account == null)
        {
            logger.LogInformation("Unable to find an account for {AccountId}, aborting the requested operation", command.AccountId);
        }
        
        return (account == null ? HandlerContinuation.Stop : HandlerContinuation.Continue, account);
    }
}

snippet source | anchor

Notice that the middleware above uses a tuple as the return value so that it can both pass an Account entity to the inner handler and also to return the continuation directing Wolverine to continue or stop the message processing.

Registering Middleware by Message Type

Let's say that some of our message types implement this interface:

cs
public interface IAccountCommand
{
    Guid AccountId { get; }
}

snippet source | anchor

We can apply the AccountMiddleware from the section above to only these message types by telling Wolverine to only apply this middleware to any message that implements the IAccountCommand interface like this:

cs
builder.Host.UseWolverine(opts =>
{
    // This middleware should be applied to all handlers where the 
    // command type implements the IAccountCommand interface that is the
    // "detected" message type of the middleware
    opts.Policies.ForMessagesOfType<IAccountCommand>().AddMiddleware(typeof(AccountLookupMiddleware));
    
    opts.UseFluentValidation();

    // Explicit routing for the AccountUpdated
    // message handling. This has precedence over conventional routing
    opts.PublishMessage<AccountUpdated>()
        .ToLocalQueue("signalr")

        // Throw the message away if it's not successfully
        // delivered within 10 seconds
        .DeliverWithin(10.Seconds())
        
        // Not durable
        .BufferedInMemory();
});

snippet source | anchor

Wolverine determines the message type for a middleware class method by assuming that the first argument is the message type, and then looking for actual messages that implement that interface or subclass.

Applying Middleware Explicitly by Attribute

TIP

You can subclass the MiddlewareAttribute class to make more specific middleware applicative attributes for your application.

You can apply the middleware types to individual handler methods with the [Middleware] attribute as shown below:

cs
public static class SomeHandler
{
    [Middleware(typeof(StopwatchMiddleware))]
    public static void Handle(PotentiallySlowMessage message)
    {
        // do something expensive with the message
    }
}

snippet source | anchor

Note that this attribute will accept multiple middleware types. Also note that the [Middleware] attribute can be placed either on an individual handler method or apply to all handler methods on the same handler class if the attribute is at the class level.

Custom Code Generation

For more advanced usage, you can drop down to the JasperFx.CodeGeneration Frame model to directly inject code.

The first step is to create a JasperFx.CodeGeneration Frame class that generates that code around the inner message or HTTP handler:

cs
public class StopwatchFrame : SyncFrame
{
    private readonly IChain _chain;
    private readonly Variable _stopwatch;
    private Variable _logger;

    public StopwatchFrame(IChain chain)
    {
        _chain = chain;

        // This frame creates a Stopwatch, so we
        // expose that fact to the rest of the generated method
        // just in case someone else wants that
        _stopwatch = new Variable(typeof(Stopwatch), "stopwatch", this);
    }

    public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
    {
        writer.Write($"var stopwatch = new {typeof(Stopwatch).FullNameInCode()}();");
        writer.Write("stopwatch.Start();");

        writer.Write("BLOCK:try");
        Next?.GenerateCode(method, writer);
        writer.FinishBlock();

        // Write a finally block where you record the stopwatch
        writer.Write("BLOCK:finally");

        writer.Write("stopwatch.Stop();");
        writer.Write(
            $"{_logger.Usage}.Log(Microsoft.Extensions.Logging.LogLevel.Information, \"{_chain.Description} ran in \" + {_stopwatch.Usage}.{nameof(Stopwatch.ElapsedMilliseconds)});)");

        writer.FinishBlock();
    }

    public override IEnumerable<Variable> FindVariables(IMethodVariables chain)
    {
        // This in effect turns into "I need ILogger<message type> injected into the
        // compiled class"
        _logger = chain.FindVariable(typeof(ILogger));
        yield return _logger;
    }
}

snippet source | anchor

Custom Attributes

To attach our StopwatchFrame as middleware to any route or message handler, we can write a custom attribute based on Wolverine's ModifyChainAttribute class as shown below:

cs
public class StopwatchAttribute : ModifyChainAttribute
{
    public override void Modify(IChain chain, GenerationRules rules, IContainer container)
    {
        chain.Middleware.Add(new StopwatchFrame(chain));
    }
}

snippet source | anchor

This attribute can now be placed either on a specific HTTP route endpoint method or message handler method to only apply to that specific action, or it can be placed on a Handler or Endpoint class to apply to all methods exported by that type.

Here's an example:

cs
public class ClockedEndpoint
{
    [Stopwatch]
    public string get_clocked()
    {
        return "how fast";
    }
}

snippet source | anchor

Now, when the application is bootstrapped, this is the code that would be generated to handle the "GET /clocked" route:

csharp
public class Wolverine_Testing_Samples_ClockedEndpoint_get_clocked : Wolverine.Http.Model.RouteHandler
{
    private readonly Microsoft.Extensions.Logging.ILogger<Wolverine.Configuration.IChain> _logger;

    public Wolverine_Testing_Samples_ClockedEndpoint_get_clocked(Microsoft.Extensions.Logging.ILogger<Wolverine.Configuration.IChain> logger)
    {
        _logger = logger;
    }

    public override Task Handle(Microsoft.AspNetCore.Http.HttpContext httpContext, System.String[] segments)
    {
        var clockedEndpoint = new Wolverine.Testing.Samples.ClockedEndpoint();
        var stopwatch = new System.Diagnostics.Stopwatch();
        stopwatch.Start();
        try
        {
            var result_of_get_clocked = clockedEndpoint.get_clocked();
            return WriteText(result_of_get_clocked, httpContext.Response);
        }

        finally
        {
            stopwatch.Stop();
            _logger.Log(Microsoft.Extensions.Logging.LogLevel.Information, "Route 'GET: clocked' ran in " + stopwatch.ElapsedMilliseconds);)
        }

    }

}

ModifyChainAttribute is a generic way to add middleware or post processing frames, but if you need to configure things specific to routes or message handlers, you can also use ModifyHandlerChainAttribute for message handlers or ModifyRouteAttribute for http routes.

Policies

warning

Again, please go easy with this feature and try not to shoot yourself in the foot by getting too aggressive with custom policies

You can register user-defined policies that apply to all chains or some subset of chains. For message handlers, implement this interface:

cs
/// <summary>
///     Use to apply your own conventions or policies to message handlers
/// </summary>
public interface IHandlerPolicy : IWolverinePolicy
{
    /// <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 Lamar Container</param>
    void Apply(IReadOnlyList<HandlerChain> chains, GenerationRules rules, IContainer container);
}

snippet source | anchor

Here's a simple sample that registers middleware on each handler chain:

cs
public class WrapWithSimple : IHandlerPolicy
{
    public void Apply(IReadOnlyList<HandlerChain> chains, GenerationRules rules, IContainer container)
    {
        foreach (var chain in chains) chain.Middleware.Add(new SimpleWrapper());
    }
}

snippet source | anchor

Then register your custom IHandlerPolicy with a Wolverine application like this:

cs
using var host = await Host.CreateDefaultBuilder()
    .UseWolverine(opts => { opts.Policies.Add<WrapWithSimple>(); }).StartAsync();

snippet source | anchor

Using Configure(chain) Methods

warning

This feature is experimental, but is meant to provide an easy way to apply middleware or other configuration to specific HTTP endpoints or message handlers without writing custom policies or having to resort to all new attributes.

There's one last option for configuring chains by a naming convention. If you want to configure the chains from just one handler or endpoint class, you can implement a method with one of these signatures:

csharp
public static void Configure(IChain)
{
    // gets called for each endpoint or message handling method
    // on just this class
}

public static void Configure(RouteChain chain)
{
    // gets called for each endpoint method on this class
}

public static void Configure(HandlerChain chain)
{
    // gets called for each message handling method
    // on just this class
}

Here's an example of this being used from Wolverine's test suite:

cs
public class CustomizedHandler
{
    public void Handle(SpecialMessage message)
    {
        // actually handle the SpecialMessage
    }

    public static void Configure(HandlerChain chain)
    {
        chain.Middleware.Add(new CustomFrame());

        // Turning off all execution tracking logging
        // from Wolverine for just this message type
        // Error logging will still be enabled on failures
        chain.SuccessLogLevel = LogLevel.None;
        chain.ProcessingLogLevel = LogLevel.None;
    }
}

snippet source | anchor

Released under the MIT License.