MediatR CQRS Example with Azure Functions in C# (.NET 8, Clean Architecture)
An Azure Functions v4 solution on .NET 8 that demonstrates MediatR and CQRS (Command Query Responsibility Segregation) with a clean multi-layer architecture and MediatR pipeline behaviours for logging and performance monitoring. The solution implements user creation and retrieval through CQRS commands and queries, wired with two MediatR pipeline behaviours.
Architecture
HTTP Request
│
▼
Mediat.AzureFunction ← Azure Functions host (HTTP triggers)
│ sends IRequest via IMediator
▼
Mediat.Business ← Command/Query handlers
│ calls repository
▼
Mediat.Data ← In-memory repository implementation
│ uses model
▼
Mediat.Model ← User entity
Pipeline behaviours (Mediat.Infrastructure) wrap every handler call:
LoggingBehavior → PerformanceMonitoringBehavior → Handler
Projects
| Project | Description |
|---|---|
Mediat.AzureFunction |
Azure Functions host — HTTP triggers for create and get user |
Mediat.Business |
CreateUserCommand + handler, GetUserQuery + handler |
Mediat.Data |
IUserRepository interface and in-memory UserRepository |
Mediat.Infrastructure |
LoggingBehavior and PerformanceMonitoringBehavior |
Mediat.Model |
User entity (Id, Name, Email) |
HTTP Endpoints
| Function | Method | Route | Description |
|---|---|---|---|
create-user-function |
POST | /api/user |
Creates a new user from JSON body |
get-user-function |
GET | /api/user/{id} |
Retrieves a user by integer ID |
Request Body (POST)
{
"name": "Alice",
"email": "alice@example.com"
}
Response (POST / GET)
{
"id": 1,
"name": "Alice",
"email": "alice@example.com"
}
Pipeline Behaviours
| Behaviour | Description |
|---|---|
LoggingBehavior<TRequest, TResponse> |
Logs Handling {request type} before and Handled {response type} after each handler call |
PerformanceMonitoringBehavior<TRequest, TResponse> |
Logs a warning if handler execution exceeds 500 ms |
Data Storage
UserRepository uses a static in-memory List<User>. IDs are assigned sequentially (count + 1). Data is not persisted between application restarts.
Requirements
- .NET 8 SDK
- Azure Functions Core Tools v4
- A
local.settings.jsonfile inMediat.AzureFunction(not committed to source control)
Minimum local.settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
}
}
NuGet Packages (Mediat.AzureFunction)
| Package | Version |
|---|---|
MediatR.Extensions.Microsoft.DependencyInjection |
11.0.0 |
Microsoft.Azure.Functions.Worker |
2.0.0 |
Microsoft.Azure.Functions.Worker.Extensions.Http |
3.3.0 |
Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore |
2.0.1 |
Microsoft.Azure.Functions.Worker.Sdk |
2.0.2 |
Newtonsoft.Json |
13.0.3 |
Running Locally
cd Mediat/Mediat.AzureFunction
func start --port 7094
Usage
# Create a user
curl -X POST http://localhost:7094/api/user \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com"}'
# Retrieve a user
curl http://localhost:7094/api/user/1
GET /api/user/{id} returns 404 Not Found when no user has that ID, and both endpoints return 400 Bad Request for an invalid request.
Project Structure
Mediat/
├── Mediat.AzureFunction/
│ ├── CreateUserFunction.cs # POST /api/user
│ ├── GetUserFunction.cs # GET /api/user/{id}
│ └── Program.cs # MediatR and DI registration
├── Mediat.Business/
│ ├── Commands/UserCommand/
│ │ ├── CreateUserCommand.cs
│ │ └── CreateUserCommandHandler.cs
│ └── Queries/UserQuery/
│ ├── GetUserQuery.cs
│ └── GetUserQueryHandler.cs
├── Mediat.Data/
│ ├── Contract/IUserRepository.cs
│ └── Repository/UserRepository.cs # In-memory implementation
├── Mediat.Infrastructure/
│ ├── LoggingBehavior.cs
│ └── PerformanceMonitoringBehavior.cs
├── Mediat.Model/
│ └── User.cs # Id, Name, Email
└── Mediat.sln
Notes
Mediat.AzureFunctionreferencesMediatR.Extensions.Microsoft.DependencyInjection11.0.0, whileMediat.BusinessandMediat.InfrastructurereferenceMediatR12.4.1, which includes theAddMediatRregistration itself. The separate DI extension package is not needed with MediatR 12 and later.- Pipeline behaviours run in registration order, so
LoggingBehaviorwrapsPerformanceMonitoringBehavior, which wraps the handler.
Related Examples
- Keyed dependency injection in Azure Functions
- Azure Functions middleware
- Durable Functions orchestration
- Back to all C# and .NET code examples
Documentation links
Read all C# and .NET Code Examples guides online · Project overview · All developer projects