Gestionnaires de routage dans les applications API minimales

Un attribut WebApplication configuré prend en charge Map{Verb} et MapMethods, où {Verb} correspond à une méthode HTTP en casse Pascal, comme Get, Post, Put ou 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();

Les arguments Delegate passés à ces méthodes sont appelés « gestionnaires de routes ».

Gestionnaires de routes

Les gestionnaires de routes sont des méthodes qui s’exécutent lorsque la route correspond. Les gestionnaires de routes peuvent être une expression lambda, une fonction locale, une méthode d’instance ou une méthode statique. Les gestionnaires de routes peuvent être synchrones ou asynchrones.

Expression 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();

Fonction locale

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

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

app.MapGet("/", LocalFunction);

app.Run();

Méthode d'instance

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éthode statique

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";
    }
}

Point de terminaison défini à l’extérieur de Program.cs

Les API minimales n’ont pas besoin d’être situées à l’emplacement 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" });
        });
    }
}

Consultez également Groupes d’itinéraires plus loin dans cet article.

Les points de terminaison peuvent recevoir des noms afin de générer des URL vers le point de terminaison. L’utilisation d’un point de terminaison nommé évite d’avoir à coder des chemins d’accès en dur dans une application :

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();

Le code précédent affiche The link to the hello endpoint is /hello à partir du point de terminaison /.

REMARQUE : Les noms des points de terminaison respectent la casse.

Les noms des points de terminaison :

  • Il doit être globalement unique.
  • Sont utilisés comme ID d’opération OpenAPI lorsque la prise en charge d’OpenAPI est activée. Pour plus d’informations, consultez OpenAPI.

Paramètres de routage

Les paramètres de routage peuvent être capturés dans le cadre de la définition du modèle de route :

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();

Le code précédent retourne The user id is 3 and book id is 7 à partir de l’URI /users/3/books/7.

Le gestionnaire de routes peut déclarer les paramètres à capturer. Lorsqu’une requête est effectuée sur une route avec des paramètres déclarés pour la capture, les paramètres sont analysés et transmis au gestionnaire. Cela permet de capturer facilement les valeurs avec un type sécurisé. Dans le code précédent, userId et bookId sont tous deux de type int.

Dans le code précédent, si l’une ou l’autre valeur de route ne peut pas être convertie en int, une exception est levée. La requête GET /users/hello/books/3 lève l’exception suivante :

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

Caractères génériques et routes catch all

Les éléments suivants interceptent tous les retours de route Routing to hello à partir du point de terminaison « /posts/hello » :

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

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

app.Run();

Contraintes d'itinéraire

Les contraintes de routage limitent le comportement de correspondance d’une route.

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();

Le tableau suivant montre les modèles de route précédents et leur comportement :

Modèle de routage Exemple d’URI en correspondance
/todos/{id:int} /todos/1
/todos/{text} /todos/something
/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

Pour plus d’informations, consultez Référence sur la contrainte d’itinéraire dans Routage dans ASP.NET Core.

Groupes de routes

La méthode d’extension MapGroup permet d’organiser des groupes de points de terminaison avec un préfixe commun. Cela réduit le code répétitif et permet de personnaliser des groupes entiers de points de terminaison avec un seul appel à des méthodes comme RequireAuthorization et WithMetadata, qui ajoutent des métadonnées de point de terminaison.

Par exemple, le code suivant crée deux groupes de points de terminaison similaires :

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;
}

Dans ce scénario, vous pouvez utiliser une adresse relative pour l’en-tête Location dans le résultat 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);
}

Le premier groupe de points de terminaison correspond uniquement aux requêtes précédées de /public/todos, accessibles sans authentification. Le second groupe de points de terminaison correspond uniquement aux requêtes préfixées par /private/todos, qui nécessitent une authentification.

La QueryPrivateTodosfabrique de filtres de point de terminaison est une fonction locale qui modifie les paramètres TodoDb du gestionnaire de routes pour autoriser l’accès aux données de tâche privées et les stocker.

Les groupes de routage prennent également en charge les groupes imbriqués et les modèles de préfixe complexes avec des contraintes et des paramètres de routage. Dans l’exemple suivant, un gestionnaire de routage mappé au groupe user peut capturer les paramètres de routage {org} et {group} définis dans les préfixes de groupe externe.

Le préfixe peut également être vide. Cela peut être utile pour ajouter des métadonnées ou des filtres de point de terminaison à un groupe de points de terminaison sans modifier le modèle de routage.

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

L’ajout de filtres ou de métadonnées à un groupe se comporte de la même façon que si vous les ajoutiez individuellement à chaque point de terminaison avant d’ajouter des filtres ou des métadonnées supplémentaires qui ont pu être ajoutés à un groupe interne ou à un point de terminaison spécifique.

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);
});

Dans l’exemple ci-dessus, le filtre externe enregistre la requête entrante avant le filtre interne, même si elle a été ajoutée en deuxième. Étant donné que les filtres ont été appliqués à différents groupes, l’ordre dans lequel ils ont été ajoutés les uns par rapport aux autres n’a pas d’importance. Les filtres d’ordre ajoutés sont importants s’ils sont appliqués au même groupe ou au même point de terminaison spécifique.

Une requête sur /outer/inner/ journalisera les éléments suivants :

/outer group filter
/inner group filter
MapGet filter

Liaison de paramètres

La liaison de paramètres dans les applications API minimales décrit en détail les règles de remplissage des paramètres du gestionnaire de routage.

Réponses

Créer des réponses dans des applications API minimales décrit en détail la façon dont les valeurs retournées par les gestionnaires d’itinéraires sont converties en réponses.