MessageValidation 1.0.0
dotnet add package MessageValidation --version 1.0.0
NuGet\Install-Package MessageValidation -Version 1.0.0
<PackageReference Include="MessageValidation" Version="1.0.0" />
<PackageVersion Include="MessageValidation" Version="1.0.0" />
<PackageReference Include="MessageValidation" />
paket add MessageValidation --version 1.0.0
#r "nuget: MessageValidation, 1.0.0"
#:package MessageValidation@1.0.0
#addin nuget:?package=MessageValidation&version=1.0.0
#tool nuget:?package=MessageValidation&version=1.0.0
MessageValidation
A protocol-agnostic message validation pipeline for .NET — validate incoming messages from MQTT, RabbitMQ, Kafka, Azure Service Bus, NATS, or any messaging transport with DI integration, pluggable validation, and configurable failure handling.
Why?
Every message consumer faces the same challenge: raw bytes arrive, you deserialize them, and then you need to validate before processing. This logic is duplicated across every protocol and every project.
MessageValidation extracts this into a single, reusable pipeline:
Raw bytes → Deserialize → Validate → Handle (or dead-letter / log / skip)
The core has zero opinion on which messaging library or validation framework you use. Bring your own transport, bring your own validator.
Installation
dotnet add package MessageValidation
Quick Start
1. Define your message
public class TemperatureReading
{
public string SensorId { get; set; } = "";
public double Value { get; set; }
public DateTime Timestamp { get; set; }
}
2. Implement a validator
public class TemperatureReadingValidator : IMessageValidator<TemperatureReading>
{
public Task<MessageValidationResult> ValidateAsync(
TemperatureReading message, CancellationToken ct = default)
{
var errors = new List<MessageValidationError>();
if (string.IsNullOrWhiteSpace(message.SensorId))
errors.Add(new("SensorId", "SensorId is required."));
if (message.Value is < -50 or > 150)
errors.Add(new("Value", "Value must be between -50 and 150."));
return Task.FromResult(errors.Count == 0
? MessageValidationResult.Success()
: MessageValidationResult.Failure(errors));
}
}
3. Implement a handler
public class TemperatureHandler : IMessageHandler<TemperatureReading>
{
public Task HandleAsync(
TemperatureReading message, MessageContext context, CancellationToken ct = default)
{
// Only reached if validation passed
Console.WriteLine($"[{context.Source}] Sensor {message.SensorId}: {message.Value}°C");
return Task.CompletedTask;
}
}
4. Implement a deserializer
using System.Text.Json;
public class JsonMessageDeserializer : IMessageDeserializer
{
public object Deserialize(byte[] payload, Type targetType) =>
JsonSerializer.Deserialize(payload, targetType)
?? throw new InvalidOperationException($"Failed to deserialize to {targetType.Name}");
}
5. Register services
builder.Services.AddMessageValidation(options =>
{
options.MapSource<TemperatureReading>("sensors/+/temperature");
options.DefaultFailureBehavior = FailureBehavior.Log;
});
builder.Services.AddMessageDeserializer<JsonMessageDeserializer>();
builder.Services.AddScoped<IMessageValidator<TemperatureReading>, TemperatureReadingValidator>();
builder.Services.AddMessageHandler<TemperatureReading, TemperatureHandler>();
6. Process messages
Feed messages into the pipeline from any transport:
var pipeline = serviceProvider.GetRequiredService<IMessageValidationPipeline>();
var context = new MessageContext
{
Source = "sensors/living-room/temperature",
RawPayload = payloadBytes
};
await pipeline.ProcessAsync(context);
Core Concepts
Source Mapping
Map source patterns (topics, queues, routing keys) to message types. Supports MQTT-style wildcards:
| Pattern | Matches |
|---|---|
sensors/living-room/temperature |
Exact match only |
sensors/+/temperature |
sensors/kitchen/temperature, sensors/bedroom/temperature, etc. |
devices/# |
devices/abc, devices/abc/status, devices/abc/status/battery, etc. |
options.MapSource<TemperatureReading>("sensors/+/temperature");
options.MapSource<DeviceHeartbeat>("devices/#");
Failure Behaviors
Configure how validation failures are handled:
| Behavior | Description |
|---|---|
Log |
Log the errors and drop the message (default) |
DeadLetter |
Route to a dead-letter destination via IDeadLetterHandler |
Skip |
Silently skip the message |
ThrowException |
Throw a MessageValidationException |
Custom |
Delegate to your IValidationFailureHandler implementation |
options.DefaultFailureBehavior = FailureBehavior.Custom;
// Register your custom handler
builder.Services.AddValidationFailureHandler<MyFailureHandler>();
Dead-Letter Queue
When FailureBehavior.DeadLetter is configured, the pipeline computes a dead-letter destination from DeadLetterPrefix + Source and delegates to an IDeadLetterHandler:
options.DefaultFailureBehavior = FailureBehavior.DeadLetter;
options.DeadLetterPrefix = "$dead-letter/"; // default — customize as needed
// Register your dead-letter handler
builder.Services.AddDeadLetterHandler<MyDeadLetterHandler>();
Implement the handler to publish the failed message to your transport's dead-letter destination:
public class MyDeadLetterHandler : IDeadLetterHandler
{
public Task HandleAsync(DeadLetterContext context, CancellationToken ct = default)
{
// context.Destination → "$dead-letter/sensors/room1/temperature"
// context.OriginalContext → the original MessageContext (source, payload, metadata)
// context.ValidationResult → the validation errors
// context.Timestamp → UTC time of the dead-letter decision
Console.WriteLine($"Dead-lettering to {context.Destination}");
return Task.CompletedTask;
}
}
Resolution priority: When DeadLetter is active, the pipeline resolves handlers in this order:
IDeadLetterHandler— preferred (receives fullDeadLetterContextwith computed destination)IValidationFailureHandler— backward-compatible fallback- Log warning — graceful degradation if no handler is registered
Abstractions
| Interface | Purpose |
|---|---|
IMessageValidationPipeline |
Core pipeline contract (mockable) |
IMessageValidator<T> |
Validates a deserialized message |
IMessageHandler<T> |
Handles a validated message |
IMessageDeserializer |
Converts raw bytes to a typed object |
IValidationFailureHandler |
Custom logic when validation fails |
IDeadLetterHandler |
Handles dead-lettered messages (receives DeadLetterContext) |
Architecture
The core library is transport-agnostic and validation-framework-agnostic. It defines contracts and a pipeline — adapters bring the implementations.
MessageValidation-Project/
├── MessageValidation/ ← Core pipeline (zero opinion)
│ ├── Abstractions/
│ │ ├── IMessageValidationPipeline.cs IMessageValidationPipeline
│ │ ├── IMessageValidator.cs IMessageValidator<T>
│ │ ├── IMessageHandler.cs IMessageHandler<T>
│ │ ├── IMessageDeserializer.cs IMessageDeserializer
│ │ ├── IValidationFailureHandler.cs IValidationFailureHandler
│ │ └── IDeadLetterHandler.cs IDeadLetterHandler
│ ├── Configuration/
│ │ ├── FailureBehavior.cs Log | DeadLetter | Skip | Throw | Custom
│ │ └── MessageValidationOptions.cs Source-to-type mapping + wildcards
│ ├── Diagnostics/
│ │ └── MessageValidationMetrics.cs System.Diagnostics.Metrics counters
│ ├── Models/
│ │ ├── MessageContext.cs Protocol-agnostic envelope
│ │ ├── MessageValidationResult.cs Validation outcome
│ │ ├── MessageValidationError.cs Single error record
│ │ └── DeadLetterContext.cs Dead-letter envelope (destination, errors, timestamp)
│ ├── Pipeline/
│ │ ├── MessageValidationPipeline.cs Deserialize → Validate → Dispatch
│ │ └── MessageValidationException.cs Thrown on FailureBehavior.ThrowException
│ └── DependencyInjection/
│ └── ServiceCollectionExtensions.cs AddMessageValidation(), AddMessageHandler<,>(), AddDeadLetterHandler<>()
│
├── MessageValidation.DataAnnotations/ ← Validation adapter (DataAnnotations)
│ ├── DataAnnotationsMessageValidator.cs Bridges DataAnnotations → IMessageValidator<T>
│ └── DependencyInjection/
│ └── ServiceCollectionExtensions.cs AddMessageDataAnnotationsValidation()
│
├── MessageValidation.FluentValidation/ ← Validation adapter (FluentValidation)
│ ├── FluentValidationMessageValidator.cs Bridges IValidator<T> → IMessageValidator<T>
│ └── DependencyInjection/
│ └── ServiceCollectionExtensions.cs AddMessageFluentValidation()
│
├── MessageValidation.MqttNet/ ← Transport adapter
│ ├── MqttClientExtensions.cs IMqttClient.UseMessageValidation()
│ ├── MqttServerExtensions.cs MqttServer.UseMessageValidation()
│ └── DependencyInjection/
│ └── ServiceCollectionExtensions.cs AddMqttNetMessageValidation()
│
├── examples/
│ └── MessageValidation.Example/ ← Runnable console demo
│
└── README.md
Pipeline flow
flowchart LR
Transport["🔌 Transport <br/> (MQTTnet, RabbitMQ,<br/> Kafka…)"]
Deserializer["📦 IMessageDeserializer <br/> bytes → object"]
Validator["✅ IMessageValidator<T> <br/> (FluentValidation,<br/> DataAnnotations…)"]
Handler["⚡ IMessageHandler<T> <br/> (only if valid)"]
Failure["⚠️ Failure Handler <br/> Log / Skip / Throw / Custom"]
DeadLetter["💀 IDeadLetterHandler <br/> DeadLetterContext → <br/> transport DLQ"]
Transport -->|raw bytes| Deserializer
Deserializer -->|typed message| Validator
Validator -->|valid| Handler
Validator -->|invalid| Failure
Validator -->|invalid + DeadLetter| DeadLetter
Adapter Packages
| Package | Role | Status | Docs |
|---|---|---|---|
MessageValidation |
Core pipeline & abstractions | ✅ Available | this file |
MessageValidation.FluentValidation |
FluentValidation adapter | ✅ Available | README |
MessageValidation.MqttNet |
MQTTnet transport hook | ✅ Available | README |
MessageValidation.DataAnnotations |
DataAnnotations adapter | ✅ Available | README |
MessageValidation.RabbitMQ |
RabbitMQ transport hook | 🔜 Planned | — |
MessageValidation.Kafka |
Kafka transport hook | 🔜 Planned | — |
Roadmap
- v0.1 — Core pipeline, abstractions, DI integration, wildcard matching, FluentValidation adapter, MQTTnet transport adapter
- v0.2 — DataAnnotations adapter,
System.Diagnostics.Metricsobservability - v0.3 — Dead-letter queue support (
IDeadLetterHandler,DeadLetterContext, dead-letter metrics, backward-compatible fallback) - v1.0 — RabbitMQ & Kafka adapters, source generators for AOT
- v2.0 — Middleware-style pipeline (
Use,Map), Azure Service Bus adapter
Requirements
- .NET 10+
Author
- Romain OD
- @romainod | GitHub
- 🌐 www.devskillsunlock.com
License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Diagnostics (>= 10.0.5)
- Microsoft.Extensions.Logging (>= 10.0.5)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
NuGet packages (5)
Showing the top 5 NuGet packages that depend on MessageValidation:
| Package | Downloads |
|---|---|
|
MessageValidation.FluentValidation
FluentValidation adapter for MessageValidation — automatically bridge FluentValidation validators into the MessageValidation pipeline. |
|
|
MessageValidation.MqttNet
MQTTnet transport adapter for MessageValidation — automatically feed MQTTnet messages into the MessageValidation pipeline. |
|
|
MessageValidation.DataAnnotations
DataAnnotations adapter for MessageValidation — validate messages using System.ComponentModel.DataAnnotations attributes in the MessageValidation pipeline. |
|
|
MessageValidation.Kafka
Confluent Kafka transport adapter for MessageValidation — automatically feed Kafka messages into the MessageValidation pipeline. |
|
|
MessageValidation.RabbitMQ
RabbitMQ transport adapter for MessageValidation — automatically feed RabbitMQ messages into the MessageValidation pipeline. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 88 | 4/1/2026 |
| 0.3.0 | 66 | 3/25/2026 |
| 0.2.0 | 67 | 3/9/2026 |
| 0.1.0-preview.1 | 45 | 3/4/2026 |