ASP.NET Core Minimal APIs en 2026: Arquitectura, Rendimiento y Preguntas de Entrevista

Dominar las Minimal APIs de ASP.NET Core para entrevistas técnicas: arquitectura del pipeline de solicitudes, grupos de rutas, filtros de endpoint, compilación Native AOT y patrones de autenticación.

ASP.NET Core Minimal APIs en 2026: Arquitectura, Rendimiento y Preguntas de Entrevista

Las Minimal APIs de ASP.NET Core eliminan la ceremonia de los controladores MVC tradicionales, ofreciendo un enfoque simplificado para construir endpoints HTTP con significativamente menos código repetitivo. Introducidas en .NET 6 y perfeccionadas a través de .NET 8, 9 y ahora .NET 10, las Minimal APIs se han convertido en una opción lista para producción para microservicios, funciones serverless y servicios web ligeros.

Minimal APIs vs Controladores

Las Minimal APIs utilizan manejadores de rutas de nivel superior definidos directamente en Program.cs, mientras que los controladores MVC requieren definiciones de clases, atributos y enrutamiento basado en convenciones. Para operaciones CRUD simples o microservicios con menos de 20 endpoints, las Minimal APIs típicamente reducen el código entre 40-60%.

Arquitectura de Minimal APIs y Pipeline de Solicitudes

El pipeline de solicitudes de ASP.NET Core procesa las solicitudes HTTP a través de componentes middleware antes de alcanzar los manejadores de endpoints. Las Minimal APIs se integran perfectamente con este pipeline mientras ofrecen una sintaxis más declarativa para la definición de rutas.

Program.cscsharp
var builder = WebApplication.CreateBuilder(args);

// Register services for dependency injection
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddScoped<IProductRepository, ProductRepository>();

var app = builder.Build();

// Middleware pipeline configuration
app.UseExceptionHandler("/error");
app.UseHttpsRedirection();
app.UseAuthorization();

// Minimal API endpoint definitions
app.MapGet("/products", async (IProductRepository repo) =>
    Results.Ok(await repo.GetAllAsync()));

app.MapGet("/products/{id:int}", async (int id, IProductRepository repo) =>
    await repo.GetByIdAsync(id) is Product product
        ? Results.Ok(product)
        : Results.NotFound());

app.Run();

Este patrón centraliza las definiciones de rutas mientras mantiene acceso completo al contenedor de inyección de dependencias. El parámetro IProductRepository demuestra la inyección sin constructor directamente en los delegados manejadores.

Grupos de Rutas y Organización de Endpoints

A medida que las aplicaciones crecen, la organización de endpoints se vuelve crítica. Los grupos de rutas, introducidos en .NET 7, proporcionan espacios de nombres y configuración compartida sin sacrificar el enfoque minimalista.

ProductEndpoints.cscsharp
public static class ProductEndpoints
{
    public static void MapProductEndpoints(this WebApplication app)
    {
        var group = app.MapGroup("/api/products")
            .WithTags("Products")
            .RequireAuthorization();

        group.MapGet("/", GetAllProducts);
        group.MapGet("/{id:int}", GetProductById);
        group.MapPost("/", CreateProduct)
            .Accepts<CreateProductRequest>("application/json")
            .Produces<Product>(StatusCodes.Status201Created);
        group.MapPut("/{id:int}", UpdateProduct);
        group.MapDelete("/{id:int}", DeleteProduct)
            .RequireAuthorization("AdminOnly");
    }

    private static async Task<IResult> GetAllProducts(
        IProductRepository repo,
        CancellationToken ct)
    {
        var products = await repo.GetAllAsync(ct);
        return Results.Ok(products);
    }

    private static async Task<IResult> GetProductById(
        int id,
        IProductRepository repo,
        CancellationToken ct)
    {
        var product = await repo.GetByIdAsync(id, ct);
        return product is not null
            ? Results.Ok(product)
            : Results.NotFound();
    }

    private static async Task<IResult> CreateProduct(
        CreateProductRequest request,
        IProductRepository repo,
        IValidator<CreateProductRequest> validator,
        CancellationToken ct)
    {
        var validation = await validator.ValidateAsync(request, ct);
        if (!validation.IsValid)
            return Results.ValidationProblem(validation.ToDictionary());

        var product = await repo.CreateAsync(request.ToProduct(), ct);
        return Results.Created($"/api/products/{product.Id}", product);
    }
}

Llamar a app.MapProductEndpoints() en Program.cs registra todas las rutas de productos con requisitos de autorización compartidos y metadatos OpenAPI. Esta estructura escala bien para aplicaciones con cientos de endpoints.

Enlace de Parámetros y Validación

Las Minimal APIs soportan múltiples fuentes de enlace: parámetros de ruta, cadenas de consulta, encabezados, cuerpos de solicitud y servicios del DI. Comprender la precedencia de enlace previene errores comunes en entrevistas.

Program.cs - Parameter binding examplescsharp
app.MapGet("/search", (
    [FromQuery] string? query,           // Explicit query string
    [FromQuery] int page = 1,            // Default value
    [FromQuery] int pageSize = 20,       // Default value
    [FromHeader(Name = "X-Correlation-Id")] string? correlationId,
    ILogger<Program> logger) =>
{
    logger.LogInformation("Search request: {Query}, Page: {Page}, CorrelationId: {CorrelationId}",
        query, page, correlationId);

    return Results.Ok(new { query, page, pageSize, correlationId });
});

// Complex model binding with validation
app.MapPost("/orders", async (
    [FromBody] CreateOrderRequest request,
    [FromServices] IValidator<CreateOrderRequest> validator,
    [FromServices] IOrderService orderService,
    HttpContext context,
    CancellationToken ct) =>
{
    var validationResult = await validator.ValidateAsync(request, ct);

    if (!validationResult.IsValid)
    {
        return Results.ValidationProblem(
            validationResult.Errors
                .GroupBy(e => e.PropertyName)
                .ToDictionary(
                    g => g.Key,
                    g => g.Select(e => e.ErrorMessage).ToArray()));
    }

    var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
    var order = await orderService.CreateOrderAsync(request, userId!, ct);

    return Results.Created($"/orders/{order.Id}", order);
});

El atributo [FromBody] es opcional para tipos complejos pero mejora la legibilidad. FluentValidation se integra naturalmente a través de inyección de dependencias, manteniendo la lógica de validación separada de los manejadores de endpoints.

Prioridad de Fuentes de Enlace

Cuando no se especifica ningún atributo, las Minimal APIs infieren las fuentes de enlace: primero parámetros de ruta, luego cadenas de consulta para tipos simples, y el cuerpo de la solicitud para tipos complejos. Los atributos explícitos como [FromQuery] o [FromBody] anulan este comportamiento.

Optimización de Rendimiento con Native AOT

.NET 8 introdujo soporte de compilación Native AOT (Ahead-of-Time) para Minimal APIs, produciendo ejecutables autocontenidos con tiempos de inicio inferiores al milisegundo. Esta capacidad hace que las Minimal APIs sean ideales para despliegues serverless donde la latencia de arranque en frío importa.

Program.cs - AOT-compatible configurationcsharp
var builder = WebApplication.CreateSlimBuilder(args);

// AOT-friendly JSON serialization
builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});

var app = builder.Build();

app.MapGet("/health", () => Results.Ok(new HealthResponse("Healthy", DateTime.UtcNow)));

app.Run();

// Source-generated JSON serializer context
[JsonSerializable(typeof(HealthResponse))]
[JsonSerializable(typeof(Product))]
[JsonSerializable(typeof(List<Product>))]
internal partial class AppJsonContext : JsonSerializerContext { }

public record HealthResponse(string Status, DateTime CheckedAt);

El método CreateSlimBuilder excluye características del framework innecesarias, mientras que el JsonSerializerContext generado por fuente elimina la reflexión en tiempo de ejecución para serialización JSON. Los binarios AOT publicados para APIs simples típicamente miden 10-15 MB comparados con 80+ MB para despliegues autocontenidos estándar.

Typed Results y Metadatos de Respuesta

.NET 7 introdujo TypedResults para verificación en tiempo de compilación de tipos de respuesta, mejorando la precisión de documentación OpenAPI y detectando incompatibilidades de tipos durante el desarrollo.

csharp
// Strongly-typed results with OpenAPI metadata
app.MapGet("/products/{id:int}", async Task<Results<Ok<Product>, NotFound, ProblemHttpResult>> (
    int id,
    IProductRepository repo,
    CancellationToken ct) =>
{
    try
    {
        var product = await repo.GetByIdAsync(id, ct);

        return product is not null
            ? TypedResults.Ok(product)
            : TypedResults.NotFound();
    }
    catch (Exception ex)
    {
        return TypedResults.Problem(
            detail: "An error occurred retrieving the product",
            statusCode: StatusCodes.Status500InternalServerError);
    }
})
.WithName("GetProductById")
.WithOpenApi(operation =>
{
    operation.Summary = "Retrieves a product by ID";
    operation.Description = "Returns the product details or 404 if not found";
    return operation;
});

El tipo unión Results<T1, T2, T3> declara todos los tipos de respuesta posibles, que los generadores Swagger/OpenAPI utilizan para producir documentación precisa. Este patrón es particularmente valioso al prepararse para preguntas de entrevista de ASP.NET Core sobre diseño de APIs.

¿Listo para aprobar tus entrevistas de .NET?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Filtros de Endpoint para Preocupaciones Transversales

Los filtros de endpoint proporcionan funcionalidad similar al middleware pero limitada a endpoints o grupos específicos, manejando preocupaciones como logging, caché y transformación de solicitudes.

ValidationFilter.cscsharp
public class ValidationFilter<T> : IEndpointFilter where T : class
{
    public async ValueTask<object?> InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        var validator = context.HttpContext
            .RequestServices
            .GetService<IValidator<T>>();

        if (validator is null)
            return await next(context);

        var argument = context.Arguments
            .OfType<T>()
            .FirstOrDefault();

        if (argument is null)
            return await next(context);

        var validationResult = await validator.ValidateAsync(argument);

        if (!validationResult.IsValid)
        {
            return Results.ValidationProblem(
                validationResult.Errors
                    .GroupBy(e => e.PropertyName)
                    .ToDictionary(
                        g => g.Key,
                        g => g.Select(e => e.ErrorMessage).ToArray()));
        }

        return await next(context);
    }
}

// Usage in Program.cs
app.MapPost("/products", CreateProduct)
    .AddEndpointFilter<ValidationFilter<CreateProductRequest>>();

// Global filter registration via route group
var api = app.MapGroup("/api")
    .AddEndpointFilter(async (context, next) =>
    {
        var logger = context.HttpContext
            .RequestServices
            .GetRequiredService<ILogger<Program>>();

        var stopwatch = Stopwatch.StartNew();
        var result = await next(context);
        stopwatch.Stop();

        logger.LogInformation(
            "Endpoint {Method} {Path} completed in {ElapsedMs}ms",
            context.HttpContext.Request.Method,
            context.HttpContext.Request.Path,
            stopwatch.ElapsedMilliseconds);

        return result;
    });

Los filtros se ejecutan en orden de registro, con el filtro más interno más cercano al manejador del endpoint. Esta arquitectura permite una separación limpia de validación, logging y lógica de autorización.

Patrones de Autenticación y Autorización

Las Minimal APIs soportan los mismos mecanismos de autenticación y autorización que los controladores MVC, con una sintaxis de configuración más declarativa.

Program.cs - JWT authentication setupcsharp
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
        };
    });

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("AdminOnly", policy =>
        policy.RequireRole("Admin"))
    .AddPolicy("PremiumUser", policy =>
        policy.RequireClaim("subscription", "premium", "enterprise"));

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

// Protected endpoints
app.MapGet("/admin/users", async (IUserService userService) =>
    Results.Ok(await userService.GetAllUsersAsync()))
    .RequireAuthorization("AdminOnly");

app.MapGet("/profile", async (ClaimsPrincipal user, IUserService userService) =>
{
    var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
    var profile = await userService.GetProfileAsync(userId!);
    return Results.Ok(profile);
})
.RequireAuthorization();

// Anonymous endpoint within protected group
var protectedGroup = app.MapGroup("/api/secure")
    .RequireAuthorization();

protectedGroup.MapGet("/public-info", () => Results.Ok("This is public"))
    .AllowAnonymous();

El método de extensión RequireAuthorization acepta nombres de políticas o puede llamarse sin argumentos para requerir cualquier usuario autenticado. Comprender estos patrones es esencial para preguntas de entrevista sobre autenticación y autorización.

Preguntas de Entrevista: Patrones Comunes

Las entrevistas técnicas frecuentemente exploran las diferencias entre Minimal APIs y controladores tradicionales. La siguiente tabla resume las distinciones clave:

| Aspecto | Minimal APIs | Controladores MVC | |---------|--------------|-------------------| | Código repetitivo | Bajo - delegados manejadores directos | Mayor - clase + método + atributos | | Enrutamiento | En línea con MapGet, MapPost | Basado en atributos o convenciones | | Enlace de modelo | Automático con atributos opcionales | Convención + atributos | | Filtros | Filtros de endpoint | Filtros de acción + middleware | | Soporte AOT | Completo desde .NET 8 | Limitado, pesado en reflexión | | Testabilidad | Basada en funciones, fácil de probar unitariamente | Requiere instanciación del controlador | | Ideal para | Microservicios, APIs simples | Aplicaciones grandes, flujos complejos |

Consejo de Entrevista

Cuando pregunten "¿Cuándo elegirías controladores sobre Minimal APIs?", menciona aplicaciones complejas que requieren filtros de acción, personalización de enlace de modelo, o bases de código existentes con patrones MVC establecidos. Las Minimal APIs destacan para microservicios greenfield y funciones serverless.

Pruebas de Endpoints Minimal API

Las pruebas de integración con WebApplicationFactory proporcionan verificación realista de endpoints sin desplegar la aplicación.

ProductEndpointsTests.cscsharp
public class ProductEndpointsTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;
    private readonly WebApplicationFactory<Program> _factory;

    public ProductEndpointsTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureServices(services =>
            {
                // Replace real repository with mock
                services.RemoveAll<IProductRepository>();
                services.AddScoped<IProductRepository, MockProductRepository>();
            });
        });
        _client = _factory.CreateClient();
    }

    [Fact]
    public async Task GetProducts_ReturnsOkWithProductList()
    {
        // Act
        var response = await _client.GetAsync("/api/products");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);

        var products = await response.Content
            .ReadFromJsonAsync<List<Product>>();

        products.Should().NotBeNull();
        products.Should().HaveCountGreaterThan(0);
    }

    [Fact]
    public async Task GetProductById_WithInvalidId_ReturnsNotFound()
    {
        // Act
        var response = await _client.GetAsync("/api/products/99999");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NotFound);
    }

    [Fact]
    public async Task CreateProduct_WithValidRequest_ReturnsCreated()
    {
        // Arrange
        var request = new CreateProductRequest("Test Product", 29.99m, "Test Description");

        // Act
        var response = await _client.PostAsJsonAsync("/api/products", request);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Created);
        response.Headers.Location.Should().NotBeNull();
    }
}

Para una exploración más profunda de patrones de arquitectura limpia en aplicaciones .NET, la capa de servicios debe probarse independientemente con pruebas unitarias, mientras que las pruebas de integración verifican el pipeline de solicitudes completo.

Conclusión

Las Minimal APIs representan el enfoque moderno para construir servicios HTTP en ASP.NET Core:

  • Los grupos de rutas y la organización de endpoints escalan desde microservicios simples hasta aplicaciones complejas
  • Los filtros de endpoint proporcionan separación limpia de preocupaciones transversales como validación y logging
  • TypedResults habilita verificación en tiempo de compilación de tipos de respuesta y documentación OpenAPI precisa
  • La compilación Native AOT entrega tiempos de inicio inferiores al milisegundo para despliegues serverless
  • Las pruebas de integración con WebApplicationFactory aseguran verificación realista de endpoints

Para preparación de entrevistas, enfócate en articular cuándo las Minimal APIs son apropiadas versus controladores tradicionales, y demuestra comprensión del pipeline de solicitudes, inyección de dependencias y patrones de autenticación.

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

Compartir

Artículos relacionados