.Net Core
Building Middleware

What are the different ways to build a Middleware pipeline in asp.net core? (With Examples)

In ASP.NET Core, there are several ways to build a middleware pipeline. Middleware is used to handle requests and responses, and the pipeline is responsible for determining the flow of these requests through various components. You can build a middleware pipeline using built-in middleware, custom middleware, or third-party middleware.

Ways to Build a Middleware Pipeline

  1. Using Built-in Middleware (Fluent API): The most common way to build the middleware pipeline is by using the fluent API provided in the Configure method of the Startup class. The middleware components are added in sequence, and each one can either handle the request or pass it on to the next component.

    Example:

    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.UseStaticFiles();  // Middleware to serve static files (CSS, JS, Images, etc.)
            app.UseRouting();      // Middleware for routing
            
            // Custom logging middleware
            app.Use(async (context, next) =>
            {
                Console.WriteLine($"Request Path: {context.Request.Path}");
                await next();  // Call the next middleware
            });
     
            app.UseAuthorization();  // Middleware to check authorization
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();  // Endpoint routing to controllers
            });
        }
    }

    In this example:

    • UseStaticFiles: Serves static files.
    • UseRouting: Sets up routing middleware.
    • Use: Adds a custom middleware that logs the request path.
    • UseAuthorization: Adds middleware to check authorization.

  1. Using Custom Middleware Class: You can define a custom middleware class that encapsulates specific logic. This is useful when you want to modularize functionality and reuse middleware across different projects.

    Example:

    Custom Middleware Class

    public class CustomLoggingMiddleware
    {
        private readonly RequestDelegate _next;
     
        public CustomLoggingMiddleware(RequestDelegate next)
        {
            _next = next;
        }
     
        public async Task InvokeAsync(HttpContext context)
        {
            Console.WriteLine($"Custom Middleware: Handling request {context.Request.Path}");
            await _next(context);  // Call the next middleware
            Console.WriteLine($"Custom Middleware: Finished handling request");
        }
    }

    Registering the Custom Middleware in Startup

    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.UseMiddleware<CustomLoggingMiddleware>();  // Register custom middleware
     
            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }

  1. Using Run Method: The Run method is used to add terminal middleware to the pipeline. Terminal middleware does not pass the request to the next component; it processes the request and ends the pipeline.

    Example:

    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.Run(async context =>
            {
                await context.Response.WriteAsync("Terminal middleware. No further processing.");
            });
        }
    }
    • In this case, the request will be handled by the Run middleware, and no further middleware in the pipeline will be executed.

  1. Chaining Multiple Middlewares with Use: The Use method allows chaining multiple middleware components in the request pipeline. Each Use can conditionally call next() to pass control to the next middleware or stop the pipeline.

    Example:

    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.Use(async (context, next) =>
            {
                Console.WriteLine("First Middleware - Before Next");
                await next();  // Pass control to the next middleware
                Console.WriteLine("First Middleware - After Next");
            });
     
            app.Use(async (context, next) =>
            {
                Console.WriteLine("Second Middleware - Before Next");
                await next();
                Console.WriteLine("Second Middleware - After Next");
            });
     
            app.Run(async context =>
            {
                await context.Response.WriteAsync("Final response from Run middleware");
            });
        }
    }

    In this example:

    • The first middleware logs before and after calling the next middleware.
    • The second middleware does the same.
    • The Run middleware produces the final response, terminating the pipeline.

  1. Conditionally Adding Middleware: Middleware can be added conditionally based on the environment or request properties.

    Example:

    public class Startup
    {
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();  // Only for development environment
            }
     
            app.Use(async (context, next) =>
            {
                if (context.Request.Path.StartsWithSegments("/api"))
                {
                    Console.WriteLine("Handling API request");
                }
                await next();
            });
     
            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }

    Here, the UseDeveloperExceptionPage middleware is added only in the development environment, and another middleware checks if the request path starts with /api.


  1. Middleware via Extensions (Factory Pattern): Instead of directly calling UseMiddleware, you can encapsulate middleware registration logic in an extension method, which makes the code cleaner and reusable.

    Example:

    Extension Method for Middleware

    public static class CustomMiddlewareExtensions
    {
        public static IApplicationBuilder UseCustomLogging(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<CustomLoggingMiddleware>();
        }
    }

    Using the Extension Method in Startup

    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.UseCustomLogging();  // Using the custom middleware extension
     
            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }

  1. Using the Map Method: The Map method is used to branch the middleware pipeline based on the request path. This allows you to create different pipelines for different request paths.

    Example:

    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.Map("/branch", branchApp =>
            {
                branchApp.Run(async context =>
                {
                    await context.Response.WriteAsync("This is the /branch endpoint");
                });
            });
     
            app.Run(async context =>
            {
                await context.Response.WriteAsync("This is the default endpoint");
            });
        }
    }

    In this example:

    • Requests starting with /branch are handled by a specific pipeline that returns a custom response.
    • Other requests are handled by the default pipeline.

Conclusion

In ASP.NET Core, building a middleware pipeline is a flexible process, and there are multiple ways to structure it:

  • You can use built-in middleware for common tasks like routing, static files, and authentication.
  • You can build custom middleware for more specialized tasks.
  • Middleware can be conditionally executed, chained together, or branched for different routes.

With this flexibility, ASP.NET Core gives developers full control over how requests are processed and responses are generated, making the middleware pipeline a powerful tool for building web applications.