Vertical Slice Architecture Is Easier Than You Think (.NET)
A gentle, beginner-friendly guide to Vertical Slice Architecture in .NET. Learn to organise code by feature with simple examples, diagrams, and no scary tools.
A lunchbox, not a buffet line
Think about how lunch works at a busy school.
In one school, the food is served buffet style. There is a long counter. One person serves only rice. The next serves only dal. The next serves only curry. To get a full meal, you walk down the whole line and collect one spoon of each thing from a different person. If the rice runs out, the whole line slows down.
In another school, every child gets a lunchbox. Inside one box is everything for that child's meal — rice, dal, curry, and a sweet, all packed together. You open one box and your full meal is right there. You do not walk anywhere.
Code can be arranged in these same two ways.
The buffet line is how most older .NET apps are built. All the controllers sit in one folder. All the services sit in another. All the database code sits in a third. To build one feature, you walk down the whole line, picking up a piece from each folder.
The lunchbox is Vertical Slice Architecture (VSA). Everything one feature needs is packed into one folder — the endpoint, the logic, the validation, and the data access. You open one box and the whole feature is there.
People hear the word "architecture" and feel scared. But this idea is gentle. By the end of this post you will see why it is easier than you think.
What a "slice" actually is
A slice is one feature, packed in one place.
Not a layer. Not a service class. One thing your app does.
Here are some real slices:
- "Add a product to the cart"
- "Place an order"
- "Get my order history"
- "Cancel a booking"
Each of these is a vertical cut through your whole app. It touches the web part, the rules part, and the database part. Instead of spreading those pieces across the building, you put them in one drawer labelled with the feature name.
The key idea: a slice is self-contained. To understand "Place an order", you open one folder and read top to bottom. You do not hunt across five other folders.
The problem slices solve
Let us look at the buffet-line way more closely, because seeing the pain helps the cure make sense.
In a classic layered app, adding one small feature like "Create Order" makes you touch many files in many folders:
- a controller in the API folder
- a service in the Application folder
- an interface for that service
- a repository in the Infrastructure folder
- a DTO, a validator, and a mapping profile in yet more folders
Each of these files lives next to files from other features. The OrdersController sits beside the UsersController and the PaymentsController. So when you want to change order code, you keep bumping into payment code and user code that has nothing to do with you.
This causes three everyday problems:
- Lots of jumping. To read one feature, you open many folders. Your eyes get tired and you lose the thread.
- Scary changes. Many features share the same big service and repository classes. Touch one method and you might break a feature you forgot existed.
- Slow new starters. A new teammate must learn the whole layer map before they can ship a small change.
Vertical slices remove all three. One feature, one folder. Change it without fear.
Buffet line vs lunchbox, side by side
Here is the same idea as a quick table.
| Question | Layered (buffet line) | Vertical Slice (lunchbox) |
|---|---|---|
| Where is one feature's code? | Spread across many folders | All in one folder |
| To add a feature, I touch... | A controller, service, repo, and more | Usually one slice file |
| Risk of breaking other features | Higher (shared big classes) | Lower (isolated slices) |
| Easy for a new teammate? | Must learn the whole map | Read one folder |
| Folder names tell you... | The tech (controllers, services) | The features (Orders, Cart) |
Notice the last row. With slices, your folder names scream what the app does. This is sometimes called "screaming architecture". You open the project and immediately see Orders, Cart, and Booking — not a wall of generic tech folders.
What a project looks like
Let us make it real. Imagine a small shop API. With vertical slices, the folder tree is organised by feature.
src/
Features/
Orders/
PlaceOrder.cs
GetOrderHistory.cs
CancelOrder.cs
Cart/
AddItemToCart.cs
RemoveItemFromCart.cs
Products/
SearchProducts.cs
Shared/
Database/
Results/
Program.csLook at Features/Orders/. Every order action is one file. To work on placing an order, you open PlaceOrder.cs and everything is there. No tour of the building needed.
The Shared folder holds the few things that truly belong to everyone, like the database connection. We will talk more about shared code soon.
Building one slice
Steps
Name it
Pick a clear feature name
One file
Create one slice file
Endpoint
Map the route
Logic
Add the rules
Data
Read or write the DB
Done
Ship without fear
A complete slice in plain .NET
Here is a full slice with no special library. Just a minimal API endpoint and a small handler class. This is all you need to start.
namespace Shop.Features.Cart;
// The data coming in from the request.
public record AddItemRequest(int ProductId, int Quantity);
// The endpoint: this is the "front door" of the slice.
public static class AddItemToCart
{
public static void MapEndpoint(IEndpointRouteBuilder app)
{
app.MapPost("/cart/items", Handle);
}
// The logic lives right next to the endpoint.
private static async Task<IResult> Handle(
AddItemRequest request,
ShopDbContext db)
{
if (request.Quantity <= 0)
{
return Results.BadRequest("Quantity must be at least 1.");
}
var item = new CartItem
{
ProductId = request.ProductId,
Quantity = request.Quantity
};
db.CartItems.Add(item);
await db.SaveChangesAsync();
return Results.Ok(new { item.Id });
}
}Read it top to bottom. The request shape, the route, the rule (quantity must be positive), and the database write all sit in one place. A new teammate can understand this feature in under a minute.
To turn it on, you call the slice's own map method in Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<ShopDbContext>();
var app = builder.Build();
// Each slice registers its own endpoint.
AddItemToCart.MapEndpoint(app);
PlaceOrder.MapEndpoint(app);
GetOrderHistory.MapEndpoint(app);
app.Run();That is the whole pattern. No magic. No framework you must master first. You can grow into nicer helpers later, but you never have to.
Commands and queries: two kinds of slices
Most slices fall into one of two simple groups.
- A command changes something. "Place an order" creates an order. "Cancel a booking" removes one.
- A query just reads. "Get my order history" reads and returns a list. It changes nothing.
This split is called CQRS (Command Query Responsibility Segregation). The name sounds big, but the idea is small: writing and reading are different jobs, so give them different slices.
Why does this help? Because a query can be tuned for speed without worrying about write rules, and a command can be careful about rules without slowing reads. Each slice does one job well.
Here is a tiny query slice. Notice it only reads.
namespace Shop.Features.Orders;
public static class GetOrderHistory
{
public static void MapEndpoint(IEndpointRouteBuilder app)
{
app.MapGet("/orders/history", Handle);
}
private static async Task<IResult> Handle(int customerId, ShopDbContext db)
{
var orders = await db.Orders
.Where(o => o.CustomerId == customerId)
.Select(o => new { o.Id, o.Total, o.PlacedAt })
.ToListAsync();
return Results.Ok(orders);
}
}The route for this is GET /orders/history. The slice owns its own query shape. If you later need a faster read, you change this one file and nothing else.
A note on MediatR
You will see many older tutorials use a library called MediatR for slices. It turns each slice into a "command" or "query" object with a matching handler, and a central dispatcher routes them.
Two honest points for 2026:
- You do not need it. Everything above works with plain classes and minimal API endpoints. The slice pattern is about where your files live, not about any tool.
- It is now commercially licensed. Since mid-2025, newer MediatR versions ship under a license that needs a paid tier for many business uses. The same is true for MassTransit. So do not feel you must reach for them.
If you do want a mediator-style helper, there are free, open-source options, or you can write a tiny dispatcher yourself in about a hundred lines. But for learning, plain handlers are clearer and easier to debug. Start there.
| Approach | Free to use? | Good for beginners? |
|---|---|---|
| Plain handlers + minimal API | Yes | Yes, start here |
| Hand-written tiny dispatcher | Yes | Later, when you want it |
| MediatR (newer versions) | Paid tier for business | Not needed to learn |
| Open-source mediator libraries | Often yes | Optional |
Handling shared code without copying
There is one fair worry about slices. If every feature is on its own, do we copy the same code into every folder?
The honest answer: a little repetition is fine, but real duplication needs a home.
Use a simple rule. Share things that are stable and truly common. Keep things that change per feature inside the slice.
- A database context, a money type, or a "Result" wrapper are stable and common. Put them in
Shared. - A request shape, a validation rule, or a special query is specific to one feature. Keep it in the slice.
Where does this code go?
Steps
New code
You wrote something
Used by many?
More than one feature?
Stable?
Rarely changes?
Shared
Move to Shared folder
In slice
Keep it local
If two slices start to share a real chunk of logic, that is a signal. Maybe they belong to the same small module. You can group related slices into a feature module and let them share a private helper. This is how slices grow naturally into a modular monolith without a big redesign.
How slices help your team
Picture three teammates. One works on Cart, one on Orders, one on Products.
With layered code, all three keep editing the same giant service and repository files. They bump into each other. Merge conflicts pile up. A change in one corner breaks another.
With slices, each person mostly stays inside their own feature folder. Their files rarely overlap. They move fast and step on each other far less.
Testing gets simpler too. To test "Place an order", you test that one slice. You give it a request and check the result. You do not wire up five layers first. Small, focused tests are easier to write and faster to run.
When slices are not the best fit
Slices are friendly, but no tool fits every job. Be honest about the limits.
- Very tiny apps. If your app has only two or three endpoints, the extra folders may feel heavier than the gain. Plain layering is fine.
- Heavy shared logic. If almost every feature runs the same complex rules, slices can lead to copying unless you pull that logic into a shared core with care.
- No team discipline. Slices need a shared rule for what goes in
Shared. Without it, code can sprawl. A short team agreement fixes this.
For most growing apps with many distinct features, though, slices keep the code calm and clear as it grows.
Putting it together
Here is the whole flow of a request through a slice, start to finish.
One route. One handler. One trip to the database. One response. All in one file you can read in a minute. That is the whole promise of vertical slices, and that is why it really is easier than you think.
Quick recap
- A slice is one feature packed in one folder: its endpoint, rules, and data access together.
- Layered code is a buffet line (walk many folders for one meal). Slices are a lunchbox (one box, full meal).
- Slices cut down on jumping, lower the risk of breaking other features, and help new teammates start fast.
- Most slices are either a command (writes) or a query (reads). That split is CQRS, and it is simpler than its name.
- You do not need MediatR. It is now commercially licensed. Plain handlers and minimal APIs work great and are easier to debug.
- Put stable, common code in
Shared. Keep feature-specific code in the slice. - Slices help teams work side by side and make tests small and focused.
- For tiny apps, plain layering is fine. For growing apps with many features, slices keep things calm.
References and further reading
- Vertical Slice Architecture — Jimmy Bogard — the original idea, explained by the person who named it.
- Vertical Slice Architecture in ASP.NET Core — Code Maze — a practical .NET walkthrough.
- Vertical Slice Architecture — Milan Jovanović — a clear, modern take with examples.
- N-Layered vs Clean vs Vertical Slice Architecture — Anton Martyniuk — how the three styles compare in 2025.
- You Don't Need MediatR — Julio Casal — why plain handlers are often the better choice today.
Related Posts
Vertical Slice Architecture in .NET: The Easy Guide
Learn Vertical Slice Architecture in .NET in simple words. Organise code by feature instead of by layer, with diagrams, real examples, a comparison with Clean Architecture, and when to use each.
N-Layered vs Clean vs Vertical Slice Architecture in .NET
A simple, friendly guide comparing N-layered, Clean, and Vertical Slice architecture in .NET, with diagrams, code, tables, and clear advice on when to pick each one.
Clean Architecture Folder Structure in .NET: A Simple Guide
Learn how to organise a .NET solution with Clean Architecture. Understand the Domain, Application, Infrastructure, and Presentation layers, the dependency rule, and the exact folders, with diagrams and examples.
Where Vertical Slices Fit Inside the Modular Monolith
A simple guide to how vertical slices live inside the modules of a modular monolith in .NET, with diagrams, code, tables, and everyday examples.
Building a Modular Monolith With Vertical Slice Architecture in .NET
Learn to build a modular monolith using vertical slice architecture in .NET. Simple words, real-life analogy, diagrams, tables, and clean C# code examples.
How to Avoid Code Duplication in Vertical Slice Architecture in .NET
Learn how to avoid code duplication in Vertical Slice Architecture in .NET without breaking your slices. Rule of three, pipeline behaviors, shared infrastructure, and clear examples.