Nexus.Pipeline.Sample.MvcApp
1.0.3
dotnet add package Nexus.Pipeline.Sample.MvcApp --version 1.0.3
NuGet\Install-Package Nexus.Pipeline.Sample.MvcApp -Version 1.0.3
<PackageReference Include="Nexus.Pipeline.Sample.MvcApp" Version="1.0.3" />
<PackageVersion Include="Nexus.Pipeline.Sample.MvcApp" Version="1.0.3" />
<PackageReference Include="Nexus.Pipeline.Sample.MvcApp" />
paket add Nexus.Pipeline.Sample.MvcApp --version 1.0.3
#r "nuget: Nexus.Pipeline.Sample.MvcApp, 1.0.3"
#:package Nexus.Pipeline.Sample.MvcApp@1.0.3
#addin nuget:?package=Nexus.Pipeline.Sample.MvcApp&version=1.0.3
#tool nuget:?package=Nexus.Pipeline.Sample.MvcApp&version=1.0.3
Nexus.Pipeline
Typed steps. Structured results. Full execution history.
Nexus.Pipeline is a typed, in-process execution pipeline library for .NET. You register an ordered sequence of steps on a fluent builder, attach cross-cutting behaviors (logging, retry, timeout, transactions, OpenTelemetry metrics), and execute against a shared IExecutionContext that carries the request, the evolving response, and a per-step execution history. Every execution returns a PipelineResult<TResponse> — structured errors, no exception-based control flow. Supports inline lambda steps and DI-resolved class-based steps interchangeably. Targets .NET 4.6.2 through .NET 10, with AOT and trim compatibility on .NET 8+.
graph TB
subgraph APP["Application Layer"]
API["ASP.NET Core / Worker"]
LEGACY["net462 Console / WCF / MVC 5"]
end
subgraph CORE["Core Engine"]
BUILDER["PipelineBuilder / PipelineDefinitionBuilder"]
EXEC["PipelineExecutor"]
CTX["ExecutionContext + ContextScope"]
COMP["CompensationEngine"]
end
subgraph BEH["Behaviors (middleware chain)"]
LOG["LoggingBehavior (−1000)"]
RETRY["RetryBehavior (−800)"]
TIMEOUT["TimeoutBehavior (−600)"]
EXC["ExceptionHandlingBehavior (MaxValue)"]
end
subgraph DI["DI / Hosting"]
SVC["AddNexusPipeline()"]
RES["IStepResolver (DI-backed)"]
end
APP --> DI
DI --> CORE
CORE --> BEH
CORE --> COMP
Feature Highlights
| Feature | Description |
|---|---|
| Multi-TFM | net462 · netstandard2.0 · net6.0 · net8.0 · net9.0 · net10.0 |
| Fluent builder | Pipeline.For<>() — build-and-execute in one chain |
| Reusable definitions | Pipeline.Define<>().Build() — immutable, thread-safe, build once |
| Validation phase | IValidationStep runs before execute; short-circuits on failure |
| Middleware behaviors | Ordered IPipelineBehavior chain wraps all step execution |
| Retry + Timeout | WithPolicy(p => p.Retry(3).Timeout(10s)) — shortcut wiring |
| Saga compensation | WithCompensation<T>() — reverse-order rollback on failure |
| Conditional routing | .If() / .Else() — runtime context-driven branching |
| Parallel branches | .Parallel() — concurrent fan-out with Task.WhenAll |
| DAG dependencies | .DependsOn<T>() — topological sort guarantees order |
| Structured errors | ErrorInfo with Code, Category, Severity, Source |
| Execution history | Per-step StepExecutionRecord with status and duration |
| OpenTelemetry | MetricsBehavior emits System.Diagnostics.Activity spans |
| Visualization | result.Metadata.ToMermaid() / ToPlantUml() diagram export |
Packages
| Package | NuGet | TFMs | Description |
|---|---|---|---|
Nexus.Pipeline |
net462 · ns2.0 · net6–10 | Execution engine, behaviors, DI integration, and visualization |
Diagram export (Mermaid, PlantUML, JSON) targets netstandard2.0 and .NET 6+ (not net462).
Installation
dotnet add package Nexus.Pipeline
# Package Manager Console
Install-Package Nexus.Pipeline
Quick Start
Pattern 1 — Pipeline.For<>(): Build-and-Execute
The fastest way to run a pipeline. Each call to .ExecuteAsync() is a fresh execution.
using Nexus.Pipeline;
using Nexus.Pipeline.Behaviors;
using Nexus.Pipeline.DependencyInjection.Extensions;
// DI wiring (once at startup)
services.AddNexusPipeline();
services.AddPipelineStep<ValidatePaymentStep>();
services.AddPipelineStep<CheckBalanceStep>();
services.AddPipelineStep<CreateTransactionStep>();
services.AddTransient(typeof(LoggingBehavior<,>));
services.AddTransient(typeof(ExceptionHandlingBehavior<,>));
// Execution (per-request)
var result = await Pipeline
.For<PaymentRequest, PaymentResponse>(resolver)
.WithName("PaymentPipeline")
.AddBehavior<LoggingBehavior<PaymentRequest, PaymentResponse>>()
.AddBehavior<ExceptionHandlingBehavior<PaymentRequest, PaymentResponse>>()
.Validation<ValidatePaymentStep>()
.Step<CheckBalanceStep>()
.Step<CreateTransactionStep>()
.ExecuteAsync(request, cancellationToken);
// Result
result.IsSuccess // bool
result.Response // PaymentResponse?
result.Errors // IReadOnlyList<ErrorInfo>
result.ExecutionHistory // per-step records
result.Metadata.Duration // TimeSpan?
Pattern 2 — Pipeline.Define<>(): Reusable Definition
Build once at startup; call ExecuteAsync many times. Each call gets its own CorrelationId and ExecutionContext — fully thread-safe.
// Built ONCE (e.g. in a singleton or static field)
IPipelineDefinition<PaymentRequest, PaymentResponse> _pipeline =
Pipeline.Define<PaymentRequest, PaymentResponse>(resolver)
.WithName("PaymentPipeline")
.WithVersion("2.0")
.WithLoggerFactory(loggerFactory)
.WithCorrelationIdFactory(() => "PAY-" + Guid.NewGuid().ToString("N")[..8])
.AddBehavior<LoggingBehavior<PaymentRequest, PaymentResponse>>()
.WithPolicy(p => p.Retry(3).Timeout(TimeSpan.FromSeconds(30)))
.Validation<ValidatePaymentStep>()
.Step<CheckBalanceStep>()
.Step<CreateTransactionStep>()
.Build();
// Executed MANY times (concurrent-safe)
var result = await _pipeline.ExecuteAsync(request, cancellationToken);
Pattern 3 — Implementing a Step
public sealed class CheckBalanceStep : IPipelineStep<PaymentRequest, PaymentResponse>
{
private readonly IAccountRepository _accounts;
public CheckBalanceStep(IAccountRepository accounts) => _accounts = accounts;
public string StepName => "CheckBalance";
public int Order => 2;
public async Task<StepOutcome> ExecuteAsync(
IExecutionContext<PaymentRequest, PaymentResponse> context,
CancellationToken cancellationToken = default)
{
var balance = await _accounts.GetBalanceAsync(context.Request.CustomerId, cancellationToken);
if (context.Request.Amount > balance)
return StepOutcome.Fail;
context.Items.Set("available_balance", balance);
return StepOutcome.Continue;
}
}
Advanced Patterns
Saga Compensation (Rollback)
Compensation steps are registered per-step and run in reverse order on pipeline failure.
await Pipeline
.For<TransferRequest, TransferResponse>(resolver)
.Validation<ValidateTransferStep>()
.Step<ReserveAmountStep>()
.WithCompensation<ReleaseReservationStep>() // runs if pipeline fails AFTER this step
.Step<ExecuteTransferStep>()
.WithCompensation<ReverseTransferStep>() // runs first (reverse order)
.Step<AuditTransferStep>()
.ExecuteAsync(request, ct);
If AuditTransferStep fails → ReverseTransferStep runs, then ReleaseReservationStep.
Conditional Routing
Pipeline.Define<ApprovalRequest, ApprovalResponse>(resolver)
.Step<CheckAutoApprovalStep>() // sets ctx.Scope["auto_approved"] = true/false
.If(ctx => ctx.Items.TryGet<bool>("auto_approved", out var v) && v,
b => b.Step<AutoApproveStep>())
.If(ctx => ctx.Items.TryGet<bool>("manager_required", out var v) && v,
b => b.Step<RequestManagerApprovalStep>())
.Build();
Parallel Branches
pipeline
.Step<ProcessOrderStep>()
.Parallel(
b => b.Step<NotifyApplicantStep>(), // concurrent
b => b.Step<NotifySlackChannelStep>()) // concurrent
.Step<FinalizeStep>()
DAG (Dependency Graph)
pipeline
.Step<StepA>()
.Step<StepB>().DependsOn<StepA>() // B runs after A
.Step<StepC>().DependsOn<StepA>() // C runs after A (parallel with B)
.Step<StepD>().DependsOn<StepB>().DependsOn<StepC>() // D waits for B and C
Validation Step
public sealed class ValidatePaymentStep : IValidationStep<PaymentRequest, PaymentResponse>
{
public string StepName => "ValidatePayment";
public int Order => 0;
public Task<ValidationResult> ValidateAsync(
IExecutionContext<PaymentRequest, PaymentResponse> context,
CancellationToken cancellationToken = default)
{
var errors = new List<ErrorInfo>();
if (context.Request.Amount <= 0)
errors.Add(ErrorInfo.Validation("PAY-VAL-001", "Amount must be positive", nameof(PaymentRequest.Amount)));
if (string.IsNullOrEmpty(context.Request.CustomerId))
errors.Add(ErrorInfo.Validation("PAY-VAL-002", "CustomerId is required", nameof(PaymentRequest.CustomerId)));
return Task.FromResult(
errors.Count == 0 ? ValidationResult.Valid() : ValidationResult.Invalid(errors.ToArray()));
}
public Task<StepOutcome> ExecuteAsync(
IExecutionContext<PaymentRequest, PaymentResponse> context,
CancellationToken cancellationToken = default)
=> Task.FromResult(StepOutcome.Continue);
}
Pipeline Visualization
// After execution, export a Mermaid diagram of what ran
var mermaid = result.Metadata.ToMermaid();
Console.WriteLine(mermaid);
// Output:
// graph TD
// ValidatePayment["ValidatePayment ✓ 2ms"]
// CheckBalance["CheckBalance ✓ 5ms"]
// CreateTransaction["CreateTransaction ✓ 12ms"]
// ValidatePayment --> CheckBalance --> CreateTransaction
Step Outcomes
| Outcome | Effect |
|---|---|
StepOutcome.Continue |
Proceed to next step |
StepOutcome.Stop |
Stop execution, return success with current response |
StepOutcome.Fail |
Stop execution, return failure; triggers compensation |
Built-in Behaviors
| Behavior | Order | Description |
|---|---|---|
LoggingBehavior<,> |
−1000 | Structured logs for start/end/duration/errors |
MetricsBehavior<,> |
−900 | System.Diagnostics.Activity spans for OpenTelemetry |
RetryBehavior<,> |
−800 | Exponential back-off retry with configurable predicate |
TimeoutBehavior<,> |
−600 | Pipeline-level deadline via CancellationToken |
TransactionBehavior<,> |
−400 | Ambient TransactionScope (commit on success) |
ExceptionHandlingBehavior<,> |
int.MaxValue |
Catches all unhandled exceptions → structured failure |
Lower Order = outermost (wraps everything inside it). ExceptionHandlingBehavior should always be outermost in practice, so register it last or use int.MaxValue.
WithPolicy() Shortcut
pipeline.WithPolicy(p => p
.Retry(3, delay: TimeSpan.FromMilliseconds(200), backoffMultiplier: 2.0)
.Timeout(TimeSpan.FromSeconds(30)));
Execution Context API
Every step receives IExecutionContext<TRequest, TResponse>:
ctx.Request // TRequest — read-only
ctx.Response // TResponse? — set by steps
ctx.CorrelationId // string — unique per pipeline run
ctx.CancellationToken // propagated cancellation
// Cross-step state bag (typed, dictionary-backed)
ctx.Items.Set("key", value);
ctx.Items.TryGet<T>("key", out var v);
ctx.Items.Remove("key");
// Execution metadata
ctx.ExecutionHistory // IReadOnlyList<StepExecutionRecord>
ctx.PipelineMetadata // name, version, tags
Error Model
result.Errors // IReadOnlyList<ErrorInfo>
// ErrorInfo properties:
error.Code // "PAY-001"
error.Message // human-readable
error.PropertyName // field that caused the error (validation)
error.ErrorType // Business | Technical | Unknown
error.Category // Validation | Authorization | NotFound | ...
error.Severity // Error | Warning | Critical | Info
error.Exception // Exception? (if from ExceptionHandlingBehavior)
// Pattern matching on result:
var msg = result.Match(
onSuccess: r => $"OK: {r.TransactionId}",
onFailure: e => $"Fail: {e[0].Code}",
onException: ex => $"Ex: {ex.Message}");
// Fluent callbacks:
result
.OnSuccess(r => log.LogInformation("Done: {TxnId}", r.TransactionId))
.OnFailure(errors => log.LogWarning("Failed: {Code}", errors[0].Code))
.OnException(ex => log.LogError(ex, "Unhandled exception"));
.NET Framework 4.6 Compatibility
All packages ship a net462 target. Add these binding redirects to app.config:
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Extensions.DependencyInjection.Abstractions"
publicKeyToken="adb9793829ddae60" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Extensions.Logging.Abstractions"
publicKeyToken="adb9793829ddae60" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
Entry point on net462 (no top-level statements):
static void Main(string[] args)
{
RunAsync().GetAwaiter().GetResult();
}
static async Task RunAsync()
{
var result = await Pipeline
.For<OrderRequest, OrderResponse>(resolver)
.Validation<ValidateOrderStep>()
.Step<CheckInventoryStep>()
.Step<ReserveInventoryStep>()
.WithCompensation<CancelReservationStep>()
.Step<CreateOrderStep>()
.ExecuteAsync(request);
}
See docs/samples/net-framework-46.md for ASP.NET MVC 5, WCF, and Autofac integration patterns.
Samples
| Sample | TFM | What it demonstrates |
|---|---|---|
Nexus.Pipeline.Sample.Console |
net9.0 | Basic step, validation, behaviors, result handling |
Nexus.Pipeline.Sample.WebApi |
net9.0 | ASP.NET Core Minimal API integration |
Nexus.Pipeline.Sample.Net9 |
net9.0 | .NET 9-specific demo |
Nexus.Pipeline.Sample.Net10 |
net10.0 | .NET 10-specific demo |
Nexus.Pipeline.BankingWorkflow |
net9.0 | Full saga: reserve → transfer → audit + compensation rollback, retry |
Nexus.Pipeline.ApprovalWorkflow |
net9.0 | Conditional routing, parallel notifications, Pipeline.Define<>() reuse |
Nexus.Pipeline.NetFramework46 |
net462 | All patterns on .NET 4.6.2: static Main, binding redirects |
Nexus.Pipeline.ManualWiring |
net462 | No DI container — manual LoggerFactory + Options wiring |
Nexus.Pipeline.Sample.MvcApp |
net462 | ASP.NET MVC 5 integration |
Nexus.Pipeline.Sample.WcfService |
net462 | WCF service integration |
Documentation
| Doc | Description |
|---|---|
docs/architecture/overview.md |
Architecture narrative, design principles, entry points |
docs/architecture/solution-structure.md |
Project layout, TFM matrix, dependency graph |
docs/execution-flow/pipeline-execution.md |
Step loop, behavior chain, compensation contract |
docs/behaviors/built-in-behaviors.md |
All 6 behaviors with order, configuration, examples |
docs/compensation/saga-pattern.md |
Saga pattern, rollback design, WithCompensation guide |
docs/best-practices/index.md |
Step design, error codes, DI patterns, testing |
docs/performance/index.md |
Benchmarks, caching, behavior selection |
docs/troubleshooting/index.md |
Common errors, circular deps, compensation debugging |
docs/migration/from-mediator.md |
Migrating from MediatR with concept mapping table |
docs/samples/net-framework-46.md |
Full .NET 4.6 guide: MVC 5, WCF, Autofac, binding redirects |
docs/diagnostics/index.md |
Execution history, logging, OpenTelemetry, health checks |
docs/persistence/index.md |
Persistence patterns, outbox, integration guidance |
docs/pipeline-engine/index.md |
Internal architecture of the execution runtime |
docs/plugins/index.md |
Extension points, custom resolvers, visualization plugins |
Architecture Diagrams
Mermaid (.mmd) and PlantUML (.puml) diagrams in docs/diagrams/:
| Diagram | Description |
|---|---|
01-high-level-architecture |
Component overview |
02-solution-structure |
Package dependency graph |
03-pipeline-execution-flow |
Full execution flowchart with compensation |
04-sequence-success |
Happy path sequence diagram |
05-sequence-validation-failure |
Short-circuit on invalid input |
06-sequence-retry |
RetryBehavior exponential backoff |
07-sequence-compensation |
Saga rollback sequence |
08-sequence-parallel |
Parallel branch fan-out/fan-in |
09-class-abstractions |
Core interface hierarchy |
10-class-result-hierarchy |
Result and error model classes |
12-dag-execution |
DAG topological sort example |
13-deployment |
Production deployment topology |
15-state-pipeline |
Pipeline lifecycle state machine |
License
MIT — see LICENSE for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET Framework | net462 is compatible. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
-
.NETFramework 4.6.2
- Nexus.Pipeline.Core (>= 1.0.3)
- System.Threading.Tasks.Extensions (>= 4.5.4)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|