Introduction
When building web applications in .NET, most developers default to MVC (Model-View-Controller). It’s familiar, well-documented, and deeply integrated into ASP.NET Core.
However, modern API design has shifted toward feature-oriented architectures, where REPR (Request-Endpoint-Response) is gaining traction—especially with libraries like FastEndpoints.
This article provides a deeper, production-oriented comparison, including architectural implications, scalability, testing, and alignment with patterns like CQRS.
What is MVC?
MVC separates concerns into three components:
- Model → Domain/data + business logic
- View → UI rendering (Razor, HTML, etc.)
- Controller → Handles HTTP requests and orchestrates responses
Example
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _service;
public UsersController(IUserService service)
{
_service = service;
}
[HttpGet("{id}")]
public IActionResult GetUser(int id)
{
var user = _service.GetUserById(id);
if (user is null)
return NotFound();
return Ok(user);
}
[HttpPost]
public IActionResult CreateUser(CreateUserDto dto)
{
var user = _service.Create(dto);
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
}
Observations
- Multiple endpoints per controller
- Shared dependencies across actions
- Implicit grouping by resource (UsersController)
- Controllers tend to grow over time (controller bloat)
What is REPR?
REPR stands for:
- Request → Input DTO
- Endpoint → Single use-case handler
- Response → Output DTO
Each HTTP endpoint is implemented as a self-contained unit, typically one class per use-case.
This aligns closely with:
- Vertical Slice Architecture
- CQRS (Command Query Responsibility Segregation)
REPR with FastEndpoints
FastEndpoints is a .NET library that implements REPR cleanly with minimal boilerplate.
Example
public class GetUserRequest
{
public int Id { get; set; }
}
public class GetUserResponse
{
public int Id { get; set; }
public string Name { get; set; }
}
public class GetUserEndpoint : Endpoint<GetUserRequest, GetUserResponse>
{
private readonly IUserService _service;
public GetUserEndpoint(IUserService service)
{
_service = service;
}
public override void Configure()
{
Get("/api/users/{id}");
AllowAnonymous();
}
public override async Task HandleAsync(GetUserRequest req, CancellationToken ct)
{
var user = await _service.GetUserByIdAsync(req.Id);
if (user is null)
{
await SendNotFoundAsync(ct);
return;
}
await SendAsync(new GetUserResponse
{
Id = user.Id,
Name = user.Name
}, cancellation: ct);
}
}
Architectural Comparison
1. Organization
| Concern | MVC | REPR |
|---|---|---|
| Structure | Horizontal (controllers) | Vertical (features) |
| Grouping | By resource | By use-case |
| Files | Fewer, larger | More, smaller |
Key Insight:
REPR follows "screaming architecture" — your folder structure reflects business capabilities.
2. Coupling & Cohesion
-
MVC
- High intra-controller coupling
- Shared dependencies across unrelated actions
- Risk of unintended side effects
-
REPR
- High cohesion per endpoint
- Minimal coupling
- Dependencies are scoped per use-case
3. Scalability (Codebase, not infra)
-
MVC
- Controllers grow (10–20+ actions common)
- Navigation becomes harder
- Merge conflicts increase in teams
-
REPR
- Linear scalability (add new files, not modify existing ones)
- Safer parallel development
- Better suited for large teams
4. Testing
MVC
- Requires controller setup
- Often involves mocking multiple dependencies
- Harder to isolate logic
REPR
- Endpoint = unit of test
- Minimal mocking
- Highly deterministic
// Example test idea (pseudo)
var endpoint = new GetUserEndpoint(fakeService);
await endpoint.HandleAsync(request, ct);
5. Alignment with CQRS
| Pattern | MVC | REPR |
|---|---|---|
| Commands & Queries | Mixed in controllers | Naturally separated |
| Read/Write models | Often shared | Easily separated |
| Mental model | Resource-centric | Use-case-centric |
REPR maps almost 1:1 with CQRS.
6. Performance Considerations
FastEndpoints removes some MVC overhead:
- No controller discovery pipeline
- Less reflection-heavy execution
- Leaner middleware interaction
While differences are often negligible, REPR frameworks can offer slightly better throughput and lower allocations.
FastEndpoints: Key Features
- Strongly typed request/response models
- Built-in validation pipeline
- Pre/post processors (middleware-like)
- Fluent configuration
- Minimal boilerplate
- Endpoint-level dependency injection
Example: Validation
public class CreateUserValidator : Validator<CreateUserRequest>
{
public CreateUserValidator()
{
RuleFor(x => x.Name).NotEmpty();
}
}
When to Use MVC
Use MVC if:
- You are building server-rendered apps (Razor Views)
- Your team is heavily invested in MVC
- You are maintaining legacy systems
- You prefer convention over explicit structure
When to Use REPR (FastEndpoints)
Use REPR if:
- You are building APIs or microservices
- You want feature-based organization
- You value low coupling and high cohesion
- You are adopting CQRS or Vertical Slice Architecture
- You want better team scalability
Trade-offs
MVC Drawbacks
- Controller bloat
- Harder long-term maintainability
- Implicit coupling between endpoints
REPR Drawbacks
- Higher file count
- Initial learning curve
- Less "standard" in enterprise environments (for now)
Folder Structure Comparison
MVC
Controllers/
UsersController.cs
Services/
Models/
REPR
Features/
Users/
GetUser/
Endpoint.cs
Request.cs
Response.cs
CreateUser/
Endpoint.cs
Validator.cs
Final Thoughts
MVC is a proven and stable architectural pattern, but it shows its age in large, API-heavy systems.
REPR—especially with FastEndpoints—offers:
- Better scalability
- Clearer separation of concerns
- Strong alignment with modern architectural patterns
If you're building a new .NET API in 2026, REPR is not just an alternative—it’s often the more maintainable default.
Further Reading
- FastEndpoints GitHub
- ASP.NET Core MVC docs
- Vertical Slice Architecture (Jimmy Bogard)
Top comments (0)