March 2, 2014

Quick and easy OWIN pipeline hooks

Once you get your head around OWIN’s Func<yness> you begin to see how powerful it can be.

In my last post I discussed the different ways you can add middleware to the OWIN pipeline; one of which was to pass an inline delegate/lambda expression.

As I continue to hack around with OWIN I often find that I want to execute logic at the beginning and/or end of request.

The extension methods below enable you to register before and/or after pipeline hooks; so rather than having to write a middleware class you can just pass a delegate. I’m not advocating that you do this for complex middleware but these are useful extensions if you’re learning OWIN and want to understand how the pipeline fits together.

The UseHooks/UseHooksAsync extensions allow you to share state between the before and after hooks without polluting the OWIN environment dictionary (effectively wrapping the two hooks in a closure).

Example 1 - Timing the OWIN pipeline

app.UseHooks(
    before: env => Stopwatch.StartNew(),
    after: (stopwatch, env) =>
    {
        stopwatch.Stop();
        Console.WriteLine(
            "Request completed in {0} milliseconds.", 
            stopwatch.ElapsedMilliseconds);
    }
);

Example 2 - Introducing latency

app.UseHooksAsync(before: env => Task.Delay(2000));

Example 3 - Logging the end of the request

app.UseHooks(after: env => Console.WriteLine(
    "Completed request to {0}", env["owin.RequestPath"]));

Both UseHooks and UseHooksAsync have overloads for optionally passing state and as the method names suggest, one executes the hooks synchronously, the other asynchronously.

using AppFunc = Func<IDictionary<string, object>, Task>;

public static class OwinPipelineHookExtensions
{
    public static IAppBuilder UseHooks(
        this IAppBuilder app,
        Action<IDictionary<string, object>> before = null,
        Action<IDictionary<string, object>> after = null)
    {
        return app.Use(new Func<AppFunc, AppFunc>(next => (async env =>
        {
            if (before != null)
            {
                before.Invoke(env);
            }

            await next.Invoke(env);

            if (after != null)
            {
                after.Invoke(env);
            }
        })));
    }
    
    public static IAppBuilder UseHooks<TState>(
        this IAppBuilder app,
        Func<IDictionary<string, object>, TState> before = null,
        Action<TState, IDictionary<string, object>> after = null,
        TState defaultState = default(TState))
    {
        return app.Use(new Func<AppFunc, AppFunc>(next => (async env =>
        {
            TState state = defaultState;

            if (before != null)
            {
                state = before.Invoke(env);
            }
            
            await next.Invoke(env);

            if (after != null)
            {
                after.Invoke(state, env);
            }
        })));
    }

    public static IAppBuilder UseHooksAsync(
        this IAppBuilder app,
        Func<IDictionary<string, object>, Task> before = null,
        Func<IDictionary<string, object>, Task> after = null)
    {
        return app.Use(new Func<AppFunc, AppFunc>(next => (async env =>
        {
            if (before != null)
            {
                await before.Invoke(env);
            }

            await next.Invoke(env);

            if (after != null)
            {
                await after.Invoke(env);
            }
        })));
    }

    public static IAppBuilder UseHooksAsync<TState>(
        this IAppBuilder app,
        Func<IDictionary<string, object>, Task<TState>> before = null,
        Func<TState, IDictionary<string, object>, Task> after = null,
        TState defaultState = default(TState))
    {
        return app.Use(new Func<AppFunc, AppFunc>(next => (async env =>
        {
            TState state = defaultState;

            if (before != null)
            {
                state = await before.Invoke(env);
            }

            await next.Invoke(env);

            if (after != null)
            {
                await after.Invoke(state, env);
            }
        })));
    }
}

View as Gist

© 2022 Ben Foster