vNext ASP.NET 5 Custom Middleware

If you're curious about creating custom Middleware in ASP.NET 5 here are a few things I've come up with while playing around.

First a little background... Middleware gets attached to the IApplicationBuilder inside of the Configure method of Startup.cs.

Middleware allows you to:
1. Do something before the response
2. Move on to the next step of middleware
3. Do something after the response

An example looks like this:

app.use(async (httpcontext, next) =>  
{
   // 1. Before the response
   Console.WriteLine('Before Response');

   // 2. Process the request and wait for completion
   await next.Invoke();

   // 3. After the response
   Console.WriteLine('After Response');
});

You can think of the pipeline as a stack of commands. You push middleware onto the stack. Each middleware method has a reference to the next piece of middleware. At some point, a piece of middleware will NOT call the next piece of middleware. At this point it starts popping off the stack.

For example, the code below adds a number of adhoc statements that will get executed in the order of the of numbers.

app.Use(async (context,  next) =>  
{
    Console.WriteLine("1");
    await next();
    Console.WriteLine("8");
});

app.Use(async (context, next) =>  
{
    Console.WriteLine("2");
    await next();
    Console.WriteLine("7");
});

app.Use(async (context, next) =>  
{
    Console.WriteLine("3");
    await next();
    Console.WriteLine("6");
});

// Instead of calling next, it executes its own task
// and begins reversing back through the middleware stack
app.Use((HttpContext context, Func<Task> next) =>  
{
    Console.WriteLine("4");       

    var task = new Task(() => Console.WriteLine("5"));
    task.Start();

    return task;                
});

// Never gets called because the previous middleware 
// never called next and instead calls its own task.
app.Use(async (context, next) =>  
{
   await next();
}

From the above you can see that each Middleware executes in the order that it is attached. Awaiting on next means that you call the next piece in the pipeline. The middleware that actually handles the request returns the task that is responsible for doing so (in this case #5). Once #5 task completes, it will traverse back through the middleware in reverse order.

There are few ways to attach Middleware. You can apply it inline as shown above or create a class.

When attatching Middleware inline via the Use method, we must pass in a argument that takes the form:

Func<HttpContext, Func<Task>, Task>  

An example of Middleware that attaches a header based on the Response Time is shown. This example starts a Stopwatch, attaches a handler to the Response's OnSendingHeaders event, and waits for the request to complete.

The first example shows how this can be inlined.

app.Use(async (HttpContext httpcontext, Func<Task> next) =>  
{
    var sw = Stopwatch.StartNew();

    httpcontext.Response.OnSendingHeaders((state) =>
    {
        sw.Stop();
        httpcontext.Response.Headers.Add("X-Response-Time", new string[] { sw.ElapsedMilliseconds.ToString() + "ms" });
    }, null);
    await next.Invoke();
});

Instead of using an async function, you can simply return next as so

app.Use((context, next) =>  
{
    Console.WriteLine("Hello");
    return next();
});

The above is great for quick and simple Middleware. If you want to create more reusable Middleware it would be good to store in in a seperate project and attach it as needed.

In order to do this you need to add Microsoft.AspNet.Http.Core and Microsoft.AspNet.Http.Extensions as references to your new project.

You can then create the Middleware class. This class should take a RequestDelegate as a Constructor argument (which will be populated by the Dependency Injection module).

You then need to implement an Invoke method that returns a Task.

public class ResponseTimerMiddleware  
{
    private readonly RequestDelegate next;        

    public ResponseTimerMiddleware(RequestDelegate next)
    {
        this.next = next;
    }

    public Task Invoke(HttpContext context)
    {            
        var sw = Stopwatch.StartNew();
        context.Response.OnSendingHeaders((state) =>
        {                
            sw.Stop();
            context.Response.Headers.Add("X-Response-Time", new string[] { sw.ElapsedMilliseconds.ToString() + "ms" });
        }, null);
        return next(context);
    }

}

You can then create an extension method on the on IApplicationBuilder that attaches your Middleware.

namespace Microsoft.AspNet.Builder  
{
    public static class ResponseTimeExtensions
    {
        public static IApplicationBuilder UseResponseTimer(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<ResponseTimerMiddleware>();
        }
    }
}

When your package is attached to your project, you can then add your Middleware to the pipeline by using the extension method:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)  
{
    // Add response timer          
    app.UseResponseTimer();        
}
comments powered by Disqus