Middleware Pipeline¶
This package provides core attributes to control middleware ordering, execution stages, and pipeline-managed transformations in any .NET backend/server using a middleware pipeline (e.g., MiddlewarePipeline<TPacket>).
These attributes let you concisely control where, when, and how your middleware gets invoked — without manual pipeline wiring.
Key features¶
Fast scan
- Stages: Inbound / Outbound / OutboundAlways
- Ordering:
[MiddlewareOrder]+[MiddlewareStage] - Thread-safe: snapshot execution (no locks)
- Errors: continue or abort via
ConfigureErrorHandling - Any packet type:
IPacketor custom DTOs
Usage example¶
Flow
Register middleware → configure error handling → execute with handler delegate.
// Register your middleware components (inbound, outbound, transform, etc)
var pipeline = new MiddlewarePipeline<IPacket>();
pipeline.Use(new UnwrapPacketMiddleware());
pipeline.Use(new PermissionMiddleware());
pipeline.Use(new ConcurrencyMiddleware());
pipeline.Use(new RateLimitMiddleware());
pipeline.Use(new TimeoutMiddleware());
pipeline.Use(new WrapPacketMiddleware()); // Outbound
// Configure global error handling behavior
pipeline.ConfigureErrorHandling(
continueOnError: true,
errorHandler: (ex, type) => logger.Error($"MW error in {type.Name}: {ex}")
);
// When handling a request:
await pipeline.ExecuteAsync(
context, // PacketContext<TPacket> with all connection/packet info
async ct => { await MyHandler(context.Packet, ct); }, // Final business handler
cancellationToken // Cancellation
);
Middleware implementation contract¶
Implement a middleware as below (example: Permission check):
[MiddlewareOrder(-50)]
[MiddlewareStage(MiddlewareStage.Inbound)]
public class PermissionMiddleware : IPacketMiddleware<IPacket>
{
public async Task InvokeAsync(
PacketContext<IPacket> context,
Func<CancellationToken, Task> next)
{
if (context.Attributes.Permission is null
|| context.Attributes.Permission.Level <= context.Connection.Level)
{
await next(context.CancellationToken);
return;
}
// Optionally: send a fail message, log, etc...
}
}
Attribute-driven ordering and staging¶
- [MiddlewareOrder(N)]: Set execution order (lower runs first for inbound, last for outbound)
- [MiddlewareStage(Inbound|Outbound|Both, AlwaysExecute = ...)]: Bind to pipeline stages
Middleware order is automatically resolved and cached for runtime performance.
Supported middleware examples¶
Pick a starter set
Inbound: UnwrapPacketMiddleware, PermissionMiddleware, ConcurrencyMiddleware, RateLimitMiddleware, TimeoutMiddleware
Outbound: WrapPacketMiddleware
| Middleware | Stage | Use-case |
|---|---|---|
UnwrapPacketMiddleware |
Inbound | Decrypt/decompress incoming data |
PermissionMiddleware |
Inbound | Permission/auth guard |
ConcurrencyMiddleware |
Inbound | Dynamic request throttling |
RateLimitMiddleware |
Inbound | Global/IP/attribute-driven rate limits |
TimeoutMiddleware |
Inbound | Per-packet/handler timeout+fail response |
WrapPacketMiddleware |
Outbound | Encrypt/compress outgoing data |
Error handling¶
Checklist
- Decide: continue or abort on exception
- Attach logger delegate
- Keep
continueOnError=falsefor security-critical paths
pipeline.ConfigureErrorHandling(
continueOnError: false, // Or true to log and skip faulty MW
errorHandler: (ex, type) => Log.Error($"[Middleware {type.Name}] {ex}")
);
Extending and advanced usage¶
- Custom middleware ideas: A/B tests, metrics, request shaping, device policies, circuit breakers.
Clear()+ re-register to hot-swap chains.- Execution is lock-free; keep middleware stateless where possible.
Available attributes¶
Quick rule
- Always set both stage and order.
- Negative = early inbound, late outbound.
- Use
AlwaysExecuteonly for must-run outbound steps.
1. MiddlewareOrderAttribute¶
Marks the execution priority/order of a middleware class in the pipeline.
- Order value meaning:
- Lower = earlier in inbound, later in outbound.
- Negative: executes before default (e.g., security, unwrapping)
- Zero: default order
- Positive: executes after default (e.g., limits, post-processing)
| Order Value | Common Use Case |
|---|---|
| -100 | Critical pre-processing (unwrapping, decryption) |
| -50 | Security/authentication checks |
| 0 | Default logic |
| 50 | Business, throttling, rate/concurrency limits |
| 100 | Post-processing (wrapping, encryption) |
2. MiddlewareStageAttribute¶
Indicates which stage (inbound, outbound, both) this middleware should run in the pipeline.
[MiddlewareStage(MiddlewareStage.Inbound)]
public class AuthGuardMiddleware : IPacketMiddleware<IPacket> { ... }
[MiddlewareStage(MiddlewareStage.Outbound, AlwaysExecute = true)]
public class AuditLogMiddleware : IPacketMiddleware<IPacket> { ... }
- Inbound: Executed before the main handler (verification, unwrapping)
- Outbound: Executed after handler (logging, wrapping)
- Both: Included in both stages
AlwaysExecute (for Outbound)¶
- By default, outbound middleware can be skipped if packet context signals
SkipOutbound. AlwaysExecute = true: Middleware always runs, even if outbound phase is being skipped (e.g., for auditing, hard security policy).
3. PipelineManagedTransformAttribute¶
- Purpose:
Marks a packet type as “handled by pipeline,” not by its own per-type transformer logic.
Catalog builder skips auto-binding transformer methods for these. - Use Case:
For message types that must not be transformed by the traversal logic (e.g., raw system/control packets, pipeline-managed records).
Enum: MiddlewareStage¶
Enum values for the above attribute:
Best practices¶
- Always annotate your middleware with both
[MiddlewareOrder]and[MiddlewareStage]for predictable execution. - Use negative orders for critical security rules and protocol unwrapping.
- Use positive orders for limits, logs, or post-processing wrappers.
- Use
AlwaysExecutefor mandatory outbound steps (compliance logging, etc).
Example: complete custom middleware¶
[MiddlewareOrder(-50)]
[MiddlewareStage(MiddlewareStage.Inbound)]
public class PermissionMiddleware : IPacketMiddleware<IPacket>
{
public async Task InvokeAsync(PacketContext<IPacket> context, Func<CancellationToken, Task> next)
{
// ...permission logic here
await next(context.CancellationToken);
}
}
Custom attributes in metadata¶
Flow
Define attribute → add via PacketMetadataBuilder.Add → read with GetCustomAttribute<T>() in middleware/handler.
The CustomAttributes feature allows dynamic extensions of packet metadata to add additional handler-specific properties beyond standard attributes such as Timeout, Permission, or Encryption. This is especially useful for advanced use cases like:
- Supporting third-party systems.
- Experimental features without modifying primary metadata structures.
- Adding tags or flags specific to business logic.
How to use custom attributes¶
- Add Custom Attribute to the Builder
During metadata creation, usePacketMetadataBuilder.Addto add custom attributes dynamically. Example:
builder.Add(new PacketRateLimitAttribute(requestsPerSecond: 50));
builder.Add(new ExampleCustomAttribute("Version", "v1.1"));
- Get Custom Attribute
Retrieve custom attributes during pipeline execution usingPacketMetadata.GetCustomAttribute<T>. Example:
var versionAttribute = metadata.GetCustomAttribute<ExampleCustomAttribute>();
if (versionAttribute != null)
{
Console.WriteLine($"Packet Version: {versionAttribute.Value}");
}
Full CustomAttributes Example¶
Here’s an end-to-end example demonstrating the definition and usage of CustomAttributes:
Step 1: Define a custom attribute¶
You can define your custom attribute class:
public class PacketTagAttribute : Attribute
{
public string Tag { get; }
public PacketTagAttribute(string tag)
{
Tag = tag;
}
}
Step 2: Attach custom attributes in metadata provider¶
Use IPacketMetadataProvider to assign your custom attributes dynamically for packets:
public class ExampleMetadataProvider : IPacketMetadataProvider
{
public void Populate(MethodInfo method, PacketMetadataBuilder builder)
{
// Assign a custom tag attribute
builder.Add(new PacketTagAttribute("Experimental"));
// Add more optional attributes based on the method
if (method.Name == "HandleCritical")
{
builder.Add(new PacketTagAttribute("Critical"));
}
}
}
Step 3: Retrieve custom attributes during execution¶
Access and process the custom attributes during middleware execution:
[MiddlewareOrder(-25)]
[MiddlewareStage(MiddlewareStage.Inbound)]
public class LogPacketTagMiddleware : IPacketMiddleware<IPacket>
{
public async Task InvokeAsync(PacketContext<IPacket> context, Func<CancellationToken, Task> next)
{
var builder = new PacketMetadataBuilder();
foreach (var provider in PacketMetadataProviders.Providers)
{
provider.Populate(context.Method, builder);
}
// Build metadata
var metadata = builder.Build();
// Log custom attributes (e.g., PacketTag)
var tag = metadata.GetCustomAttribute<PacketTagAttribute>()?.Tag;
if (tag != null)
{
Console.WriteLine($"Processing packet with Tag: {tag}");
}
// Continue the pipeline
await next(context.CancellationToken);
}
}
Custom attribute use cases¶
Here are some practical examples leveraging CustomAttributes:
| Use Case | Attribute | Example |
|---|---|---|
| Tagging packets | PacketTagAttribute |
Add tags like "HighPriority" or "System" |
| Packet versioning | PacketVersionAttribute |
Track experimental packet versions |
| Third-party integrations | PacketExternalSystemAttribute (link to external tool ID) |
Integrate with external analytics tools |
| Tracing/Diagnostics | Add unique diagnostic flags | Debugging server message paths |
Best practices for custom attributes¶
- Use
CustomAttributesfor extending existing metadata without breaking core functionality. - Dynamically add attributes using
PacketMetadataBuilder.Add()in combination with specificMethodInfometadata. - Always include a fallback mechanism in case a
CustomAttributeis missing while retrieving it.