1. Um Controller é uma classe; uma Action é um método
public class ClienteController : Controller
{
private readonly ClienteService _service;
public ClienteController(ClienteService service) => _service = service;
// Action — cada método público (não privado) é um endpoint em potencial
public async Task<IActionResult> Listar()
{
var clientes = await _service.ListarAsync(null, true);
return View(clientes);
}
public async Task<IActionResult> Detalhes(int id)
{
var cliente = await _service.ObterOuFalharAsync(id);
return View(cliente);
}
}
Toda classe cujo nome termina em Controller e herda de
Controller é descoberta automaticamente pelo framework — sem precisar
registrar rotas manualmente uma a uma, como você faria em app.get(...)
do Express.
2. Routing baseado em convenção
Por padrão, o ASP.NET Core MVC usa um padrão de rota que mapeia
/{controller}/{action}/{id?} diretamente para classe/método/parâmetro:
Fig. 1 — Roteamento por convenção: cada segmento da URL mapeia direto para classe, método e parâmetro.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
var app = builder.Build();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
3. Routing baseado em atributos — controle explícito
Para rotas mais específicas (comum em APIs, aprofundado no Módulo 4), atributos dão controle total sobre a URL, sem depender da convenção de nomes:
[Route("clientes")]
public class ClienteController : Controller
{
[Route("")] // GET /clientes
public async Task<IActionResult> Listar() { ... }
[Route("{id:int}")] // GET /clientes/42 (só aceita id numérico)
public async Task<IActionResult> Detalhes(int id) { ... }
[Route("ativos")] // GET /clientes/ativos
public async Task<IActionResult> Ativos() { ... }
}
| Restrição de rota | Exemplo | Efeito |
|---|---|---|
:int | {'{'}id:int{'}'} | Só casa se o segmento for um número inteiro |
:required | {'{'}nome:required{'}'} | Segmento obrigatório |
? | {'{'}id?{'}'} | Segmento opcional |
4. Verbos HTTP em uma Action
Por padrão, uma Action responde a qualquer verbo. Atributos de verbo restringem isso — essencial quando a mesma URL tem comportamentos diferentes para exibir um formulário (GET) e processá-lo (POST):
[HttpGet("clientes/novo")]
public IActionResult Novo() => View(); // exibe o formulário vazio
[HttpPost("clientes/novo")]
public async Task<IActionResult> Novo(Cliente cliente)
{
if (!ModelState.IsValid) // validação — capítulo 5
return View(cliente);
await _service.CriarAsync(cliente);
return RedirectToAction(nameof(Listar));
}
Repare no RedirectToAction após o POST bem-sucedido, em vez de
devolver uma View diretamente. Esse padrão (Post/Redirect/Get) evita que o usuário reenvie o
mesmo formulário sem querer ao apertar F5 — o navegador vai reexecutar o último GET, não o
POST original.
5. IActionResult — os tipos de resposta mais comuns
| Retorno | Resposta HTTP gerada |
|---|---|
View(modelo) | Renderiza a View correspondente com o modelo |
RedirectToAction(nome) | 302, redireciona para outra action |
NotFound() | 404 |
BadRequest(erros) | 400, com detalhes do erro |
Json(dados) | 200 com corpo JSON (mais comum no Módulo 4, APIs) |
6. Onde isso te leva
Com controllers, actions e rotas cobertos, o próximo capítulo completa o outro lado da resposta
View(modelo): como o Razor transforma esse modelo em HTML de verdade.