.NET  

10 Secrets Senior Developers Use to Make Controllers 10× Better (.NET)

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; }
}
[ApiController]
public class UsersController : ControllerBase

✅ [ApiController] gives you

  • Automatic 400 responses

  • Consistent validation errors

  • Cleaner controllers

5. Never Accept Domain Models as Request Bodies

Junior shortcut

public IActionResult Create(User user)

💥 This causes:

  • Over-posting vulnerabilities

  • Tight coupling

  • Accidental API breaking changes

Senior rule: One DTO per use case

public record CreateUserRequest(
    string Email,
    string Password
);

Controllers deal only in DTOs, not domain entities.

6. Controllers Should Be Dumb About Business Errors

Junior approach

if (!user.IsActive)
    return BadRequest("Inactive user");

Senior approach

  • Business logic throws domain/app errors

  • Controller translates to HTTP

try
{
    await _service.DoSomethingAsync(id);
    return NoContent();
}
catch (UserInactiveException)
{
    return Conflict("User is inactive");
}

✅ Even better

  • Global exception middleware → Controllers stay ultra-clean.

7. Use Explicit Response Types for APIs

Seniors care about consumer clarity.

[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[HttpGet("{id}")]
public async Task<ActionResult<OrderDto>> Get(int id)

✅ Benefits

  • Accurate Swagger/OpenAPI

  • Fewer frontend misunderstandings

  • Better API contracts

8. Controllers Are Not for Orchestration

If you see this…

var a = await serviceA.Run();
var b = await serviceB.Run(a);
await serviceC.Run(b);

🚨 That is application orchestration, not controller logic.

Senior fix

await _checkoutWorkflow.ExecuteAsync(request);

✅ Controllers delegate workflows to services

9. Route Design Is Part of Controller Quality

Junior routes

  • GET /GetUserById

  • POST /CreateUser

Senior RESTful routes

  • GET /users/{id}

  • POST /users

  • PUT /users/{id}

  • DELETE /users/{id}

✅ Seniors think in resources, not methods

10. One Controller = One Aggregate / Use Area

Junior controllers grow endlessly

UsersController
  ├─ Login
  ├─ Register
  ├─ ChangePassword
  ├─ UploadAvatar
  ├─ DeleteAccount

Senior split by responsibility

  • AuthController

  • ProfileController

  • UsersController

✅ Smaller controllers

  • Easier to test

  • Easier to reason about

  • Lower merge conflicts

Conclusion

Here, we tried to cover 10 Secrets Senior Developers Use to Make Controllers 10× Better (.NET).