Round #703
10 May 2026, 15:08· csharp· Meta-Llama-3.3-70B-Instruct· r13-clean-hints
- Complexity
- band 1
- ZP / UO / OBS
- 13 / 0 / 0
- Journey weight
- 872
- Lightshift ΔL
- -0.08
Prompt
Write a custom IPipelineBehavior<TRequest, TResponse> for MediatR that captures FluentValidation errors and re-throws as a structured ValidationException.
Zero-point index (what the engine surfaced)
ZPI-ZPI-0002 | 2026-05-10T15:08:38Z
Write a custom IPipelineBehavior<TRequest, TResponse> for MediatR that captures FluentValidation errors and re-throws as a structured ValidationException.
ZERO POINT — 13 entries (structural invariants)
● hsl(176.24, 100%, 32%) INotificationHandler<TNotification> [contract]
● hsl(351.03, 100%, 32%) IRequestHandler<TRequest, TResponse> [contract]
● hsl(256.59, 100%, 32%) INotification [contract]
● hsl(276.16, 100%, 32%) IRequest<TResponse> [contract]
● hsl(237.58, 100%, 32%) IPipelineBehavior<TRequest, TResponse> [contract]
● hsl(290.83, 100%, 32%) IRequestPreProcessor<TRequest> [contract]
● hsl(314.89, 100%, 32%) IRequestPostProcessor<TRequest, TResponse> [contract]
● hsl(159.80, 100%, 33%) IValidator<T> [contract]
● hsl(240.67, 100%, 33%) AbstractValidator<T> [contract]
● hsl(175.80, 100%, 33%) IValidatableObject [contract]
● hsl(328.68, 100%, 33%) IValidatorFactory [contract]
● hsl(145.31, 100%, 33%) ValidationResult [contract]
● hsl(39.29, 100%, 33%) ValidationContext<T> [contract]
UNOBSERVED — 0 entries (implied truths)
OBSERVED — 0 entries (explicit in request)
COMPLEXITY │ 13 entries │ spread: 0° │ band: MEDIUM
Baseline response (no ZPI)
Here's an example implementation of a custom `IPipelineBehavior<TRequest, TResponse>` for MediatR that captures FluentValidation errors and re-throws as a structured `ValidationException`:
```csharp
using MediatR;
using System;
using System.Threading;
using System.Threading.Tasks;
using FluentValidation;
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly IValidator<TRequest> _validator;
public ValidationBehavior(IValidator<TRequest> validator)
{
_validator = validator ?? throw new ArgumentNullException(nameof(validator));
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if (_validator != null)
{
var validationResult = await _validator.ValidateAsync(request, cancellationToken);
if (!validationResult.IsValid)
{
var errors = validationResult.Errors
.GroupBy(e => e.PropertyName)
.ToDictionary(g => g.Key, g => g.Select(e => e.ErrorMessage).ToList());
throw new ValidationException(errors);
}
}
var response = await next();
return response;
}
}
public class ValidationException : Exception
{
public Dictionary<string, List<string>> Errors { get; }
public ValidationException(Dictionary<string, List<string>> errors)
{
Errors = errors;
}
public override string ToString()
{
return $"Validation failed: {string.Join(", ", Errors.Select(kvp => $"{kvp.Key}: {string.Join(", ", kvp.Value)}"))}";
}
}
```
To use this behavior, you'll need to register it in the DI container. Here's an example using the `Microsoft.Extensions.DependencyInjection` package:
```csharp
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
```
You'll also need to register your validators:
```csharp
services.AddTransient<IValidator<MyRequest>, MyRequestValidator>();
```
With this setup, any requests that are invalid according to the validator will throw a `ValidationException` with a dictionary of error messages.
Example usage:
```csharp
public class MyRequest
{
public string Name { get; set; }
public int Age { get; set; }
}
public class MyRequestValidator : AbstractValidator<MyRequest>
{
public MyRequestValidator()
{
RuleFor(r => r.Name).NotEmpty();
RuleFor(r => r.Age).GreaterThan(0);
}
}
public class MyRequestHandler : IRequestHandler<MyRequest, MyResponse>
{
public async Task<MyResponse> Handle(MyRequest request, CancellationToken cancellationToken)
{
// Handle the request
return new MyResponse();
}
}
public class MyResponse
{
// Response properties
}
```
In this example, if you send a `MyRequest` with an empty `Name` or a non-positive `Age`, the `ValidationBehavior` will catch the validation error and throw a `ValidationException` with a dictionary containing the error messages.
Enhanced response (with ZPI)
Here's a custom implementation of `IPipelineBehavior<TRequest, TResponse>` that captures FluentValidation errors and re-throws them as a structured `ValidationException`:
```csharp
using MediatR;
using MediatR.Pipeline;
using FluentValidation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly IValidatorFactory _validatorFactory;
public ValidationBehavior(IValidatorFactory validatorFactory)
{
_validatorFactory = validatorFactory;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var validator = _validatorFactory.GetValidator(request);
if (validator != null)
{
var validationResult = await validator.ValidateAsync(request, cancellationToken);
if (!validationResult.IsValid)
{
var errors = validationResult.Errors
.GroupBy(e => e.PropertyName)
.ToDictionary(g => g.Key, g => g.Select(e => e.ErrorMessage).ToList());
throw new ValidationException(errors);
}
}
return await next();
}
}
public class ValidationException : Exception
{
public Dictionary<string, List<string>> Errors { get; }
public ValidationException(Dictionary<string, List<string>> errors)
{
Errors = errors;
}
public override string ToString()
{
return $"Validation failed: {string.Join(", ", Errors.Select(kvp => $"{kvp.Key}: {string.Join(", ", kvp.Value)}"))}";
}
}
```
In this implementation, the `ValidationBehavior` class takes an instance of `IValidatorFactory` in its constructor, which is used to retrieve the validator for the incoming request. If a validator is found, it's used to validate the request asynchronously. If the validation fails, a `ValidationException` is thrown with a dictionary of errors, where each key is a property name and the value is a list of error messages.
The `ValidationException` class is a custom exception that holds the validation errors and provides a meaningful `ToString` implementation for easy debugging.
To register this behavior in the MediatR pipeline, you can use the following code in your DI setup:
```csharp
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
```
This will enable the validation behavior for all requests handled by MediatR. Make sure to also register your validators with the `IValidatorFactory` instance, for example:
```csharp
services.AddTransient<IValidator<MyRequest>, MyRequestValidator>();
```
With this setup, any validation errors will be caught and re-thrown as a structured `ValidationException`, making it easier to handle and display validation errors in your application.
Grader chain
Each grader reads the same baseline + enhanced pair and casts an independent verdict. Disagreement is the framework's own signal — not noise to be averaged away.
V1samba/Meta-Llama-3.3-70B-Instruct
BetterV4qwen2.5-coder-3b-instruct
BetterV5openrouter/qwen/qwen3-235b-a22b-2507
BetterV7qwen2.5-coder-7b-instruct
SameV8openai/gpt-4.1
SameV9anthropic/claude-opus-4-7
WorseV12openai/gpt-4o
BetterComments
Our grader said what it said. What do you say? Comment as a guest below.
No comments yet. Be the first to say what you make of this round.