SimpleVerticalSlice 10.0.0

dotnet add package SimpleVerticalSlice --version 10.0.0
                    
NuGet\Install-Package SimpleVerticalSlice -Version 10.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SimpleVerticalSlice" Version="10.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SimpleVerticalSlice" Version="10.0.0" />
                    
Directory.Packages.props
<PackageReference Include="SimpleVerticalSlice" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SimpleVerticalSlice --version 10.0.0
                    
#r "nuget: SimpleVerticalSlice, 10.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SimpleVerticalSlice@10.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SimpleVerticalSlice&version=10.0.0
                    
Install as a Cake Addin
#tool nuget:?package=SimpleVerticalSlice&version=10.0.0
                    
Install as a Cake Tool

SimpleVerticalSlice

GitLab NuGet NuGet Downloads

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 validation
    • PrepareOperation – preparation before authorization
    • Authorize – permission checks
    • ValidateBusinessRules – domain/business rules
    • ExecuteBusinessrequired core business logic
    • PostHandle – 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:

  1. Null check for cmd
  2. PreHandle(cmd, ct)
  3. ValidateSyntax(cmd, ct)
  4. PrepareOperation(cmd, ct)
  5. Authorize(cmd, ct)
  6. ValidateBusinessRules(cmd, ct)
  7. ExecuteBusiness(cmd, ct) → business result
  8. PostHandle(businessResult, ct) → final result
  9. 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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.

Version Downloads Last Updated
10.0.0 94 1/21/2026
1.0.0 271 12/16/2025