Gestori di route nelle app per le API minime

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere Criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Un oggetto configurato supporta e dove è un metodo HTTP con maiuscole e minuscole Pascal, ad Getesempio , PostPut o Delete:{Verb} MapMethods Map{Verb} WebApplication

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

Gli Delegate argomenti passati a questi metodi sono denominati "gestori di route".

Gestori di route

I gestori di route sono metodi eseguiti quando la route corrisponde. I gestori di route possono essere un'espressione lambda, una funzione locale, un metodo di istanza o un metodo statico. I gestori di route possono essere sincroni o asincroni.

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

Funzione locale

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

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

app.MapGet("/", LocalFunction);

app.Run();

Metodo di istanza

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

Metodo statico

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

Endpoint definito all'esterno di Program.cs

Non è necessario che le API minime si trovino in 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" });
        });
    }
}

Vedere anche Instradare i gruppi più avanti in questo articolo.

Gli endpoint possono essere assegnati ai nomi per generare URL all'endpoint. L'uso di un endpoint denominato evita di dover impostare percorsi hardcoded in un'app:

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

Il codice precedente viene visualizzato The link to the hello route is /hello dall'endpoint / .

NOTA: i nomi degli endpoint fanno distinzione tra maiuscole e minuscole.

Nomi degli endpoint:

  • Deve essere univoco a livello globale.
  • Vengono usati come ID operazione OpenAPI quando è abilitato il supporto OpenAPI. Per altre informazioni, vedere OpenAPI.

Parametri di route

I parametri di route possono essere acquisiti come parte della definizione del modello di 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();

Il codice precedente restituisce The user id is 3 and book id is 7 dall'URI /users/3/books/7.

Il gestore di route può dichiarare i parametri da acquisire. Quando viene effettuata una richiesta a una route con parametri dichiarati per l'acquisizione, i parametri vengono analizzati e passati al gestore. In questo modo è facile acquisire i valori in modo sicuro per il tipo. Nel codice userId precedente e bookId sono entrambi int.

Nel codice precedente, se uno dei valori di route non può essere convertito in int, viene generata un'eccezione. La richiesta /users/hello/books/3 GET genera l'eccezione seguente:

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

Carattere jolly e intercettare tutte le route

Il seguente catch all route restituisce Routing to hello dall'endpoint '/posts/hello':

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

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

app.Run();

Vincoli della route

I vincoli di route vincolano il comportamento di corrispondenza di una 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();

La tabella seguente illustra i modelli di route precedenti e il relativo comportamento:

Modello di route URI corrispondente di esempio
/todos/{id:int} /todos/1
/todos/{text} /todos/something
/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

Per altre informazioni, vedere Informazioni di riferimento sui vincoli di route in Routing in ASP.NET Core.

Gruppi di route

Il MapGroup metodo di estensione consente di organizzare gruppi di endpoint con un prefisso comune. Riduce il codice ripetitivo e consente di personalizzare interi gruppi di endpoint con una singola chiamata a metodi come RequireAuthorization e WithMetadata che aggiungono metadati dell'endpoint.

Ad esempio, il codice seguente crea due gruppi simili di endpoint:

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

In questo scenario è possibile usare un indirizzo relativo per l'intestazione Location nel 201 Created risultato:

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

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

Il primo gruppo di endpoint corrisponderà solo alle richieste precedute da /public/todos e sono accessibili senza alcuna autenticazione. Il secondo gruppo di endpoint corrisponderà solo alle richieste precedute /private/todos da e richiederanno l'autenticazione.

La QueryPrivateTodos factory del filtro endpoint è una funzione locale che modifica i parametri del TodoDb gestore di route per consentire l'accesso e l'archiviazione di dati todo privati.

I gruppi di route supportano anche gruppi annidati e modelli di prefisso complessi con parametri e vincoli di route. Nell'esempio seguente e il gestore di route mappato al user gruppo possono acquisire i {org} parametri e {group} di route definiti nei prefissi del gruppo esterno.

Il prefisso può anche essere vuoto. Ciò può essere utile per l'aggiunta di metadati o filtri dell'endpoint a un gruppo di endpoint senza modificare il modello di route.

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

L'aggiunta di filtri o metadati a un gruppo ha lo stesso comportamento dell'aggiunta singolarmente a ogni endpoint prima di aggiungere altri filtri o metadati che potrebbero essere stati aggiunti a un gruppo interno o a un endpoint specifico.

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

Nell'esempio precedente, il filtro esterno registra la richiesta in ingresso prima del filtro interno anche se è stato aggiunto secondo. Poiché i filtri sono stati applicati a gruppi diversi, l'ordine in cui sono stati aggiunti l'uno rispetto all'altro non è importante. I filtri dell'ordine vengono aggiunti se applicati allo stesso gruppo o allo stesso endpoint specifico.

Una richiesta per /outer/inner/ registrare quanto segue:

/outer group filter
/inner group filter
MapGet filter

Binding di parametri

L'associazione di parametri nelle applicazioni API minime descrive in dettaglio le regole per il popolamento dei parametri del gestore di route.

Risposte

Creare risposte nelle applicazioni API minime descrive in dettaglio il modo in cui i valori restituiti dai gestori di route vengono convertiti in risposte.