When we talk about “clean architecture” in ASP.NET Core, it is easy to end up with too many layers and too many rules, and the simple business logic becomes hard to read. This is exactly where Chain of Responsibility can help, especially when the business flow is something like approvals.
The chain-of-responsibility pattern is basically a smarter version of the classic if / else if / else tree. Instead of one method that knows everything, you pass the request through a list of handlers. Each handler decides, “can I handle this?”, if yes, it stops, if not, it forwards to the next one. The pattern is described as a chain of processing objects where each one handles certain commands and passes the rest forward. (Wikipedia)
In this repo, the “request” is a Purchase Order and the “handlers” are approvers (Employee, Supervisor, General Manager). (GitHub)
With CQRS, we already separate the world into:
- Commands: change state (Create, Approve, Reject)
- Queries: read state (GetById, List)
MediatR fits nicely because each command/query becomes a single request with a handler. What I like here is that the approval chain stays a domain concern, not something mixed inside controllers or endpoints.
Also, MediatR supports “pipeline behaviors”, which are like middleware around handlers (logging, validation, transactions). The base interface is IPipelineBehavior<,>. (GitHub)
So you get two “chains” in the project, but for different responsibilities:
- Pipeline behaviors: cross-cutting technical concerns
- ApprovalChain: business decision flow
What the project does (in simple words)
This project shows:
- Carter endpoints (instead of controllers) for a clean API surface (GitHub)
- CQRS with MediatR handlers (one handler per feature) (GitHub)
- Behaviors for logging, validation, transactions (GitHub)
- A domain ApprovalChain using specifications to keep rules readable (GitHub)
- Swagger/OpenAPI so you can test fast from the browser (Microsoft Learn)
Repo:
https://github.com/stevsharp/ChainOfResponsibilityWithMediatR
(GitHub)
The approval chain idea (how to think about it)
Instead of writing something like:
- if price < 100 and amount == 1, employee approves
- else if price < 200 and amount <= 2, supervisor approves
- else GM approves
You create small classes:
EmployeeApprover
SupervisorApprover
GeneralManagerApprover
And put them in an ordered list inside ApprovalChain.
Each approver checks rules using a Specification, so the rule becomes easy to reuse and test. (GitHub)
The big win for me is that later, when the company says “we need 2 more approval levels”, you add 2 new approvers, you do not rewrite one huge method and risk breaking everything.
Validation, Swagger, and why they are here
Validation is done with FluentValidation, and validators are auto-registered from an assembly (so you do not manually register 10 validators one by one). (docs.fluentvalidation.net)
Swagger is there for a simple reason, you can test your endpoints instantly and see the contracts. Swashbuckle is the most common way to do that in ASP.NET Core. (Microsoft Learn)
In this article I focused on the idea and why this pattern fits very well with CQRS.
In the next article, I will go deeper and show the actual implementation step by step, how the Carter endpoints connect to MediatR, how the commands and queries are structured, how the pipeline behaviors are added (logging, validation, transaction), and finally how the ApprovalChain and Specifications work together in the domain.
The goal is to make it a real “breakthrough” article where you can copy the structure and apply it to your own projects with minimal changes.
- How I Built a CQRS Approval Flow with MediatR, Carter, FluentValidation, and SQLite:
https://coderlegion.com/8906/chain-of-responsibility-inside-cqrs-with-mediatr-a-small-practical-example
- How to use Carter in a CQRS API, advantages and alternatives :
https://coderlegion.com/9342/carter-in-a-cqrs-api-advantages-and-alternatives
References
- GitHub repo:
ChainOfResponsibilityWithMediatR (GitHub)
- Chain-of-Responsibility pattern definition and overview (Wikipedia)
- MediatR
IPipelineBehavior<,> interface (pipeline behaviors concept) (GitHub)
- Carter framework (modular endpoints on top of ASP.NET Core) (GitHub)
- FluentValidation DI registration (
AddValidatorsFromAssembly...) (docs.fluentvalidation.net)
- Microsoft docs, Swashbuckle and Swagger/OpenAPI setup (Microsoft Learn)