Skip to content

02 - Pipeline

Request Pipeline

  • Middleware is software that's assembled into an app pipeline to handle requests and responses
    • 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
    • Modify the request and response on the way
  • Request delegates are used to build the request pipeline
    • Configured with run, map and use extension methods

Pipeline

Simplest aspnet middleware/app

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Run(
    async httpContext =>
    {
        await httpContext.Response.WriteAsync("Hello, World!");
    }
);

app.Run();

Chain multiple request delegates together with Use. The next parameter represents the next delegate in the pipeline.

 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
28
29
30
31
32
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var rnd = new Random();

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.
});

app.Use(async (httpContext, next) =>
{
    if (rnd.NextDouble() < 0.5)
    {
        await next.Invoke();
    }
    else
    {
        // short-circuit
        await httpContext.Response.WriteAsync("Shortcut executed");
    }
});

app.Run(
    async httpContext =>
    {
        await httpContext.Response.WriteAsync("Hello, World!");
    }
);

app.Run();

Don't call next.Invoke after the response has been sent to the client (terminal middleware).
Check httpContext.Response.HasStarted (true after response headers have been sent to client)!

Branching middleware

 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
28
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/do-first", HandleDoFirst);
app.Map("/do-second", HandleDoSecond);

app.Run(async context =>
{
    await context.Response.WriteAsync("<h1>Hello from non-Map delegate. </h1>");
});

app.Run();

static void HandleDoFirst(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("First");
    });
}

static void HandleDoSecond(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Second");
    });
}

When Map is used, the matched path segments are removed from HttpRequest.Path and appended to HttpRequest.PathBase for each request.

Pipeline is configured with methods

  • Run – short circuit, return
  • Map - branch
  • Use – Chaining middleware-s together

MapWhen branches the request pipeline based on the result of the given predicate.

  • predicate of type Func<HttpContext, bool>
1
app.MapWhen(context => context.Request.Query.ContainsKey("test"), HandleBranch);

UseWhen also branches the request pipeline based on the result of the given predicate, but branch is rejoined to the main pipeline (if it doesn't short-circuit).

1
2
app.UseWhen(context => context.Request.Query.ContainsKey("test"),
    appBuilder => HandleBranchAndRejoin(appBuilder));

Custom middleware

 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
public class CustomMiddleware
{
    private readonly RequestDelegate _next;

    public CustomMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // Call the next delegate/middleware in the pipeline.
        await _next(context);
        // do something
    }
}

// extension method for registering
public static class CustomMiddlewareExtensions
{
    public static IApplicationBuilder UseCustomMiddleware(
        this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<CustomMiddleware>();
    }
}

And inject it into pipeline

1
2
app.UseCustomMiddleware();
app.Run();

NB! Dependncy injection and scoped dependencies

Middleware is singleton, use constructor DI.
For scoped and transient DI use InvokeAsync additional parameters.

Middleware ordering

Middlewares are called in order they are added. So the ordering is important/critical for severel reasons - security, performance, and functionality.

1
2
3
4
5
6
7
8
9
app.UseExceptionHandler("/Home/Error"); // Call first to catch exceptions
                                        // thrown in the following middleware modules.

app.UseStaticFiles();                   // Return static files and end pipeline.

app.UseIdentity();                      // Authenticate before you access
                                        // secure resources.

app.UseMvc();                           // Add MVC to the request pipeline.

ASP.NET Middleware

Typical ASP.NET Core MVC pipeline

Pipeline

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// catch errors, redirect to error page
app.UseExceptionHandler("/Error");
//  redirects HTTP requests to HTTPS
app.UseHsts();
app.UseHttpsRedirection();
// returns static files and short-circuits 
app.UseStaticFiles();
// GDPR
app.UseCookiePolicy();
// endpoint routing
app.UseRouting();
// authenticate user
app.UseAuthentication();
// check the rights
app.UseAuthorization();
// maintain session state
app.UseSession();
// mvc routing
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
// razor pages support
app.MapRazorPages();

Endpoint middleware...

Pipeline