SOFTWARE ARCHITECTURE & REFACTORING
How I Upgrade my Code-Style of MediatR Pipeline using .NET 6
I want to share with you how .NET 6 features with the new MediatR.Extensions.AttributedBehaviors NuGet package helped me polish my MediatR pipelines.
What is MediatR
It is a popular library for C# created by Jimmy Boggard, which can be described as a simple Mediator Pattern implementation in .NET. MediatR NuGet package currently has almost 50 million downloads and still rising.
Wiki page on its GitHub says:
MediatR is a low-ambition library trying to solve a simple problem — decoupling the in-process sending of messages from handling messages.
The word in-process is essential. This is not a Message Broker as Kinesis or RabbitMQ. This is about sending the message from one place and handling such message in a different place, but still in one process.
I use MediatR for decoupling Command and Query Requests. So In my case, the MediatR message is Query or Command, depending on the direction of data flow. This architectural design is called Command-Query Separation, and I am explaining it in the first paragraph of blog-post 3 CQRS Architectures that Every Software Architect Should Know.
You can see how I can handle HTTP GET by defining a GetWarehousesQuery as a MediatR request (message), and GetWarehousesQueryHandler as its Handler in the code sample below.
Warehouses Query Handling
Upgrade Code with .NET 6 Features
Records
Actually records are with us since .NET 5 and C# 9, but it is related to the topic of a blog post so that I will introduce them to you too. The feature called Records. Records introduced a shorter syntax for defining an immutable class. I find helpful when I am declaring Command or Query class.
Look at line 8. The definition of GetWarehousesQuery is now one-line code. I like to have Query and Command classes with theirs Handler classes in one place, which makes my code cleaner.
There is a drawback. You will not be able to change the value of any property after Record initialization. It is an immutable object, and the setter of every property is defined with the init keyword.
Global Usings
Another sugar feature of .NET 6 is Global Usings. The new keyword global before Using directive is making it possible not to include using MediatR in every file where we are implementing interfaces from the MediatR library.
Global Usings are project-scoped, so I recommend you to create just one dedicated file for them in the root of the project folder. Define your Global Usings only there. It can be a mess once you start to write global using in a random file.
Let’s create a GlobalUsings.cs file with this line of code:
global using MediatR;
Now, we can remove using MediatR; from the code sample.
File Scoped Namespaces
File Scoped Namespace means defining one namespace per file without curly brackets. So our code sample can be even cleaner. Take a look below.
Query and Handler in one short file. That’s how I like it.
Upgrade Code with MediatR Attributed Behaviors
MediatR Pipeline
Before moving to my last upgrade, I need to explain what MediatR Pipeline is. It is a pipeline defined for one MediatR Request (message). Pipelines execution starts with Mediator.Send method invocation and passing an object of MediatR Request into it.
Side note: if you enjoy small, practical ways to keep code clean like this, I share short free lessons in The Claude Code Memory Starter — a tiny email series you can join in one click.
Simple pipeline
Pipeline Behaviors
MediatR has a feature called Pipeline Behaviors. Behaviors can be attached to the pipeline by defining a proper interface implementation.
Pipeline with behaviors
You can attach Behavior before or after Handler, depends where you will invoke the next() delegate. A typical example can be a Validation Behavior that will validate the MediatR Request form before Handler.
Now some code. This is my new CreateWarehouseCommand pipeline. The code in the Handler is not vital for proof of the concept. What it does is to persist the new Warehouse record into the database.
Now let’s define Validation Behavior.
The ValidateCreateWarehouseBehavior will be executed before Handler because we are invoking next delegate after validation. If the await next() was at line 14 before if, Handler would be invoked before validation. This is how you can manage the position of Behavior towards the Handler or another Behavior in the pipeline.
Behaviors have to be registered into the DI container manually.
services.AddScoped>, ValidateCreatedWarehouseBehavior>();
By the way, if you have multiple Behaviors for one pipeline, their order of execution is defined by order of registrations into the DI container. This is an unpleasantly hidden way of behaving by MediatR. But I have a nice fix for it in the next paragraph.
MediatR Attributed Behaviors
The last upgrade is the extension library for MediatR called MediatR Attributed Behaviors. The library helps you remove the responsibility of manual DI registrations of Behaviors from the developer. Also, you can see the Behaviors of your pipeline right over the MediatR Request definition. Take a look at the last code sample.
Another power of this extension library is defining the order and scope of behaviors and passing it as other parameters.
[MediatRBehavior(typeof(MySingletonPipelineBehavior), serviceLifetime: ServiceLifetime.Singleton, order: 1)]
public class MyQuery : IRequest
Sources
Last Words
I appreciate your attention. I have a list of non-restricted links for each of my blog-post here. I am part of the Itixo company organizing the biggest .NET conference in Central Europe, Update Conference Prague.
Further Reading
From Monolith to Microservices in 5 Minutes
_a microservice architectural style is an approach to developing a single application as a suite of small services —…_levelup.gitconnected.com
Functional Organization of Folders & Screaming Architecture
_The architecture should scream the intent of the system — Uncle Bob_levelup.gitconnected.com
Layers in Software Architecture that Every Sofware Architect should Know
_“All architectures have the same objective - the separation of concerns. They all achieve it by dividing the software…_levelup.gitconnected.com
3 Domain-Centric Architectures Every Software Developer Should Know
_The first concern of the architect is to make sure that the house is usable, it is not to ensure that the house is made…_levelup.gitconnected.com
If this was useful, you might also like The Claude Code Memory Starter — a short free email series, one bite-sized lesson at a time.



Top comments (0)