SimpleVerticalSlice 10.0.0
dotnet add package SimpleVerticalSlice --version 10.0.0
NuGet\Install-Package SimpleVerticalSlice -Version 10.0.0
<PackageReference Include="SimpleVerticalSlice" Version="10.0.0" />
<PackageVersion Include="SimpleVerticalSlice" Version="10.0.0" />
<PackageReference Include="SimpleVerticalSlice" />
paket add SimpleVerticalSlice --version 10.0.0
#r "nuget: SimpleVerticalSlice, 10.0.0"
#:package SimpleVerticalSlice@10.0.0
#addin nuget:?package=SimpleVerticalSlice&version=10.0.0
#tool nuget:?package=SimpleVerticalSlice&version=10.0.0
SimpleVerticalSlice
SimpleVerticalSlice is a tiny helper library for .NET applications using a vertical slice architecture.
It provides a single base class – CommandHandlerBase<TCommand, TResult> – that defines a simple, consistent async pipeline for handling commands:
PreHandle → ValidateSyntax → PrepareOperation → Authorize → ValidateBusinessRules → ExecuteBusiness → PostHandle (+ ErrorHandle on failure)
Instead of re-writing the same flow in every use case, you inherit from this base class and override only the steps you need.
Features
✅ Simple, opinionated pipeline for command handlers
✅ Template Method pattern
✅ Async,
CancellationToken-aware✅ Hooks for:
PreHandle– pre-processing (logging, context, etc.)ValidateSyntax– input shape/format validationPrepareOperation– preparation before authorizationAuthorize– permission checksValidateBusinessRules– domain/business rulesExecuteBusiness– required core business logicPostHandle– persistence + post-processing (save, notify, events…)ErrorHandle– centralized error hook
✅ Works great alongside MediatR (or any other message bus)
Installation
Via .NET CLI:
dotnet add package SimpleVerticalSlice
In your .csproj:
<ItemGroup>
<PackageReference Include="SimpleVerticalSlice" Version="1.0.0" />
</ItemGroup>
Command pipeline
The pipeline executed by Handle is:
- Null check for
cmd PreHandle(cmd, ct)ValidateSyntax(cmd, ct)PrepareOperation(cmd, ct)Authorize(cmd, ct)ValidateBusinessRules(cmd, ct)ExecuteBusiness(cmd, ct)→ business resultPostHandle(businessResult, ct)→ final result- On any exception in steps 1–8:
ErrorHandle(cmd, exception, ct)
All steps are optional except ExecuteBusiness, which is abstract.
Basic usage
1. Define your command
You can use a record or a class. Example:
public record CreateVehicleCommand(string Name, string Plate);
2. Implement your handler
Inherit from CommandHandlerBase<TCommand, TResult> and override the steps you need.
using System;
using System.Threading;
using System.Threading.Tasks;
using SimpleVerticalSlice.CommandHandler;
public sealed class CreateVehicleHandler
: CommandHandlerBase<CreateVehicleCommand, Vehicle>
{
private readonly ApplicationDbContext _db;
public CreateVehicleHandler(ApplicationDbContext db)
{
this._db = db;
}
protected override Task ValidateSyntax(CreateVehicleCommand cmd, CancellationToken ct)
{
if (string.IsNullOrWhiteSpace(cmd.Name))
{
throw new ArgumentException("Name is required.", nameof(cmd.Name));
}
if (string.IsNullOrWhiteSpace(cmd.Plate))
{
throw new ArgumentException("Plate is required.", nameof(cmd.Plate));
}
return Task.CompletedTask;
}
protected override Task ValidateBusinessRules(CreateVehicleCommand cmd, CancellationToken ct)
{
// Example: check business limits, state, etc.
// You can query the database or domain services here.
return Task.CompletedTask;
}
protected override async Task<Vehicle> ExecuteBusiness(CreateVehicleCommand cmd, CancellationToken ct)
{
var entity = new Vehicle
{
Name = cmd.Name,
Plate = cmd.Plate
};
this._db.Vehicles.Add(entity);
return entity;
}
protected override async Task<Vehicle> PostHandle(Vehicle businessResult, CancellationToken ct)
{
// Persist + post-processing
await this._db.SaveChangesAsync(ct);
// You could publish events, send notifications, etc. here
return businessResult;
}
}
3. Call the handler
var cmd = new CreateVehicleCommand("Hybrid car", "1234-ABC");
var handler = new CreateVehicleHandler(dbContext);
var vehicle = await handler.Handle(cmd, cancellationToken);
Integration with MediatR
SimpleVerticalSlice is not a message bus. It is meant to be used behind your message bus (e.g. MediatR) as the internal implementation of each use case.
Example with MediatR:
using MediatR;
using SimpleVerticalSlice.CommandHandler;
public record CreateVehicleCommand(string Name, string Plate) : IRequest<Vehicle>;
public sealed class CreateVehicleHandler
: CommandHandlerBase<CreateVehicleCommand, Vehicle>,
IRequestHandler<CreateVehicleCommand, Vehicle>
{
private readonly ApplicationDbContext _db;
public CreateVehicleHandler(ApplicationDbContext db)
{
this._db = db;
}
// MediatR entry point → delegates to the SimpleVerticalSlice pipeline
public Task<Vehicle> Handle(CreateVehicleCommand request, CancellationToken cancellationToken)
=> this.Handle(request, cancellationToken);
protected override Task ValidateSyntax(CreateVehicleCommand cmd, CancellationToken ct)
{
if (string.IsNullOrWhiteSpace(cmd.Name))
throw new ArgumentException("Name is required.", nameof(cmd.Name));
if (string.IsNullOrWhiteSpace(cmd.Plate))
throw new ArgumentException("Plate is required.", nameof(cmd.Plate));
return Task.CompletedTask;
}
protected override async Task<Vehicle> ExecuteBusiness(CreateVehicleCommand cmd, CancellationToken ct)
{
var entity = new Vehicle
{
Name = cmd.Name,
Plate = cmd.Plate
};
this._db.Vehicles.Add(entity);
return entity;
}
protected override async Task<Vehicle> PostHandle(Vehicle businessResult, CancellationToken ct)
{
await this._db.SaveChangesAsync(ct);
return businessResult;
}
}
Error handling
By default, any exception thrown in the pipeline is passed to:
protected virtual Task<TResult> ErrorHandle(TCommand cmd, Exception exception, CancellationToken ct)
=> Task.FromException<TResult>(exception);
You can override this to add custom behaviour:
protected override Task<TResult> ErrorHandle(CreateVehicleCommand cmd, Exception ex, CancellationToken ct)
{
// Example:
// _logger.LogError(ex, "Error handling CreateVehicleCommand: {@Command}", cmd);
// return Task.FromException<TResult>(new DomainException("Something went wrong", ex));
return base.ErrorHandle(cmd, ex, ct);
}
When to use (and when not)
Great fit for:
Vertical slice / CQRS-ish application layer
Use cases where you often repeat:
- validate → authorize → apply business rules → execute → save/notify
Codebases already using MediatR or similar that want a consistent internal pipeline
Maybe not ideal for:
- Pure queries with trivial logic (you can still use it, but may be overkill)
- Streaming scenarios (
IAsyncEnumerable<T>, large exports) - Extremely complex orchestration with multiple independent persistence steps
License
MIT
| 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
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.