Controladores de ruta en las aplicaciones de API mínimas

Un elemento WebApplication configurado admite Map{Verb} y MapMethods donde {Verb} es un método HTTP con mayúsculas y minúsculas Pascal, como Get, Post, Put o Delete:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "This is a GET");
app.MapPost("/", () => "This is a POST");
app.MapPut("/", () => "This is a PUT");
app.MapDelete("/", () => "This is a DELETE");

app.MapMethods("/options-or-head", new[] { "OPTIONS", "HEAD" }, 
                          () => "This is an options or head request ");

app.Run();

Los argumentos Delegate que se pasan a estos métodos se denominan "controladores de ruta".

Controladores de ruta

Los controladores de ruta son métodos que se ejecutan cuando coincide una ruta. Los controladores de ruta pueden ser una expresión lambda, una función local, un método de instancia o un método estático. Los controladores de ruta pueden ser sincrónicos o asincrónicos.

Expresión lambda

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/inline", () => "This is an inline lambda");

var handler = () => "This is a lambda variable";

app.MapGet("/", handler);

app.Run();

Función local

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

string LocalFunction() => "This is local function";

app.MapGet("/", LocalFunction);

app.Run();

Método de instancia

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

var handler = new HelloHandler();

app.MapGet("/", handler.Hello);

app.Run();

class HelloHandler
{
    public string Hello()
    {
        return "Hello Instance method";
    }
}

Método estático

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", HelloHandler.Hello);

app.Run();

class HelloHandler
{
    public static string Hello()
    {
        return "Hello static method";
    }
}

Punto de conexión definido fuera de Program.cs.

Las API mínimas no tienen que encontrarse en Program.cs.

Program.cs

using MinAPISeparateFile;

var builder = WebApplication.CreateSlimBuilder(args);

var app = builder.Build();

TodoEndpoints.Map(app);

app.Run();

TodoEndpoints.cs

namespace MinAPISeparateFile;

public static class TodoEndpoints
{
    public static void Map(WebApplication app)
    {
        app.MapGet("/", async context =>
        {
            // Get all todo items
            await context.Response.WriteAsJsonAsync(new { Message = "All todo items" });
        });

        app.MapGet("/{id}", async context =>
        {
            // Get one todo item
            await context.Response.WriteAsJsonAsync(new { Message = "One todo item" });
        });
    }
}

Consulte también Grupos de rutas más adelante en este artículo.

Se pueden asignar nombres a los puntos de conexión para generar direcciones URL al punto de conexión. El uso de un punto de conexión con nombre evita tener que codificar las rutas de acceso de forma rígida en una aplicación:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/hello", () => "Hello named route")
   .WithName("hi");

app.MapGet("/", (LinkGenerator linker) => 
        $"The link to the hello route is {linker.GetPathByName("hi", values: null)}");

app.Run();

El código anterior muestra The link to the hello endpoint is /hello desde el punto de conexión /.

NOTA: Los nombres de punto de conexión distinguen mayúsculas de minúsculas.

Nombres de punto de conexión:

  • Debe ser único globalmente.
  • Se usan como identificador de operación de OpenAPI cuando se habilita la compatibilidad con OpenAPI. Para obtener más información, consulte OpenAPI.

Parámetros de ruta

Los parámetros de ruta se pueden capturar como parte de la definición del patrón de ruta:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/users/{userId}/books/{bookId}", 
    (int userId, int bookId) => $"The user id is {userId} and book id is {bookId}");

app.Run();

El código anterior devuelve The user id is 3 and book id is 7 a partir del URI /users/3/books/7.

El controlador de rutas puede declarar los parámetros que se capturan. Cuando se realiza una solicitud en una ruta con parámetros declarados para la captura, los parámetros se analizan y se pasan al controlador. Esto facilita la captura de los valores en un método con seguridad de tipos. En el código anterior, userId y bookId son int.

En el código anterior, si alguno de los valores de ruta no se puede convertir en int, se produce una excepción. La solicitud GET /users/hello/books/3 produce la siguiente excepción:

BadHttpRequestException: Failed to bind parameter "int userId" from "hello".

Rutas con carácter comodín y de captura total

La siguiente ruta de captura total devuelve Routing to hello del punto de conexión "/posts/hello":

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");

app.Run();

Restricciones de ruta

Las restricciones de ruta restringen el comportamiento de coincidencia de una ruta.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/todos/{id:int}", (int id) => db.Todos.Find(id));
app.MapGet("/todos/{text}", (string text) => db.Todos.Where(t => t.Text.Contains(text));
app.MapGet("/posts/{slug:regex(^[a-z0-9_-]+$)}", (string slug) => $"Post {slug}");

app.Run();

En la tabla siguiente se muestran las plantillas de ruta anteriores y su comportamiento:

Plantilla de ruta URI coincidente de ejemplo
/todos/{id:int} /todos/1
/todos/{text} /todos/something
/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

Para más información, vea Referencia de restricción de ruta en Enrutamiento en ASP.NET Core.

Grupos de rutas

El método de extensión MapGroup ayuda a organizar grupos de puntos de conexión con un prefijo común. Reduce el código repetitivo y permite personalizar grupos completos de puntos de conexión con una sola llamada a métodos como RequireAuthorization y WithMetadata, que agregan metadatos de punto de conexión.

Por ejemplo, el código siguiente crea dos grupos similares de puntos de conexión:

app.MapGroup("/public/todos")
    .MapTodosApi()
    .WithTags("Public");

app.MapGroup("/private/todos")
    .MapTodosApi()
    .WithTags("Private")
    .AddEndpointFilterFactory(QueryPrivateTodos)
    .RequireAuthorization();


EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
    var dbContextIndex = -1;

    foreach (var argument in factoryContext.MethodInfo.GetParameters())
    {
        if (argument.ParameterType == typeof(TodoDb))
        {
            dbContextIndex = argument.Position;
            break;
        }
    }

    // Skip filter if the method doesn't have a TodoDb parameter.
    if (dbContextIndex < 0)
    {
        return next;
    }

    return async invocationContext =>
    {
        var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
        dbContext.IsPrivate = true;

        try
        {
            return await next(invocationContext);
        }
        finally
        {
            // This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
            dbContext.IsPrivate = false;
        }
    };
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
    group.MapGet("/", GetAllTodos);
    group.MapGet("/{id}", GetTodo);
    group.MapPost("/", CreateTodo);
    group.MapPut("/{id}", UpdateTodo);
    group.MapDelete("/{id}", DeleteTodo);

    return group;
}

En este escenario, puede usar una dirección relativa para el encabezado Location en el resultado 201 Created:

public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
    await database.AddAsync(todo);
    await database.SaveChangesAsync();

    return TypedResults.Created($"{todo.Id}", todo);
}

El primer grupo de puntos de conexión solo coincidirá con las solicitudes con el prefijo /public/todos y que sean accesibles sin autenticación. El segundo grupo de puntos de conexión solo coincidirá con las solicitudes con el prefijo /private/todos y que requieran autenticación.

La QueryPrivateTodosfábrica de filtros de punto de conexión es una función local que modifica los parámetros TodoDb del controlador para permitir el acceso y almacenar datos privados de tareas pendientes.

Los grupos de rutas también admiten grupos anidados y patrones de prefijo complejos con parámetros y restricciones de ruta. En el ejemplo siguiente, y el controlador de rutas asignado al grupo user puede capturar los parámetros de ruta {org} y {group} definidos en los prefijos del grupo externo.

El prefijo también puede estar vacío. Esto puede ser útil para agregar metadatos de punto de conexión o filtros a un grupo de puntos de conexión sin cambiar el patrón de ruta.

var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");

La adición de filtros o metadatos a un grupo se comporta del mismo modo que la adición individual a cada punto de conexión antes de agregar filtros o metadatos adicionales que quizás se hayan agregado a un grupo interno o a un punto de conexión específico.

var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");

inner.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/inner group filter");
    return next(context);
});

outer.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/outer group filter");
    return next(context);
});

inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("MapGet filter");
    return next(context);
});

En el ejemplo anterior, el filtro externo registrará la solicitud entrante antes que el filtro interno aunque se haya agregado en segundo lugar. Dado que los filtros se aplicaron a diferentes grupos, el orden en que se agregaron el uno con respecto al otro no es importante. El orden en que se agregan los filtros es importante si se aplican al mismo grupo o punto de conexión específico.

Una solicitud a /outer/inner/ registrará lo siguiente:

/outer group filter
/inner group filter
MapGet filter

Enlace de parámetros

Enlace de parámetros en las aplicaciones de API mínimas describe las reglas en detalle sobre cómo se rellenan los parámetros del controlador de ruta.

Respuestas

Creación de respuestas en aplicaciones de API mínimas describe detalladamente cómo se convierten en respuestas los valores devueltos de los controladores de ruta.