Middleware is an important concept to grasp when developing an ASP.NET Core application and can be a powerful ally when dealing with the HTTP request pipeline.

Middleware are components that are inserted into the request pipeline to handle requests and responses, and from the Microsoft documentation, each component:

  • Chooses whether to pass the request to the next component in the pipeline.
  • Can perform work before and after the next component in the pipeline is invoked.

The 3 ways of inserting middleware into your Startup class are by using either: app.Run(), app.Use() or app.Map*() and they can be defined inline or in separate classes.

// The first instance of app.Run short circuits the request
// more on short circuiting later
app.Run(async context =>
{
	// As soon as this is run, it writes a response and ends the pipeline
    await context.Response.WriteAsync("Hello, World!");
});

// You can chain multiple `app.Use()` middleware components together,  
// calling `next.Invoke()` invokes the next piece of Middleware in the pipeline
app.Use(async (context, next) =>
{
    // Do work that doesn't write to the Response.
    await next.Invoke();
    // Do logging or other work that doesn't write to the Response.
});

// This is the delegate that is called if `app.Map()` component is matched
private static void HandleMapTest1(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}
// This calls the HandleMapTest1 method when the route matches  
// i.e 'https://localhost:5001/map1'
app.Map("/map1", HandleMapTest1);

Ordering of the Request Pipeline

It's important to understand the order in which code gets executed in the request pipeline. For example, take the following configure method with two app.Use components:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.Use(async (context, next) =>    // Component 1
    {
        Console.WriteLine("----1----");
        await next.Invoke();
        Console.WriteLine("----2----");
    });

    app.Use(async (context, next) =>    // Component 2
    {
        Console.WriteLine("----3----");
        await next.Invoke();
        Console.WriteLine("----4----");
    });

    app.UseStaticFiles();
}

In the preceding example, if you had created a new MVC app by using dotnet new mvc in your terminal or through Visual Studio, then you would have a wwwroot/images directory with some SVG files in. If you run up the app and enter http://localhost:5000/images/banner1.svg and then inspect the output in the console, we can workout the order of execution, which displays:

  • ----1----
  • ----3----
  • ----4----
  • ----2----

By that output we can tell that, first the execution flows into component 1 and prints 1 to the console. Then, component 1, calls next.Invoke(), which invokes component 2, that then prints 3 to the console. Component 2 then calls next.Invoke() which invokes app.UseStaticFiles(); which does its work of looking for static files, and then when it's found its file it short circuits (more on short circuiting later I promise) the pipeline and then starts on the response, so the app.UseStaticFiles() component passes the execution back to component 2, which carries on executing from the line after await next.Invoke();, so then 4 is printed to the console, that then that passes execution back to component 1 which prints 2 to the console.

All that is very wordy and hard to understand, the below image is from the Microsoft docs and it is much easier to understand for most:

alt text

Short-Circuiting the Pipeline

Short-circuiting occurs when a middleware component chooses not to call next.Invoke(). This can be for a variety of reasons, but an obvious one is the app.UseStaticFiles() component. Once the app.UseStaticFiles() component has found the file that is requested, there is no need to call the next piece of Middleware as it just sends the file back to caller, so returns from the method and doesn't call next.Invoke(). We can demo this by changing our Configure method to be the following:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.Use(async (context, next) =>    // Component 1
    {
        Console.WriteLine("----1----");
        await next.Invoke();
        Console.WriteLine("----2----");
    });

    app.Use(async (context, next) =>    // Component 2
    {
        Console.WriteLine("----3----");
        await next.Invoke();
        Console.WriteLine("----4----");
    });

    app.UseStaticFiles();
    
    app.Use(async (context, next) =>    // Component 3
    {
        Console.WriteLine("----5----");
        await next.Invoke();
        Console.WriteLine("----6----");
    });
}

You can see above that component 3 is after the app.UseStaticFiles() component, which means that component 3 will only get called if app.UseStaticFiles() fails to find a file. If app.UseStaticFiles() finds a file, then like we said above, it short-circuits the pipeline and returns the response.

You can test this by running up the application and entering these 2 URLs in your browser:

  • http://localhost:5000/images/banner1.svg - this will short-circuit the pipeline
  • http://localhost:5000/images/banner999.svg - this will pass through because the static file does not exist

I would strongly recommend having a look on Github at how the built in Middleware components work. Here is the link for the app.UseStaticFiles() that has been talked about here: https://github.com/aspnet/StaticFiles/blob/master/src/Microsoft.AspNetCore.StaticFiles/StaticFileMiddleware.cs

For more information on Middleware then the Microsoft docs can be found here: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/