In this article, we’ll explore different techniques on how to conditionally add middleware in ASP.NET Core applications.

To download the source code for this article, you can visit our GitHub repository.

Let’s start!

Conditional Middleware: Why and When?

Conditional middleware in ASP.NET Core is crucial for dynamically adapting our application behavior based on the runtime environment or specific request details. Let’s take a look at the key scenarios where it is essential.

In development, we use middleware that provides detailed error reports, logging, and debugging tools, which are often unsuitable for production due to performance and security concerns. For example, we typically use the Developer Exception Page (app.UseDeveloperExceptionPage()) only in development.

Using middleware, we can dynamically adjust application features based on feature flags during request processing. Middleware can control access based on user roles or authentication status, restrict parts of the application, or log activities.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

We can also customize middleware to respond to specific HTTP request details, such as IP addresses, query parameters, or headers, for tailored responses.

If you want to learn more about middleware in ASP.NET Core, we strongly suggest reading our ASP.NET Core Middleware article.

Conditionally Add Middleware Using Environment-Based Condition

One of the most straightforward and common use cases for conditional middleware involves tailoring middleware components to specific deployment environments, such as development, staging, or production. In ASP.NET Core, the IWebHostEnvironment interface provides information about the web hosting environment in which the application runs.

We’re going to create an ASP.NET Core Web API application to see how we can conditionally add middleware:

dotnet new webapi -n ConditionalMiddleware

Next, let’s create a custom middleware:

public class DevelopmentLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<DevelopmentLoggingMiddleware> _logger;

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

    public async Task InvokeAsync(HttpContext context)
    {
        _logger.LogInformation("Handling request: {RequestPath}", context.Request.Path);
        await _next(context);
        _logger.LogInformation("Finished handling request.");
    }
}

Here, we create a DevelopmentLoggingMiddleware class and inject the instance of RequestDelegate and ILogger in the constructor. Next, we define an InvokeAsync() method that accepts the HttpContext object.

ASP.NET Core automatically invokes the InvokeAsync() method when a request reaches this middleware. We log the incoming request path inside the method using the context object. Then, we invoke the _next() delegate by passing the HttpContext object, which invokes the next middleware in the HTTP request processing pipeline.

Now, let’s register the DevelopmentLoggingMiddleware middleware in the Program class:

if (app.Environment.IsDevelopment())
{
    app.UseMiddleware<DevelopmentLoggingMiddleware>();
}

Here, we invoke the IsDevelopment() method, which is part of the IWebHostEnvironment interface to check if the application runs in the “Development” environment.

The IsDevelopment() method returns true if we set the environment name to “Development”, which we can do by setting the ASPNETCORE_ENVIRONMENT environment variable to “Development” in the launchSettings.json file.

Then, we register the DevelopmentLoggingMiddleware class into the application’s middleware pipeline using the UseMiddleware() extension method.

Now, once we call an action from any controller, we will see the logs in the console window:

info: ConditionalMiddleware.DevelopmentLoggingMiddleware[0]
      Handling request: /api/WeatherForecast
info: ConditionalMiddleware.DevelopmentLoggingMiddleware[0]
      Finished handling request.

As we can see, the ASP.NET Core invokes the DevelopmentLoggingMiddleware class successfully.

Conditionally Add Middleware Using UseWhen() Extension Method

The UseWhen() extension method allows us to insert middleware into the pipeline based on a custom predicate that examines the incoming HTTP context. Using the UseWhen() method, we can execute middleware based on specific request conditions, such as headers, methods, or combinations of request attributes.

Now, let’s implement the UseWhen() extension method in the Program class:

app.MapWhen(context => context.Request.Headers.ContainsKey("X-Custom-Header") &&
context.Request.Method == "POST" && context.Request.Query.ContainsKey("active"), appBuilder =>
{
    appBuilder.UseMiddleware<DevelopmentLoggingMiddleware>();
});

Here, we first verify whether the incoming request contains a specific header. Next, we check if the request method is POST. This ensures that we only apply the middleware to POST requests. Lastly, we look for the presence of a specific query parameter.

If we meet all the above conditions, the pipeline will add the DevelopmentLoggingMiddleware class for that request.

Next, let’s define a POST endpoint in the WeatherForecastController class:

[HttpPost("post-weather")]
public IActionResult SaveWeather()
{
    return Ok();
}

Now, let’s test the implementation in Postman:

Postman result

As we can see, we include the query parameter active and add a header X-Custom-Header to the POST API request. Since the predicate evaluates to true, we receive the logs as before.

Conditionally Add Middleware Using MapWhen() Extension Method

The MapWhen() extension method splits the request pipeline based on the outcome of a provided predicate. We can use any predicate of type Func<HttpContext, bool> to direct requests to a new branch of the pipeline.

Now, let’s add the MapWhen() extension method to the Program class:

app.MapWhen(context => context.Request.Path.ToString() == "/api/WeatherForecast/update-weather", appBuilder =>
{
    appBuilder.UseMiddleware<DevelopmentLoggingMiddleware>();
    appBuilder.UseRouting();
    appBuilder.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
});

Here, we specify the condition to check if the request’s path matches /api/WeatherForecast/update-weather using a lambda expression. Then, we call the UseRouting() middleware to set up route matching. This step is essential as it helps determine the appropriate controller and action to handle the request based on its URL and other route data.

Lastly, we configure our endpoints with the UseEndpoints() middleware, where we use the MapControllers() method to map the incoming requests to their corresponding controller actions.

Moving on, let’s define a PUT endpoint in the WeatherForecastController class:

[HttpPut("update-weather")]
public IActionResult UpdateWeather()
{
    return Ok();
}

Here, we define a simple PUT endpoint to test our implementation, and we get the same output as before.

Difference Between MapWhen() and UseWhen() Extension Methods

We use the MapWhen() method to create a new middleware pipeline branch based on a specified condition. When this condition evaluates to true, we divert the request into this new branch, where we can apply a set of middleware configurations specifically tailored for that branch.

Importantly, after this branch completes its execution, the request does not automatically return to the main pipeline; it effectively concludes within the branched pipeline unless we explicitly configure it to continue.

In contrast, we use the UseWhen() method to allow conditional middleware execution without creating a separate pipeline. Instead, it conditionally applies middleware within the existing pipeline.

If the condition is met, we execute the specified middleware, and then the request continues down the main pipeline as if the UseWhen() method was just another middleware component in our configuration.

Conclusion

We emphasize the importance of choosing the appropriate branching technique based on the specific needs of the application—whether it’s isolating requests into a separate pipeline with MapWhen() method or integrating conditional middleware into the main pipeline with UseWhen() method.

In this article, we explored different methods to conditionally add middleware in ASP.NET Core.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!