.NET 10Vertical Slice ArchitectureFree Project Template

Every feature in one folder

A production-ready .NET backend template built on Vertical Slice Architecture. Each use case is a folder holding its endpoint, handler, validator and mapping. Fourteen projects, one Core, no MediatR, no repositories. JWT auth, EF Core with PostgreSQL, OpenTelemetry, Aspire, Docker and 116 tests are already wired.

Get the template
No Credit Card Required • 100% Free
By requesting the template, you agree to receive emails with .NET newsletter from antondevtips. You can unsubscribe at any time.
5,000+developers use this template
POST /api/shipmentsFeatures/Shipments/CreateShipment/

The use case. DbContext injected directly, returns a Result instead of throwing, publishes a domain event.

Domains
4

Domains

Projects
14

Projects

Tests
116

Tests

Endpoints
20

Endpoints

Framework
.NET 10

Framework

Anatomy of a slice

Open one folder, see the whole use case

No jumping between Domain, Application, Infrastructure and Web to follow a single request. Everything CreateShipment needs sits in CreateShipment.

Core/Shipments/Features/CreateShipment/CreateShipment.Endpoint.cs
namespace Core.Shipments.Features.CreateShipment; public sealed record CreateShipmentRequest( string OrderId, Address Address, string Carrier, string ReceiverEmail, List<ShipmentItemRequest> Items); public class CreateShipmentApiEndpoint : IApiEndpoint { public void MapEndpoint(WebApplication app) { app.MapPost(RouteConsts.BaseRoute, Handle); } private static async Task<IResult> Handle( [FromBody] CreateShipmentRequest request, IValidator<CreateShipmentRequest> validator, ICreateShipmentHandler handler, CancellationToken cancellationToken) { var validationResult = await validator.ValidateAsync(request, cancellationToken); if (!validationResult.IsValid) { return Results.ValidationProblem(validationResult.ToDictionary()); } var response = await handler.HandleAsync(request, cancellationToken); return response.IsError ? response.Errors.ToProblem() : Results.Ok(response.Value); } }

The route

  • The request record and the endpoint live in the same file, so the contract is right where the route is.
  • Endpoints implement IApiEndpoint and are discovered by scanning the Core assembly once at startup.
  • Validation runs before the handler and failures come back as a ValidationProblem.
  • Business errors map to ProblemDetails through ToProblem(). Exceptions are for bugs, not for flow.
Guardrails

Guardrails, not folder conventions

Vertical slices are easy to navigate and easy to abuse. These three things keep a growing slice honest, and the last one fails the build if it is not.

01

Contracts between domains

Shipments never touches the Stocks DbContext. It calls a contract interface, wrapped in a tracing decorator so the hop is visible in Jaeger.

IStockModuleApiTracedStockModuleApi
02

Internal-only slices

CheckStock and DecreaseStock have handlers and no HTTP endpoint at all. They are reachable only through the contract, never from the outside.

CheckStockDecreaseStock
03

Result and validation

A Result type with typed errors, one validator per use case and a global exception handler returning ProblemDetails. Business failures never throw.

Result<T>ErrorProblemDetails
Common/Modules.Common.Tests.Architecture/ModuleTests.cs
[Fact] public void Domain_ShouldNotHaveDependencyOn_CoreInfrastructureOrHost() { var result = Types.InAssembly(ModuleAssemblies.DomainAssembly) .Should() .NotHaveDependencyOnAny( CoreNamespace, InfrastructureNamespace, HostNamespace, CommonApiNamespace, CommonInfrastructureNamespace) .GetResult(); Assert.True(result.IsSuccessful, string.Join(", ", result.FailingTypeNames ?? [])); }
Passed! - Failed: 0, Passed: 116, Skipped: 0

Three NetArchTest rules run with the rest of the suite: Domain must not know about Core, Infrastructure or the host, Infrastructure must not know about Core or the host, and Core must not know about the host. Layering is checked by the build instead of trusted to code review.

Included

The boring parts, already decided

Everything a new .NET API needs before it can do anything useful, configured and running.

Minimal APIs, no MediatR

Endpoints, handlers and validators are discovered by scanning one assembly. Handlers are plain classes, so a stack trace leads straight to your code.

EF Core and PostgreSQL

A DbContext per domain with its own schema and migration history, entity configurations and an auditing interceptor.

Auth that survives production

ASP.NET Identity with JWT, refresh tokens and revocation, plus policy-based authorization defined per domain.

Result pattern and validation

A Result type with typed errors, FluentValidation per use case and a global exception handler returning ProblemDetails.

Serilog, Seq and OpenTelemetry

Structured logs land in Seq, traces and metrics in Jaeger. Each domain has its own activity source and tracing middleware.

Aspire and Docker Compose

Run it with .NET Aspire locally, or bring the API, PostgreSQL, Seq and Jaeger up with Docker Compose and a real health check.

Tests you can run today

116 tests: unit tests, integration tests on Testcontainers with Respawn cleanup, and NetArchTest rules for the layering.

Quality gates in the build

Meziantou, Sonar and Roslynator analyzers with warnings as errors, central package management and .editorconfig.

Swagger and 21 ready requests

Swagger UI in Development plus 21 .http files with an environment file, so you can call every endpoint from your IDE.

The shape of it

Slices across the layers, not instead of them

Each use case cuts vertically through the API, application and data access it needs, while the Domain layer stays free of infrastructure. That is what the architecture tests protect.

Vertical Slice Architecture combined with Clean Architecture: use cases cutting vertically through the API, Application, Domain and Infrastructure layers
Vertical slices for features, clean layering underneath, enforced by three architecture tests.
The solution

Fourteen projects you can hold in your head

Four layers, four domains inside each, and a Core project where every feature lives. Nothing is more than two folders away.

  • Four domains live as namespaces inside Core, Domain and Infrastructure. No project per domain to keep in sync.
  • A slice is a folder: endpoint, handler, validators, mapping and any events it raises.
  • Deleting a feature means deleting a folder. Nothing is stranded in a shared DTO project.
  • Adding a feature means copying a folder and renaming it. Registration is automatic.
  • Outgrow a slice and you can promote it. The folder boundary already shows exactly what moves.
dotnet-vsa-template/src/
Core/ # every vertical slice
Shipments/Features/CreateShipment/
CreateShipment.Endpoint.cs
CreateShipment.Handler.cs
CreateShipment.Validators.cs
CreateShipment.Mapping.cs
Events/ # event + two handlers
Stocks/InternalApi/ # contracts + decorators
Domain/ # entities, value objects, policies
Infrastructure/ # DbContexts, migrations, auth
Common/ # Result, events, error handling
Modules.Common.Tests.Architecture/ # layer rules
WebApi.Host/ # Program.cs, seeding, .http files
AppHost/ # .NET Aspire
Tests/ # unit + Testcontainers
NetProjectTemplate.slnx # modern solution format

Free Project Template, .NET 10.

Clone it, run it, and start on your own features instead of your own plumbing.

Get the template
Getting started

Running before your coffee gets cold

Three steps from the email in your inbox to a request hitting a real database.

  1. 1

    Drop your email

    The template arrives in 2-3 minutes as ready-to-run solution.

  2. 2

    Start the stack

    Aspire provisions PostgreSQL for you. Docker Compose is there too and waits for the database health check before starting the API.

    dotnet run --project src/AppHost
  3. 3

    Add your own feature

    Copy a feature folder and rename it. Add your code. Run the solution. Swagger is on localhost:5000, Seq on 8081, Jaeger on 16686. Open a .http file, fire a request and you're ready.

Two templates

Which one should you start from?

Both templates build the same shipping application with the same stack. They differ in how hard the boundaries are.

Projects

.NET Backend
14 projects, one Core
Modular Monolith
25 projects, three per module

Boundaries

.NET Backend
Namespaces plus layer architecture tests
Modular Monolith
Separate projects plus PublicApi contracts

Cross-domain calls

.NET Backend
Contract interfaces with tracing decorators inside Core
Modular Monolith
Contract interfaces in dedicated PublicApi projects

Best for

.NET Backend
Teams that want the smallest solution to navigate
Modular Monolith
Teams that may extract a module into a service later

Stack

.NET Backend
.NET 10, EF Core, Postgres, JWT, OTel, Aspire
Modular Monolith
Identical

Need harder boundaries, with each module in its own projects and its own public contract? Start from the Modular Monolith template instead.

See the Modular Monolith template
Questions

Questions people ask

What is Vertical Slice Architecture?

Code is organised by business capability instead of by technical layer. Rather than a Command in one project, a Handler in another and a DTO in a third, everything a use case needs lives in one folder. You read a feature top to bottom in one place, and you delete it the same way.

How is this different from the Modular Monolith template?

Same application, same stack, harder boundaries in the other one. This template keeps the four domains as namespaces inside a single Core project and relies on architecture tests, which gives you fourteen projects instead of twenty-five. The Modular Monolith template gives each module its own projects and a PublicApi contract, which is what you want if you expect to extract a service later.

Why are there no repositories?

DbContext is already a unit of work and DbSet is already a repository. Wrapping them again costs you LINQ, change tracking and half of EF Core's query capability in exchange for an abstraction most teams never swap out. Integration tests run against a real PostgreSQL container instead.

Why are there no service classes?

A ShipmentService starts with one method and ends up with fifteen, and every caller drags in all of them. Here each use case is its own handler class: the Shipments domain has eight slices, from CreateShipment to CancelShipment. The constructor injects only what that one use case needs. If you need to write unit tests, you will need only 2 mocked dependencies instead of 10.

Why is there no MediatR?

A handler is just a class. The template registers handlers by assembly scanning and the endpoint injects the interface it needs, so there is no pipeline to configure, no behaviour ordering to reason about and one less licensed dependency.

Which .NET version does the template target?

.NET 10, with the modern .slnx solution format. The patterns work on .NET 8 and 9 too, though you will need to change the target framework and step back a few package versions.

Is it really free?

Yes, no credit card required. You will also get my .NET newsletter, and you can unsubscribe whenever you want.

Your next feature is one folder away

The full .NET backend template, free, in your inbox. Clone it, run it, and spend day one on the part that is actually yours.

Free Project Template. No credit card.