---
name: dotnet-web-api
description: Diseña, implementa y revisa APIs REST en .NET 8 con Minimal APIs o controladores, EF Core y validación. Úsala al crear endpoints, configurar el pipeline, modelar entidades con EF Core o depurar consultas lentas.
version: 1.0.0
license: CC-BY-4.0
updated: 2026-08-16
---

# .NET: API REST mantenible

Convenciones para una API en .NET 8 que se pueda leer dentro de seis meses.

## Estructura

```
src/
  Api/            → endpoints, DI, middleware
  Application/    → casos de uso, DTOs, validadores
  Domain/         → entidades y reglas de negocio
  Infrastructure/ → DbContext, repositorios, servicios externos
```

La dependencia apunta siempre hacia dentro: `Api → Application → Domain`.
`Infrastructure` implementa interfaces declaradas en `Application`.

## Endpoints

Minimal APIs agrupadas por recurso, nunca un `Program.cs` de 400 líneas:

```csharp
public static class ProjectsEndpoints
{
    public static RouteGroupBuilder MapProjects(this IEndpointRouteBuilder app)
    {
        var group = app.MapGroup("/api/projects").WithTags("Projects");

        group.MapGet("/", GetAll);
        group.MapGet("/{id:guid}", GetById).WithName(nameof(GetById));
        group.MapPost("/", Create).RequireAuthorization();

        return group;
    }

    private static async Task<Results<Ok<ProjectDto>, NotFound>> GetById(
        Guid id, IProjectService service, CancellationToken ct)
    {
        var project = await service.GetAsync(id, ct);
        return project is null ? TypedResults.NotFound() : TypedResults.Ok(project);
    }
}
```

- **`TypedResults`** sobre `Results`: documenta el contrato en OpenAPI sin
  atributos extra.
- **`CancellationToken` en toda operación async** que llegue a base de datos.
- La entidad de dominio nunca cruza el límite HTTP: se mapea a DTO.

## EF Core

```csharp
var projects = await _db.Projects
    .AsNoTracking()                       // solo lectura → sin change tracker
    .Where(p => p.Status == status)
    .OrderBy(p => p.Order)
    .Select(p => new ProjectDto(p.Id, p.Title, p.Url))  // proyección en SQL
    .ToListAsync(ct);
```

Checklist antes de dar por buena una consulta:

- [ ] `AsNoTracking()` en lecturas.
- [ ] Proyección con `Select` antes de materializar: no traigas la entidad
      entera para usar tres columnas.
- [ ] Sin N+1: `Include` explícito o proyección; revísalo activando el log de
      SQL en desarrollo.
- [ ] Índice en toda columna usada para filtrar u ordenar.
- [ ] Migraciones revisadas a mano antes de aplicarlas a producción.

## Errores y validación

- Un `IExceptionHandler` global que devuelve **ProblemDetails** (RFC 7807).
  Nada de `try/catch` repetido en cada endpoint.
- Validación con FluentValidation en el borde, no dentro del dominio.
- Nunca devuelvas el mensaje de la excepción al cliente: registra el detalle,
  responde con un identificador de correlación.

## Configuración

- Secretos en User Secrets (desarrollo) o Key Vault (producción). Jamás en
  `appsettings.json` versionado.
- `IOptions<T>` con validación al arrancar (`ValidateOnStart`): mejor fallar en
  el despliegue que en la primera petición.
