ASP.NET Core 6.0 Middleware

Middleware is a component that is assembled into an application pipeline to handle requests and responses.

Middleware is linked one after the other, so each gets to choose whether to pass the request to the next middleware and work before and after the next component in the pipeline.

Middleware:

  • Sits between the requestor and the target.
  • Can directly modify the response.
  • Can log things.
  • Can use the data within the request to generate the response.

Take look at the diagram below:


Image Courtesy -Microsoft Doc

ASP.NET 6.0 implements a pipeline consisting of a series of middleware classes.

  • Requests filter down the pipeline until they reach a point where the middleware class creates a response.
  • Responses filter back through the middleware in reverse order until they reach the requestor.

Middleware is a great place to do the following:

  • Authorization
  • Authentication
  • Diagnostics
  • Error handling and logging

Each middleware consists of a request delegate. This is a specific kind of object in .NET that can pass execution control to the next object.

Let us create a simple Web API project. For this tutorial I’m using the tools below:

  1. Visual Studio Community Edition 2022 (64-bit) – Preview (Version 17.3.0 Preview 4.0)
  2. .NET 6.0
  3. Minimal Web API
  4. Swagger

Please find the program.cs file part of the minimal API:

var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment()) {
    app.UseSwagger();
    app.UseSwaggerUI();
}
//I have commented out the below codes as we are going to check Use(), Map() and Run()
/* var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateTime.Now.AddDays(index),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast"); */
app.Run();
/*internal record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

} */

If we run this project, you should get the below output.

Now let's create simple middleware that will return “Hello Readers!” as response. In Program.cs, we add a new middleware using Run() method like below. We normally call it inline middleware.

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment()) {
    app.Run(async context => {
        await context.Response.WriteAsync("Hello Readers!");
    });
    app.UseSwagger();
    app.UseSwaggerUI();
}

Run the application. You should get the below output:

While going through each line of code in Program.cs, generally we identify which parts of the code are considered middleware by looking at the methods used to add them to the pipeline. Those methods are Run(), Use() and Map().

Let's look at each method to understand the usage and differences between them.

Run()

This method only receives only context parameter and doesn’t know about the next middleware. These delegates are usually known as terminal delegates because they terminate or end the middleware pipeline.

Let us add another delegate as below and see how it behaves:

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment()) {
    app.Run(async context => {
        await context.Response.WriteAsync("Hello Readers!");
    });
    app.Run(async context => {
        await context.Response.WriteAsync("We are learning Middlware!");
    });
    app.UseSwagger();
    app.UseSwaggerUI();
}

Go head and run the application. You should get the below output:

The second delegate didn’t invoke here because the first one terminated the pipeline.

Use()

The whole idea behind middleware is to link one after another. Let us take a look at the Use() method, which helps us to chain the delegates one after the other.

This method will accept two parameters, context and next. Let us create a inline middleware using the Use() method:

/ Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment()) {
    app.Use(async (context, next) => {
        await context.Response.WriteAsync($ "Before Request {Environment.NewLine}");
        await next();
        await context.Response.WriteAsync($ "After Request {Environment.NewLine}");
    });
    app.Run(async context => {
        await context.Response.WriteAsync($ "Hello Readers!{Environment.NewLine}");
    });
    app.UseSwagger();
    app.UseSwaggerUI();
}

The output is given below:

This will show us that the middleware has been changed, one after another. Here, the await next() triggered the next middleware, which was implemented using the method Run().

Notes:

  • Don’t call next.invoke after the response has been sent to client.
  • Writing to the response body after calling next may cause a protocol violation.
  • Writing to the response body after calling next may cause the body format

Map()

Map extensions are used for branching the pipeline. Map extensions branch the request pipeline based on matching the given request path. If the request path starts with the given path, the branch is executed.

Let us see two middleware, as below:

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment()) {
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.Map("/BranchOne", MapBranchOne);
app.Map("/BranchTwo", MapBranchTwo);
app.Run();
static void MapBranchOne(IApplicationBuilder app) {
    app.Run(async context => {
        await context.Response.WriteAsync("You are on Branch One!");
    });
}
static void MapBranchTwo(IApplicationBuilder app) {
    app.Run(async context => {
        await context.Response.WriteAsync("You are on Branch Two!");
    });
}

The output should be as below:

http://localhost:1233/branchone

http://localhost:1234/branchtwo

We have covered the basics of middleware. In the upcoming tutorial, I will be covering custom middleware. Thank you for reading my article. Please leave your comments in the comment box below.