Saltar al contenido
jesusprodriguez.com

dotnet-web-api

.NET: API REST mantenible

Estructura y checklist para una API REST en .NET 8: Minimal APIs por recurso, EF Core sin N+1 y errores en ProblemDetails.

plugin:
.NET
stack:
.NET Core
versión:
v1.0.0
actualizada:
tamaño:
3.1 KB
lectura:
2 min
licencia:
CC-BY-4.0

Cuándo se activa

Al crear endpoints, modelar entidades con EF Core o depurar consultas lentas.

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.

  • Minimal APIs
  • EF Core
  • ProblemDetails
  • Configuración y secretos

Cómo se le pide

> Monta el endpoint de pedidos en .NET 8 siguiendo la estructura que ya tiene el proyecto.

Escríbeselo tal cual al agente: la skill se carga sola por la descripción, no hay que nombrarla.

Cómo se instala

/plugin marketplace add https://jesusprodriguez.com/skills/marketplace.json
/plugin install dotnet@jprodriguez-toolkit

La vía nativa, y la única que se actualiza sola: el marketplace se añade una vez y `/plugin marketplace update` trae las versiones nuevas. Las skills quedan con espacio de nombres propio (`azure-devops:azure-pr-review`).

El fichero, entero

Esto es exactamente lo que descargas: sin resúmenes ni recortes.

.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:

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

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.