Introduction
In this article, we will learn about 10 Secrets Senior Developers Use to Make Controllers 10× Better (.NET).
Let's get started.
1. Controllers Should Be Painfully Thin
Junior mindset: “The controller is where I put my logic.”
Senior mindset: “If my controller does more than mapping → calling → returning, it’s a smell.”
✅ Good controller responsibilities
HTTP concerns (route, status code, headers)
Model binding & validation
Calling application/domain services
❌ Bad responsibilities
Business rules
Data access
Calculations
Condition-heavy logic
Before (junior-style):
[HttpPost]
public IActionResult CreateOrder(CreateOrderDto dto)
{
if (dto.Quantity <= 0) return BadRequest();
var price = dto.Quantity * 100;
var order = new Order { Price = price };
_db.Orders.Add(order);
_db.SaveChanges();
return Ok(order);
}
After (senior-style):
[HttpPost]
public async Task<IActionResult> CreateOrder(
CreateOrderRequest request)
{
var result = await _orderService.CreateAsync(request);
return Ok(result);
}
2. Controllers Should NOT Know About EF Core
If your controller references:
DbContext
DbSet
.Include()
.SaveChanges()
🚨 You already lost separation of concerns
Senior pattern
Controller → Application Service → Domain → Infrastructure (EF)
Controllers talk to services, not databases.
3. Always Return ActionResult (Not Just T)
Junior mistake
[HttpGet]
public Order Get(int id)
This locks you into one response shape.
Senior approach
[HttpGet("{id}")]
public async Task<ActionResult<OrderDto>> Get(int id)
{
var order = await _service.GetAsync(id);
if (order is null) return NotFound();
return Ok(order);
}
✅ Enables
Proper HTTP status codes
Better OpenAPI/Swagger docs
Cleaner error handling
4. Trust Model Validation — Don’t Re‑Validate Manually
Juniors re-check everything
if (string.IsNullOrEmpty(dto.Name)) ...
Seniors let the framework work
public class CreateUserRequest
{
[Required]
[EmailAddress]
public string Email { get; set; }
}

Join the conversation! Your thoughts help the community grow.