Skip to content

Case 11: The Global Slowdown - Inefficient Middleware

The Story

Imagine you want to add a feature to your ASP.NET Core API: logging every incoming request to a text file. You decide to create a custom middleware for this.

After deploying the new middleware, the entire application becomes sluggish. Every API call is a little bit slower, even the simple ones that don't touch the database.

The Problem Code (Blocking I/O in Middleware)

csharp
// In the Middleware class
public class InefficientLoggingMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task InvokeAsync(HttpContext context)
    {
        var logMessage = $"[{DateTime.Now}] Received request: {context.Request.Path}\n";

        // MISTAKE: Performing a synchronous file write, which blocks the request thread.
        File.AppendAllText("requests.log", logMessage);

        // Pass the request to the next middleware in the pipeline
        await _next(context);
    }
}

// In Program.cs
app.UseMiddleware<InefficientLoggingMiddleware>();

Why is this a problem?

  1. What is the Middleware Pipeline? Every HTTP request to your application goes through a series of middleware components. Each middleware can process the request and then pass it to the next one.

  2. Global Impact: Code inside a middleware runs for every single request. Any inefficiency in a middleware becomes a global bottleneck for the entire application.

  3. Blocking I/O: As we've learned, synchronous I/O operations like File.AppendAllText() block the thread. The thread is frozen, waiting for the operating system to finish writing the data to the disk.

  4. The Result: This one innocent-looking line of code adds a small delay (waiting for the disk) to every API call. This dramatically reduces the application's throughput (how many requests it can handle per second) and its ability to scale.

The Solution: Use Asynchronous I/O and Specialized Services

The key is to keep your middleware pipeline fully asynchronous and to use tools designed for high-performance tasks.

Here is the better code:

csharp
public class EfficientLoggingMiddleware
{
    private readonly RequestDelegate _next;
    // Inject ILogger, a service highly optimized for logging.
    private readonly ILogger<EfficientLoggingMiddleware> _logger;

    public EfficientLoggingMiddleware(RequestDelegate next, ILogger<EfficientLogging_Middleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // The best way: Use ILogger.
        // Logging frameworks (like Serilog or NLog) will manage writing logs
        // to a file efficiently on a separate background thread.
        _logger.LogInformation("Received request: {Path}", context.Request.Path);

        await _next(context);
    }
}

What We Gained

  1. No More Blocking: The request thread is no longer frozen by the file-writing operation. It can immediately move on to the next middleware or handle another request.
  2. Throughput Restored: The application's ability to handle many concurrent requests is restored, allowing the system to perform well under load.
  3. Using the Right Tool for the Job: The task of logging is delegated to a specialized framework that is designed to handle thousands of log messages per second without impacting the performance of the main application threads.

The Golden Rule

"The middleware pipeline is the main highway for every request."

Never perform synchronous, blocking I/O operations inside a middleware. Be extremely careful about the performance of any code you add to the middleware pipeline, because its cost is multiplied by the total number of requests your application receives.