This is Part 4 of a four-part series. You might want to read Part 1, Part 2, and Part 3 first.

Welcome back! So far in this series, we've covered the basics of Middleware in .NET 6 applications, shown how to create custom Middleware classes, and discussed why the order of operations of the Middleware pipeline is so important.

In this final part of the series, we will show two ways to conditionally execute middleware in the pipeline: by using settings in the AppSettings.json file to determine whether or not to add the middleware to the pipeline in the first place, or by using the incoming request's data to conditionally execute a middleware piece already in the pipeline.

This way, or that way? Photo by Sigmund / Unsplash

Conditional Middleware based on AppSettings

Remember the TimeLoggingMiddleware class from Part 3? If you don't, here it is again.

using MiddlewareNET6Demo.Logging;
using System.Diagnostics;

namespace MiddlewareNET6Demo.Middleware
{
    public class TimeLoggingMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILoggingService _logger;

        public TimeLoggingMiddleware(RequestDelegate next, 
                                     ILoggingService logger)
        {
            _next = next;
            _logger = logger;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();

            await _next(context);

            watch.Stop();
            _logger.Log(LogLevel.Information, "Time to execute: " + watch.ElapsedMilliseconds + " milliseconds.");
        }
    }
}

We're going to say, in this post, that we only want to add the TimeLoggingMiddleware to the application pipeline under certain conditions. Those conditions might be when we're tracking down a bug, or the customer has complained of slowness in the app, or something else.

In order to conditionally add a piece of middleware to the pipeline, it's convenient to set up a section in our AppSettings.json file that can be read by the Program.cs file.

Let's add a section called MiddlewareSettings, and a property called UseTimeLoggingMiddleware, to our AppSettings.json file.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "MiddlewareSettings": {
    "UseTimeLoggingMiddleware": "true",
  }
}

We also need a C# class that can hold the value of these settings. By convention, the class name should match the section name MiddlewareSettings, and the property name should match the item name UseTimeLoggingMiddleware.

namespace MiddlewareNET6Demo
{
    public class MiddlewareSettings
    {
        public bool UseTimeLoggingMiddleware { get; set; }
    }
}

Then, in our Program.cs file, we can read that section of AppSettings.json and map it to an instance of the MiddlewareSettings C# class:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddTransient<ILoggingService, LoggingService>();

var app = builder.Build();

var middlewareSettings = builder.Configuration.GetSection("MiddlewareSettings").Get<MiddlewareSettings>();

//...Rest of Program.cs

Using the new middlewareSettings instance, we can add the TimeLoggingMiddleware to the pipeline only if UseTimeLoggingMiddleware is true:

//...Rest of Program.cs

if(middlewareSettings.UseTimeLoggingMiddleware)
    app.UseTimeLoggingMiddleware();
    
//...Rest of Program.cs

In this way, we can control which middleware is active in the pipeline based on application-wide settings. This might make it easier, for example, to log execution times when trying to improve app performance.

Conditional Middleware based on Request URL

This next way to conditionally run middleware is a bit of a cheat; the middleware here will always be added to the pipeline, but will not always do anything other than pass execution to the next middleware piece.

Say we have a new middleware class called CultureMiddleware:

using System.Globalization;

namespace MiddlewareNET6Demo.Middleware
{
    public class CultureMiddleware
    {
        private readonly RequestDelegate _next;

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

        public async Task InvokeAsync(HttpContext context)
        {
            var cultureQuery = context.Request.Query["culture"];
            if (!string.IsNullOrWhiteSpace(cultureQuery))
            {
                var culture = new CultureInfo(cultureQuery);

                CultureInfo.CurrentCulture = culture;
                CultureInfo.CurrentUICulture = culture;
            }
            await _next(context);
        }
    }
}

Note that this middleware only does some actual work if there exists a culture request parameter in the incoming request. If that parameter does exist, the middleware sets the current culture of the app to the incoming parameter's value.

For example, if we submitted the following request:

http://ourDemoSite.com/Users/174/Details?culture=fr-FR

The CultureMiddleware would set the culture of the app to fr-FR, which is the culture for France, and then normal processing would take the user to whatever page or location is represented by the Users/174/Details portion of the URL.

If the request was

http://ourDemoSite.com/Invoices/Details/1235267376?culture=uk

Then the middleware sets the culture to uk (Ukrainian) and then processes normally.

If, on the other hand, the incoming request was

http://ourDemoSite.com/Subscriptions/v4nd37c/matthew-jones

Then the middleware, effectively, does nothing, and the culture of the app remains the default culture.

We can use the entirety of the incoming Request for this kind of logic. We could, for example, only execute the code for the TimeLoggingMiddleware if the incoming request contained certain values. If we were using an MVC app, for example, maybe we only want to record execution times for a single controller. In Razor Pages, maybe it's only a certain page that's having problems. Maybe only one endpoint in an API is slow. In any of these cases, we could use this methodology to target the TimeLoggingMiddleware to exactly what we want to record.

In short, this method of conditionally executing middleware gives us finer-grain control over what to execute than the AppSettings.json solution does, at the potential cost of always needing to insert the middleware into the pipeline.

Thank You for Reading!

Thanks for reading this series! If you enjoyed it, please check out some of my other posts in about similar topics:

Dependency Injection in .NET 6 - Intro and Background
What is dependency injection, and why should we bother?
Bite-Size .NET 6 - DateOnly and TimeOnly
Learn about DateOnly and TimeOnly, structs that are new in .NET 6, and how they are used!
Creating a Dapper Helper C# Class to Generate Parameterized SQL
Let’s create a class that can generate parameterized SQL for INSERT and UPDATE statements using Dapper!
Solitaire in Blazor Part 1 - Overview
The biggest time waster in history, now in C# and Blazor WebAssembly!

Happy Coding!