[ASP.NET Core MVC Pipeline] Middleware Pipeline

Lucas Araujo | Azure Coder
7 min readJul 5, 2017

--

Let’s play with the Middleware Pipeline :)

Do you guys remember the flow that I showed you in the first post? No? Let’s revisit it:

The ASP.NET Core MVC Pipeline
The ASP.NET Core MVC Pipeline

So, what do we want to do here is to start from the beginning and in this post we are going to focus on the Blue part of the flow, the Middleware Pipeline:

Middleware Pipeline
Middleware Pipeline

What is a Middleware?

So, what is a middleware in the context of the Core MVC Pipeline, and why should we care about it?

Middleware is software that is assembled into an application pipeline to handle requests and responses. Each component chooses whether to pass the request on to the next component in the pipeline, and can perform certain actions before and after the next component is invoked in the pipeline.

Hmmm… so each middleware is actually a piece of code, which follow some patterns, that we can ‘pile’ forming what we call a pipeline. Each of those pieces of code can (and should) implement its own logic and, that is the most interesting part, can decide whether or not to pass on a request! Because of that, each middleware is empowered to end an HTTP request and ignore every other middleware after if it decides to. Let me draw it for you:

Middleware Pipeline
Middleware Pipeline

In the example on the left, the request pass through all the middleware in the Pipeline and finally gets answered by the Routing Middleware.

In the example on the right, the request is cut short by a custom middleware that send the response itself rather than pass it along to next one.

A couple of important things to notice:

  • A middleware can execute successfully its function and still pass the request to next middleware, therefore allowing for more changes before sending the response.
  • The routing middleware is certainly an important one but he is not vital on the middleware pipeline. It is vital to the MVC Framework, but you could have a perfectly working application without the routing middleware.

Our first Middleware

The sequence of middleware is defined on the Configure method of the Startup.cs class.

public class Startup
{
// ... More Code

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseDeveloperExceptionPage(); // Exception Handling Page Middleware

app.UseStaticFiles(); // Static Files serving Middleware

app.UseMvcWithDefaultRoute(); // MVC Routing Middleware
}
}

Each of the “Use…” methods define a middleware. Let’s inject our own super simple middleware on the stack:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseDeveloperExceptionPage(); // Exception Handling Page Middleware

app.UseStaticFiles(); // Static Files serving Middleware

/* Custom Middleware Inline */
app.Use(async (context, next) =>
{
if (context.Request.Method == "GET")
await next();
else
await context.Response.WriteAsync("Invalid method");
});
/* End Custom Middleware Inline */

app.UseMvcWithDefaultRoute(); // MVC Routing Middleware
}

In this case we are defining that only the requests sent with the HTTP method GET are going to be pass on to the Routing Middleware. Other HTTP methods will be answered directly by our middleware and will never reach the Routing system. Notice that we defined our middleware inline, inside the Configure method and that is not a good practice because it worsen readability and break the Single Responsibility principle.

Pretty simple, ahn? Let’s get into some details.

Order Matters

Keep it in mind, the order in which your middleware is set up inside the Configure method matters! It matters because this is the order in which they are going to be executed when a request is received. In our example our custom middleware would never be hit if the user requests an existing static resource (like an image, or a JS file) because the UseStaticFiles middleware would have handle the request and sent a response to the user shortcutting the request.

Before AND After the next Middleware

We can see in our example that the execution of the next middleware in the pipeline is represented by a call to the next() delegate, and that means that we can execute some logic before and after the next delegate execution.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseStaticFiles();

app.Use(async (context, next) =>
{
// Execute some BEFORE before calling the next middleware
await next();
// Execute some AFTER before calling the next middleware
});

app.UseMvcWithDefaultRoute();
}

Use, Run or Map

Every middleware must run using one those three methods that define a specific behavior for the middleware.

Use

First of all, the most common method, the ‘Use…’. This method is the one that allows our middleware to receive a request, process it and, if necessary, pass it on to the next middleware on the pipeline.

Use(IApplicationBuilder, Func<HttpContext, Func, Task>)

We receive a delegate with an HTTPContext with all the details available about the request and another delegate, which is actually the call to the next middleware. As a result we can call the next middleware in the pipeline whenever we want or not even call it, if our logic says it is not necessary.

app.Use(async (context, next) => { 
// Our custom logic
if(something)
{
await next();
// More custom logic
}
else
{
// Next middleware is never called on this path
}
});

Run

Then we have the ‘Run…’ method and this method allows us to define a middleware that will always end the execution, simply because it has no knowledge of a next middleware in the pipeline.

Run(IApplicationBuilder, RequestDelegate)

In this case, the only thing we receive is the delegate with the custom logic of our own middleware.

Map & MapWhen

And then we have the ‘Map…’ method which, as the name implies, allows us to map a request to a middleware depending on the Path of the request.

Map is a little bit different from the previous methods in the sense that you only map the request to a middleware, but you are still going to need to set an ‘Use…’ or ‘Run…’ method to actually execute our logic.

Map(IApplicationBuilder, PathString, Action)

So the Map method actually pass on an instance of the IApplicationBuilder interface! Which allows us to define a new middleware depending on the mapped request.

app.Map("/CustomPath", (appPath1) =>
{
appPath1.Use(async (context, next) =>
{
// Cuustom Middleware code for Path 1
await next();
});
});

app.Map("/AnotherCustomPath", (appPath2) =>
{
appPath2.Run(async (context) =>
{
// Custom Middleware code for Path 2
await context.Response.WriteAsync("Done");
});
});

The Map method also has a variant called MapWhen that allows us to map the middleware against some condition on the HttpContext.

app.MapWhen(context => context.Request.IsHttps, appContext =>
{
appContext.Use(async (context, next) =>
{
// Custom Logic
await next();
});
});

app.MapWhen(context => context.Request.Method == "GET", appContext =>
{
appContext.Run(async context =>
{
await context.Response.WriteAsync("Done!");
});
});

So, those four methods are supposed to give us all the flexibility that we need to create our middleware.

I think it is possible to notice that creating a middleware inline is not a great idea, right? It gets messy really quick and we don’t want that. Let’s start creating a real Middleware in a more decent way.

Creating our Custom Middleware

First of all we need to do is to create a new class and, to do that, we are going to follow some simple conventions. Our Middleware’s class and filename must end with the word ‘Middleware’:

public class CustomHeadersMiddleware
{
private readonly RequestDelegate next;

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

public Task Invoke(HttpContext context)
{
return next(context);
}
}

This code, by itself, is already a working middleware. It won’t do anything, except calling the next middleware, but it will work. :)

Now I expect you to be asking “Where is the Base Class?”, “You forgot to implement the interface?” or “Where are the overridden methods?” and believe, I asked myself these exact same questions. The answer is simple, the framework will understand and use our Middleware based on the conventions we followed:

  • The Middleware must have a Constructor that accepts one RequestDelegate
  • The Middleware must have a public Method that receives an HttpContext as parameter and returns a Task type.

Pretty straightforward, right?

Let’s implement some logic to our Middleware. Worth mentioning that there should be better ways to implement what we are doing here, but for the purpose of the Post Series, it should be enough.

public class CustomHeadersMiddleware
{
private readonly RequestDelegate next;

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

public async Task Invoke(HttpContext context)
{
if (context.Request.Method == "GET")
{
context.Response.Headers.Add("AzureCoderBlog", DateTime.UtcNow.ToString());
await next(context);
}
else
{
await context.Response.WriteAsync("Method not Allowed");
}
}
}

Basically what our middleware is doing is making sure that only requests with the “GET” method ever reach the next middleware and if any other method is used than the request will be shortcut.

Looks much better than having all this code inline right? But we are not ready yet!

Hookup with the Middleware Pipeline!

While this is a working code for a middleware we haven’t hooked this up to our pipeline. Let’s do this now. Back to our Startup.cs Class, in our configure method, we want our new middleware to be added like this:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseStaticFiles();

app.UseMiddleware<CustomHeadersMiddleware>();

app.UseMvcWithDefaultRoute();
}

Looking really good now, isn’t it? Let’s make look more like the rest of the middleware pipeline by creating one extension method for our middleware.

public static class CustomHeadersMiddlewareExtensions
{
public static IApplicationBuilder UseCustomHeaders(this IApplicationBuilder app)
{
return app.UseMiddleware<CustomHeadersMiddleware>();
}
}

And now we can set up our Middleware like this:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseStaticFiles();

app.UseCustomHeaders();

app.UseMvcWithDefaultRoute();
}

And that is it! We have a custom middleware setup and working. Our pipeline now should work like this:

Middleware Samples
Middleware Samples

And that is it for the first post of the Core MVC Pipeline. What do you think? Can you imagine some scenarios where changing the Middleware Pipeline would be useful for your application?

On the next post we will start talking about the Routing Middleware, where we can introduce some logic to change the Routing rules :)

Tot ziens!

Source:

Originally published at The Azure Coder.

--

--