Hello, in this article we will examine the middleware structure in ASP.NET Core. Middleware is used to perform the operations between the request and response process and to guide the process when the application runs.
As seen in the image above, the Middleware 1 next() method runs the next layer, Middleware 2. Middleware 2 completes its operations and Middleware 3 is executed with the next() method. When Middleware 3 completes its operations, since there is no other Middleware to run, it returns the result of the operation to Middleware 2 layer and Middleware 2 to Middleware 1 layer.
When each middleware completes its own process, it calls the next layer with the next() method and waits for a response from the next layer without finishing its own process. This spiral structure is called a pipeline. The middleware should finish processing and send a request until the entire pipeline is complete.
Let’s examine the Middleware issue by creating a sample ASP.NET Core Web API (.NET 6) project.
1
dotnet new webapi -o SampleMW
When we created the project, some services and middleware were added as standard in Program.cs. When we examine these codes, we can see the middleware that can be added to the runtime with the app variable added in the WebApplication type.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using SampleMW;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// 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();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
This is how we can add middleware. We can also create our own middleware that can kick in at runtime. There are methods added so that we can run middleware that we can add in this way.
UseMiddleware Method
Let’s create a class called CustomMiddleware. In this class, after the _next object of RequestDelegate type that we inject in the Constructor is triggered, the next middleware is called with the Invoke method when the related operations are completed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace SampleMW
{
public class CustomMiddleware
{
private readonly RequestDelegate _next;
public CustomMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
//logic
await context.Response.WriteAsync("|CustomMiddleware|");
await _next.Invoke(context);
}
}
}
1
app.UseMiddleware<CustomMiddleware>();
Use Method
When the operations are completed, it calls the next middleware. When the processes of the next middleware are completed, the process continues.
1
2
3
4
5
6
7
8
9
10
11
12
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("|USE|");
await next.Invoke();
await context.Response.WriteAsync("|USE-RETURN|");
});
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("|USE-NEXT|");
await next.Invoke();
});
Run Method
It prevents the next intermediate layer from working. In this case, the pipeline will be terminated. This interruption is called a short circuit. Let’s add the Run method to the above code block and run it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("|USE|");
await next.Invoke();
await context.Response.WriteAsync("|USE-RETURN|");
});
app.Run(async c =>
{
c.Response.WriteAsync("|RUN|");
});
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("|USE-NEXT|");
await next.Invoke();
});
As you can see, the return was made without running the Use method containing the Use-Next text.
Map Method
We use it when we want to run different middleware by filtering according to the path of the request from the client.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
app.Map("/weatherforecast", builder =>
{
builder.Run(async x=> await x.Response.WriteAsync("|RUN MAP - WEATHERFORECAST|"));
});
app.Map("/home", builder =>
{
builder.Use(async (context, next) =>
{
await context.Response.WriteAsync("|USE MAP - HOME|");
await next.Invoke();
await context.Response.WriteAsync("|USE MAP RETURN - HOME|");
});
});
MapWhen Method
With the MapWhen method, filtering can be done according to any feature of the request from the client.
1
2
3
4
5
6
7
8
9
10
app.MapWhen(x => x.Request.Path.Equals("/home") && x.Request.Method.Equals("GET"), builder =>
{
builder.Run(async x => await x.Response.WriteAsync("|RUN MAPWHEN - HOME|"));
}); app.MapWhen(x => x.Request.Path.Equals("/home") && x.Request.Method.Equals("GET"), builder =>
{
builder.Run(async x => await x.Response.WriteAsync("|RUN MAPWHEN - HOME|"));
});app.MapWhen(x => x.Request.Path.Equals("/home") && x.Request.Method.Equals("GET"), builder =>
{
builder.Run(async x => await x.Response.WriteAsync("|RUN MAPWHEN - HOME|"));
});