Condividi tramite


Informazioni di riferimento rapido sulle API minime

Note

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

Warning

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

Questo documento:

Le API minime sono costituite da:

WebApplication

Il codice seguente viene generato da un modello ASP.NET Core:

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

app.MapGet("/", () => "Hello World!");

app.Run();

Il codice precedente può essere creato tramite dotnet new web la riga di comando o selezionando il modello Web vuoto in Visual Studio.

Il codice seguente crea un oggetto WebApplication (app) senza creare in modo esplicito un oggetto WebApplicationBuilder:

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run();

WebApplication.Create inizializza una nuova istanza della WebApplication classe con impostazioni predefinite preconfigurate.

WebApplication aggiunge automaticamente il middleware seguente nelle applicazioni API minime a seconda di determinate condizioni:

  • UseDeveloperExceptionPage viene aggiunto per primo quando è HostingEnvironment"Development".
  • UseRouting viene aggiunto secondo se il codice utente non ha già chiamato UseRouting e se sono stati configurati endpoint, ad esempio app.MapGet.
  • UseEndpoints viene aggiunto alla fine della pipeline middleware se sono configurati endpoint.
  • UseAuthentication viene aggiunto immediatamente dopo UseRouting se il codice utente non ha già chiamato UseAuthentication e se IAuthenticationSchemeProvider è possibile rilevare nel provider di servizi. IAuthenticationSchemeProvider viene aggiunto per impostazione predefinita quando si usano AddAuthenticationi servizi e viene rilevato tramite IServiceProviderIsService.
  • UseAuthorization viene aggiunto successivamente se il codice utente non ha già chiamato UseAuthorization e se IAuthorizationHandlerProvider è possibile rilevare nel provider di servizi. IAuthorizationHandlerProvider viene aggiunto per impostazione predefinita quando si usano AddAuthorizationi servizi e viene rilevato tramite IServiceProviderIsService.
  • Il middleware e gli endpoint configurati dall'utente vengono aggiunti tra UseRouting e UseEndpoints.

Il codice seguente è effettivamente ciò che il middleware automatico aggiunto all'app produce:

if (isDevelopment)
{
    app.UseDeveloperExceptionPage();
}

app.UseRouting();

if (isAuthenticationConfigured)
{
    app.UseAuthentication();
}

if (isAuthorizationConfigured)
{
    app.UseAuthorization();
}

// user middleware/endpoints
app.CustomMiddleware(...);
app.MapGet("/", () => "hello world");
// end user middleware/endpoints

app.UseEndpoints(e => {});

In alcuni casi, la configurazione del middleware predefinita non è corretta per l'app e richiede modifiche. Ad esempio, UseCors deve essere chiamato prima UseAuthentication di e UseAuthorization. L'app deve chiamare UseAuthentication e UseAuthorization se UseCors viene chiamato:

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

Se il middleware deve essere eseguito prima che si verifichi la corrispondenza della route, UseRouting deve essere chiamato e il middleware deve essere posizionato prima della chiamata a UseRouting. UseEndpoints in questo caso non è obbligatorio perché viene aggiunto automaticamente come descritto in precedenza:

app.Use((context, next) =>
{
    return next(context);
});

app.UseRouting();

// other middleware and endpoints

Quando si aggiunge un middleware del terminale:

  • Il middleware deve essere aggiunto dopo UseEndpoints.
  • L'app deve chiamare UseRouting e UseEndpoints in modo che il middleware del terminale possa essere posizionato nella posizione corretta.
app.UseRouting();

app.MapGet("/", () => "hello world");

app.UseEndpoints(e => {});

app.Run(context =>
{
    context.Response.StatusCode = 404;
    return Task.CompletedTask;
});

Il middleware del terminale è middleware che viene eseguito se nessun endpoint gestisce la richiesta.

Uso delle porte

Quando viene creata un'app Web con Visual Studio o dotnet new, viene creato un Properties/launchSettings.json file che specifica le porte a cui risponde l'app. Negli esempi di impostazione della porta che seguono, l'esecuzione dell'app da Visual Studio restituisce una finestra di dialogo Unable to connect to web server 'AppName'di errore. Visual Studio restituisce un errore perché prevede la porta specificata in Properties/launchSettings.json, ma l'app usa la porta specificata da app.Run("http://localhost:3000"). Eseguire i seguenti esempi di modifica della porta dalla riga di comando.

Le sezioni seguenti impostano la porta a cui risponde l'app.

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run("http://localhost:3000");

Nel codice precedente l'app risponde alla porta 3000.

Più porte

Nel codice seguente l'app risponde alla porta 3000 e 4000a .

var app = WebApplication.Create(args);

app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");

app.MapGet("/", () => "Hello World");

app.Run();

Impostare la porta dalla riga di comando

Il comando seguente rende l'app risponde alla porta 7777:

dotnet run --urls="https://localhost:7777"

Se l'endpoint Kestrel è configurato anche nel appsettings.json file, viene usato l'URL specificato dal appsettings.json file. Per altre informazioni, vedere Kestrel Configurazione dell'endpoint

Leggere la porta dall'ambiente

Il codice seguente legge la porta dall'ambiente:

var app = WebApplication.Create(args);

var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";

app.MapGet("/", () => "Hello World");

app.Run($"http://localhost:{port}");

Il modo migliore per impostare la porta dall'ambiente consiste nell'usare la ASPNETCORE_URLS variabile di ambiente, illustrata nella sezione seguente.

Impostare le porte tramite la variabile di ambiente ASPNETCORE_URLS

La ASPNETCORE_URLS variabile di ambiente è disponibile per impostare la porta:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS supporta più URL:

ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000

Ascoltare tutte le interfacce

Gli esempi seguenti illustrano l'ascolto su tutte le interfacce

http://*:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://*:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://+:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://+:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://0.0.0.0:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://0.0.0.0:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Ascoltare tutte le interfacce usando ASPNETCORE_URLS

Gli esempi precedenti possono usare ASPNETCORE_URLS

ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005

Ascoltare tutte le interfacce usando ASPNETCORE_HTTPS_PORTS

Gli esempi precedenti possono usare ASPNETCORE_HTTPS_PORTS e ASPNETCORE_HTTP_PORTS.

ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000

Per altre informazioni, vedere Configurare gli endpoint per il server Web ASP.NET Core Kestrel

Specificare HTTPS con il certificato di sviluppo

var app = WebApplication.Create(args);

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Per altre informazioni sul certificato di sviluppo, vedere Considerare attendibile il certificato di sviluppo HTTPS di ASP.NET Core in Windows e macOS.

Specificare HTTPS usando un certificato personalizzato

Le sezioni seguenti illustrano come specificare il certificato personalizzato usando il appsettings.json file e tramite la configurazione.

Specificare il certificato personalizzato con appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Kestrel": {
    "Certificates": {
      "Default": {
        "Path": "cert.pem",
        "KeyPath": "key.pem"
      }
    }
  }
}

Specificare il certificato personalizzato tramite la configurazione

var builder = WebApplication.CreateBuilder(args);

// Configure the cert and the key
builder.Configuration["Kestrel:Certificates:Default:Path"] = "cert.pem";
builder.Configuration["Kestrel:Certificates:Default:KeyPath"] = "key.pem";

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Usare le API del certificato

using System.Security.Cryptography.X509Certificates;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
    options.ConfigureHttpsDefaults(httpsOptions =>
    {
        var certPath = Path.Combine(builder.Environment.ContentRootPath, "cert.pem");
        var keyPath = Path.Combine(builder.Environment.ContentRootPath, "key.pem");

        httpsOptions.ServerCertificate = X509Certificate2.CreateFromPemFile(certPath, 
                                         keyPath);
    });
});

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Leggere l'ambiente

var app = WebApplication.Create(args);

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/oops");
}

app.MapGet("/", () => "Hello World");
app.MapGet("/oops", () => "Oops! An error happened.");

app.Run();

Per ulteriori informazioni sull'uso dell'ambiente, vedere ASP.NET Core ambienti di runtime

Configuration

Il codice seguente legge dal sistema di configurazione:

var app = WebApplication.Create(args);

var message = app.Configuration["HelloKey"] ?? "Config failed!";

app.MapGet("/", () => message);

app.Run();

Per altre informazioni, vedere Configurazione in ASP.NET Core

Logging

Il codice seguente scrive un messaggio all'avvio dell'applicazione di accesso:

var app = WebApplication.Create(args);

app.Logger.LogInformation("The app started");

app.MapGet("/", () => "Hello World");

app.Run();

Per altre informazioni, vedere Registrazione in .NET e ASP.NET Core

Accedere al contenitore di inserimento delle dipendenze

Il codice seguente illustra come ottenere servizi dal contenitore di inserimento delle dipendenze durante l'avvio dell'applicazione:


var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddScoped<SampleService>();

var app = builder.Build();

app.MapControllers();

using (var scope = app.Services.CreateScope())
{
    var sampleService = scope.ServiceProvider.GetRequiredService<SampleService>();
    sampleService.DoSomething();
}

app.Run();

Il codice seguente illustra come accedere alle chiavi dal contenitore di inserimento delle dipendenze usando l'attributo [FromKeyedServices] :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");

var app = builder.Build();

app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));

app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) => smallCache.Get("date"));

app.Run();

public interface ICache
{
    object Get(string key);
}
public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

Per altre informazioni sull'inserimento delle dipendenze, vedere Inserimento delle dipendenze in ASP.NET Core.

WebApplicationBuilder

Questa sezione contiene codice di esempio che usa WebApplicationBuilder.

Modificare la radice del contenuto, il nome dell'applicazione e l'ambiente

Il codice seguente imposta la radice del contenuto, il nome dell'applicazione e l'ambiente:

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    ApplicationName = typeof(Program).Assembly.FullName,
    ContentRootPath = Directory.GetCurrentDirectory(),
    EnvironmentName = Environments.Staging,
    WebRootPath = "customwwwroot"
});

Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");

var app = builder.Build();

WebApplication.CreateBuilder inizializza una nuova istanza della classe WebApplicationBuilder con valori predefiniti preconfigurati.

Per altre informazioni, vedere ASP.NET Panoramica dei concetti fondamentali di base

Modificare la radice del contenuto, il nome dell'app e l'ambiente usando variabili di ambiente o riga di comando

La tabella seguente illustra la variabile di ambiente e l'argomento della riga di comando usati per modificare la radice del contenuto, il nome dell'app e l'ambiente:

feature Variabile di ambiente Argomento della riga di comando
Nome applicazione ASPNETCORE_APPLICATIONNAME --applicationName
Nome dell'ambiente ASPNETCORE_ENVIRONMENT --environment
Radice del contenuto ASPNETCORE_CONTENTROOT --contentRoot

Aggiungere provider di configurazione

L'esempio seguente aggiunge il provider di configurazione INI:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddIniFile("appsettings.ini");

var app = builder.Build();

Per informazioni dettagliate, vedere Provider di configurazione file in Configurazione in ASP.NET Core.

Leggere la configurazione

Per impostazione predefinita, la WebApplicationBuilder configurazione legge da più origini, tra cui:

  • appSettings.json e appSettings.{environment}.json
  • Variabili di ambiente
  • Riga di comando

Per un elenco completo delle origini di configurazione, vedere Configurazione predefinita in Configurazione in ASP.NET Core.

Il codice seguente legge HelloKey dalla configurazione e visualizza il valore nell'endpoint / . Se il valore di configurazione è Null, "Hello" viene assegnato a message:

var builder = WebApplication.CreateBuilder(args);

var message = builder.Configuration["HelloKey"] ?? "Hello";

var app = builder.Build();

app.MapGet("/", () => message);

app.Run();

Leggere l'ambiente

var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsDevelopment())
{
    Console.WriteLine($"Running in development.");
}

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Aggiungere provider di registrazione

var builder = WebApplication.CreateBuilder(args);

// Configure JSON logging to the console.
builder.Logging.AddJsonConsole();

var app = builder.Build();

app.MapGet("/", () => "Hello JSON console!");

app.Run();

Aggiungere servizi

var builder = WebApplication.CreateBuilder(args);

// Add the memory cache services.
builder.Services.AddMemoryCache();

// Add a custom scoped service.
builder.Services.AddScoped<ITodoRepository, TodoRepository>();
var app = builder.Build();

Personalizzare IHostBuilder

È possibile accedere ai metodi di estensione esistenti in IHostBuilder usando la proprietà Host:

var builder = WebApplication.CreateBuilder(args);

// Wait 30 seconds for graceful shutdown.
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Personalizzare IWebHostBuilder

È possibile accedere ai metodi IWebHostBuilder di estensione in usando la proprietà WebApplicationBuilder.WebHost .

var builder = WebApplication.CreateBuilder(args);

// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();

var app = builder.Build();

app.MapGet("/", () => "Hello HTTP.sys");

app.Run();

Modificare la radice Web

Per impostazione predefinita, la radice Web è relativa alla radice del contenuto nella wwwroot cartella. La radice Web è la posizione in cui il middleware dei file statici cerca i file statici. La radice Web può essere modificata con WebHostOptions, la riga di comando o con il UseWebRoot metodo :

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    // Look for static files in webroot
    WebRootPath = "webroot"
});

var app = builder.Build();

app.Run();

Contenitore di inserimento delle dipendenze personalizzato

L'esempio seguente usa Autofac:

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));

var app = builder.Build();

Aggiungere middleware

Qualsiasi middleware core ASP.NET esistente può essere configurato in WebApplication:

var app = WebApplication.Create(args);

// Setup the file server to serve static files.
app.UseFileServer();

app.MapGet("/", () => "Hello World!");

app.Run();

Per altre informazioni, vedere middleware ASP.NET Core

Pagina delle eccezioni per gli sviluppatori

WebApplication.CreateBuilder inizializza una nuova istanza della WebApplicationBuilder classe con impostazioni predefinite preconfigurate. La pagina delle eccezioni per sviluppatori è abilitata nelle impostazioni predefinite preconfigurate. Quando il codice seguente viene eseguito nell'ambiente di sviluppo, spostarsi per eseguire / il rendering di una pagina descrittiva che mostra l'eccezione.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () =>
{
    throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});

app.Run();

Middleware di ASP.NET Core

La tabella seguente elenca alcuni dei middleware usati di frequente con API minime.

Middleware Description API
Authentication Offre il supporto dell'autenticazione. UseAuthentication
Authorization Fornisce il supporto per l'autorizzazione. UseAuthorization
CORS Configura la condivisione di risorse tra le origini (CORS). UseCors
Gestore eccezioni Gestisce globalmente le eccezioni generate dalla pipeline middleware. UseExceptionHandler
Intestazioni inoltrate Inoltra le intestazioni proxy nella richiesta corrente. UseForwardedHeaders
Reindirizzamento HTTPS Reindirizza tutte le richieste HTTP a HTTPS. UseHttpsRedirection
Protocollo HTTP Strict Transport Security (HSTS) Middleware di ottimizzazione della sicurezza che aggiunge un'intestazione della risposta speciale. UseHsts
Registrazione richieste Fornisce supporto per la registrazione di richieste e risposte HTTP. UseHttpLogging
Timeout delle richieste Fornisce il supporto per la configurazione dei timeout delle richieste, impostazione predefinita globale e per endpoint. UseRequestTimeouts
Registrazione delle richieste W3C Fornisce il supporto per la registrazione di richieste e risposte HTTP nel formato W3C. UseW3CLogging
Memorizzazione nella cache delle risposte Offre il supporto per la memorizzazione delle risposte nella cache. UseResponseCaching
Compressione della risposta Offre il supporto per la compressione delle risposte. UseResponseCompression
Session Offre il supporto per la gestione delle sessioni utente. UseSession
File statici Offre il supporto per la gestione di file statici e l'esplorazione directory. UseStaticFiles, UseFileServer
WebSockets Abilita il protocollo WebSocket. UseWebSockets

Le sezioni seguenti illustrano la gestione delle richieste: routing, associazione di parametri e risposte.

Routing

Un oggetto configurato supporta e dove è un metodo HTTP con maiuscole e minuscole camel come WebApplication, Map{Verb}MapMethods, o {Verb}:GetPostPutDelete

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.

Espressione 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 dell'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 QueryPrivateTodosfactory 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

Associazione di parametri

L'associazione di parametri è il processo di conversione dei dati delle richieste in parametri fortemente tipizzato espressi dai gestori di route. Un'origine di associazione determina la posizione da cui sono associati i parametri. Le origini di associazione possono essere esplicite o dedotte in base al metodo HTTP e al tipo di parametro.

Origini di associazione supportate:

  • Valori di route
  • Stringa di query
  • Header
  • Corpo (come JSON)
  • Valori modulo
  • Servizi forniti dall'inserimento delle dipendenze
  • Custom

Il gestore di route GET seguente usa alcune di queste origini di associazione di parametri:

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();

app.MapGet("/{id}", (int id,
                     int page,
                     [FromHeader(Name = "X-CUSTOM-HEADER")] string customHeader,
                     Service service) => { });

class Service { }

Funzionalità di associazione dei parametri chiave

  • Associazione esplicita: usare attributi come [FromRoute], [FromQuery][FromHeader], [FromBody], [FromForm], e [FromServices] per specificare in modo esplicito le origini di associazione.
  • Associazione di moduli: associare i valori dei moduli usando l'attributo [FromForm] , incluso il supporto per IFormFile e IFormFileCollection per i caricamenti di file.
  • Tipi complessi: Legare alle raccolte e ai tipi complessi nei moduli, nelle stringhe di query e nelle intestazioni.
  • Associazione personalizzata: implementare la logica di associazione personalizzata usando TryParse, BindAsynco l'interfaccia IBindableFromHttpContext<T> .
  • Parametri facoltativi: supportano i tipi nullable e i valori predefiniti per i parametri facoltativi.
  • Inserimento delle dipendenze: i parametri vengono automaticamente associati ai servizi registrati nel contenitore DI.
  • Tipi speciali: associazione automatica per HttpContext, HttpRequest, HttpResponse, CancellationTokenClaimsPrincipal, Stream, e PipeReader.

Ulteriori informazioni: Per informazioni dettagliate sull'associazione di parametri, inclusi scenari avanzati, convalida, precedenza di associazione e risoluzione dei problemi, vedere Associazione di parametri nelle applicazioni API minime.

Deserializzazione json+PipeReader nelle API minime

A partire da .NET 10, le seguenti aree funzionali di ASP.NET Core usano gli overload di JsonSerializer.DeserializeAsync basati su PipeReader anziché su Stream.

  • API minime (associazione di parametri, corpo della richiesta di lettura)
  • MVC (formattatori di input, modello)
  • Metodi HttpRequestJsonExtensions di estensione per leggere il corpo della richiesta come JSON.

Per la maggior parte delle applicazioni, una transizione da Stream a PipeReader offre prestazioni migliori senza richiedere modifiche al codice dell'applicazione. Tuttavia, se l'applicazione dispone di un convertitore personalizzato, il convertitore potrebbe non essere gestito Utf8JsonReader.HasValueSequence correttamente. In caso contrario, il risultato potrebbe essere un errore, ArgumentOutOfRangeException ad esempio o dati mancanti durante la deserializzazione. Sono disponibili le opzioni seguenti per il corretto funzionamento del convertitore senza errori correlati a PipeReader.

Opzione 1: Soluzione temporanea

La soluzione alternativa rapida consiste nel tornare all'uso di Stream senza il supporto di PipeReader. Per implementare questa opzione, impostare l'opzione AppContext "Microsoft.AspNetCore.UseStreamBasedJsonParsing" su "true". È consigliabile eseguire questa operazione solo come soluzione alternativa temporanea e aggiornare il convertitore per supportare HasValueSequence il più presto possibile. L'opzione potrebbe essere rimossa in .NET 11. L'unico scopo era quello di offrire agli sviluppatori il tempo necessario per aggiornare i convertitori.

Opzione 2: Una correzione rapida per le implementazioni di JsonConverter

Per questa correzione, si alloca un array da ReadOnlySequence. Questo esempio mostra l'aspetto del codice:

public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
    // previous code
}

Opzione 3: Una correzione più complessa ma più efficiente

Questa correzione comporta la configurazione di un percorso di codice separato per la ReadOnlySequence gestione:

public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    if (reader.HasValueSequence)
    {
        reader.ValueSequence;
        // ReadOnlySequence optimized path
    }
    else
    {
        reader.ValueSpan;
        // ReadOnlySpan optimized path
    }
}

Per ulteriori informazioni, vedere

Supporto della convalida nelle API minime

L'abilitazione della convalida consente al runtime di ASP.NET Core di eseguire le convalide definite in:

  • Query
  • Header
  • Testo della richiesta

Le validazioni vengono definite usando attributi nello spazio dei nomi DataAnnotations.

Quando un parametro per un endpoint API minimo è una classe o un tipo di record, gli attributi di convalida vengono applicati automaticamente. Per esempio:

public record Product(
    [Required] string Name,
    [Range(1, 1000)] int Quantity);

Gli sviluppatori personalizzano il comportamento del sistema di convalida in base a:

Se la convalida ha esito negativo, il runtime restituisce una risposta 400 - Richiesta non valida con i dettagli degli errori di convalida.

Abilitare il supporto di convalida predefinito per le API minime

Abilitare il supporto di convalida predefinito per le API minime chiamando il AddValidation metodo di estensione per registrare i servizi necessari nel contenitore di servizi per l'applicazione:

builder.Services.AddValidation();

L'implementazione individua automaticamente i tipi definiti nei gestori API minimi o come tipi di base definiti nei gestori API minimi. Un filtro per gli endpoint esegue la convalida su questi tipi ed è aggiunto per ciascun endpoint.

La convalida può essere disabilitata per endpoint specifici usando il DisableValidation metodo di estensione, come nell'esempio seguente:

app.MapPost("/products",
    ([EvenNumber(ErrorMessage = "Product ID must be even")] int productId, [Required] string name)
        => TypedResults.Ok(productId))
    .DisableValidation();

Personalizzare le risposte agli errori di convalida usando IProblemDetailsService

Personalizzare le risposte di errore dalla logica di convalida minima dell'API con un'implementazione IProblemDetailsService . Registrare questo servizio nella raccolta di servizi dell'applicazione per abilitare risposte di errore più coerenti e specifiche dell'utente. Il supporto per la convalida minima dell'API è stato introdotto in ASP.NET Core in .NET 10.

Per implementare risposte di errore di convalida personalizzate:

  • Implementare o usare l'implementazione IProblemDetailsService predefinita
  • Registrare il servizio nel contenitore di inserimento delle dipendenze
  • Il sistema di convalida usa automaticamente il servizio registrato per formattare le risposte agli errori di convalida

Per altre informazioni sulla personalizzazione delle risposte agli errori di convalida con IProblemDetailsService, vedere Creare risposte nelle applicazioni API minime.

Responses

I gestori di route supportano i tipi di valori restituiti seguenti:

  1. IResult based : include Task<IResult> e ValueTask<IResult>
  2. string - Questo include Task<string> e ValueTask<string>
  3. T (Qualsiasi altro tipo) - Include Task<T> e ValueTask<T>
Valore restituito Behavior Content-Type
IResult Il framework chiama IResult.ExecuteAsync Deciso dall'implementazione IResult
string Il framework scrive la stringa direttamente nella risposta text/plain
T (Qualsiasi altro tipo) Il framework JSON serializza la risposta application/json

Per una guida più approfondita ai valori restituiti del gestore di route, vedere Creare risposte nelle applicazioni API minime

Valori restituiti di esempio

valori restituiti di stringa

app.MapGet("/hello", () => "Hello World");

Valori restituiti JSON

app.MapGet("/hello", () => new { Message = "Hello World" });

Restituisci TypedResults

Il codice seguente restituisce un oggetto TypedResults:

app.MapGet("/hello", () => TypedResults.Ok(new Message() {  Text = "Hello World!" }));

La restituzione di è preferibile TypedResultsResultsper restituire . Per altre informazioni, vedere TypedResults vs Results.

Valori restituiti IResult

app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));

Nell'esempio seguente vengono usati i tipi di risultati predefiniti per personalizzare la risposta:

app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
         await db.Todos.FindAsync(id) 
         is Todo todo
         ? Results.Ok(todo) 
         : Results.NotFound())
   .Produces<Todo>(StatusCodes.Status200OK)
   .Produces(StatusCodes.Status404NotFound);

JSON

app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));

Codice di stato personalizzato

app.MapGet("/405", () => Results.StatusCode(405));

Text

app.MapGet("/text", () => Results.Text("This is some text"));

Stream

var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () => 
{
    var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
    // Proxy the response as JSON
    return Results.Stream(stream, "application/json");
});

Per altri esempi, vedere Creare risposte nelle applicazioni API minime.

Redirect

app.MapGet("/old-path", () => Results.Redirect("/new-path"));

File

app.MapGet("/download", () => Results.File("myfile.text"));

Risultati predefiniti

Gli helper di risultati comuni esistono nelle Results classi statiche e TypedResults . La restituzione di è preferibile TypedResultsResultsper restituire . Per altre informazioni, vedere TypedResults vs Results.

Modifica delle intestazioni

Usare l'oggetto HttpResponse per modificare le intestazioni di risposta:

app.MapGet("/", (HttpContext context) => {
    // Set a custom header
    context.Response.Headers["X-Custom-Header"] = "CustomValue";

    // Set a known header
    context.Response.Headers.CacheControl = $"public,max-age=3600";

    return "Hello World";
});

Personalizzazione dei risultati

Le applicazioni possono controllare le risposte implementando un tipo personalizzato IResult . Il codice seguente è un esempio di tipo di risultato HTML:

using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
    public static IResult Html(this IResultExtensions resultExtensions, string html)
    {
        ArgumentNullException.ThrowIfNull(resultExtensions);

        return new HtmlResult(html);
    }
}

class HtmlResult : IResult
{
    private readonly string _html;

    public HtmlResult(string html)
    {
        _html = html;
    }

    public Task ExecuteAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Html;
        httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
        return httpContext.Response.WriteAsync(_html);
    }
}

È consigliabile aggiungere un metodo di estensione per Microsoft.AspNetCore.Http.IResultExtensions rendere questi risultati personalizzati più individuabili.

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

app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
    <head><title>miniHTML</title></head>
    <body>
        <h1>Hello World</h1>
        <p>The time on the server is {DateTime.Now:O}</p>
    </body>
</html>"));

app.Run();

Risultati tipizzato

L'interfaccia IResult può rappresentare i valori restituiti dalle API minime che non usano il supporto implicito per la serializzazione JSON dell'oggetto restituito alla risposta HTTP. La classe static Results viene usata per creare oggetti variabili IResult che rappresentano tipi diversi di risposte. Ad esempio, impostare il codice di stato della risposta o reindirizzare a un altro URL.

I tipi che implementano IResult sono pubblici, consentendo le asserzioni di tipo durante il test. Per esempio:

[TestClass()]
public class WeatherApiTests
{
    [TestMethod()]
    public void MapWeatherApiTest()
    {
        var result = WeatherApi.GetAllWeathers();
        Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
    }      
}

È possibile esaminare i tipi restituiti dei metodi corrispondenti nella classe Static TypedResults per trovare il tipo pubblico IResult corretto a cui eseguire il cast.

Per altri esempi, vedere Creare risposte nelle applicazioni API minime.

Filters

Per altre informazioni, vedere Filtri nelle app per le API minime.

Authorization

Le route possono essere protette usando i criteri di autorizzazione. Questi valori possono essere dichiarati tramite l'attributo [Authorize] o usando il RequireAuthorization metodo :

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/auth", [Authorize] () => "This endpoint requires authorization.");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

Il codice precedente può essere scritto con RequireAuthorization:

app.MapGet("/auth", () => "This endpoint requires authorization")
   .RequireAuthorization();

L'esempio seguente usa l'autorizzazione basata su criteri:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/admin", [Authorize("AdminsOnly")] () => 
                             "The /admin endpoint is for admins only.");

app.MapGet("/admin2", () => "The /admin2 endpoint is for admins only.")
   .RequireAuthorization("AdminsOnly");

app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

Consentire agli utenti non autenticati di accedere a un endpoint

[AllowAnonymous] consente agli utenti non autenticati di accedere agli endpoint:

app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");


app.MapGet("/login2", () => "This endpoint also for all roles.")
   .AllowAnonymous();

CORS

Le route possono essere abilitate per CORS usando i criteri CORS. È possibile dichiarare CORS tramite l'attributo [EnableCors] o usando il RequireCors metodo . Gli esempi seguenti abilitano CORS:

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/",() => "Hello CORS!");

app.Run();
using Microsoft.AspNetCore.Cors;

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/cors", [EnableCors(MyAllowSpecificOrigins)] () => 
                           "This endpoint allows cross origin requests!");
app.MapGet("/cors2", () => "This endpoint allows cross origin requests!")
                     .RequireCors(MyAllowSpecificOrigins);

app.Run();

Per altre informazioni, vedere Abilitare le richieste tra le origini (CORS) in ASP.NET Core

ValidateScopes e ValidateOnBuild

ValidateScopes e ValidateOnBuild sono abilitati per impostazione predefinita nell'ambiente di sviluppo , ma disabilitati in altri ambienti.

Quando ValidateOnBuild è true, il contenitore di inserimento delle dipendenze convalida la configurazione del servizio in fase di compilazione. Se la configurazione del servizio non è valida, la compilazione non riesce all'avvio dell'app, anziché in fase di esecuzione quando viene richiesto il servizio.

Quando ValidateScopes è true, il contenitore di inserimento delle dipendenze verifica che un servizio con ambito non venga risolto dall'ambito radice. La risoluzione di un servizio con ambito dall'ambito radice può causare una perdita di memoria perché il servizio viene mantenuto in memoria più lungo dell'ambito della richiesta.

ValidateScopes e ValidateOnBuild sono false per impostazione predefinita nelle modalità non di sviluppo per motivi di prestazioni.

Il codice seguente mostra ValidateScopes che è abilitato per impostazione predefinita in modalità di sviluppo, ma disabilitato in modalità di rilascio:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<MyScopedService>();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    Console.WriteLine("Development environment");
}
else
{
    Console.WriteLine("Release environment");
}

app.MapGet("/", context =>
{
    // Intentionally getting service provider from app, not from the request
    // This causes an exception from attempting to resolve a scoped service
    // outside of a scope.
    // Throws System.InvalidOperationException:
    // 'Cannot resolve scoped service 'MyScopedService' from root provider.'
    var service = app.Services.GetRequiredService<MyScopedService>();
    return context.Response.WriteAsync("Service resolved");
});

app.Run();

public class MyScopedService { }

Il codice seguente mostra ValidateOnBuild che è abilitato per impostazione predefinita in modalità di sviluppo, ma disabilitato in modalità di rilascio:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<MyScopedService>();
builder.Services.AddScoped<AnotherService>();

// System.AggregateException: 'Some services are not able to be constructed (Error
// while validating the service descriptor 'ServiceType: AnotherService Lifetime:
// Scoped ImplementationType: AnotherService': Unable to resolve service for type
// 'BrokenService' while attempting to activate 'AnotherService'.)'
var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    Console.WriteLine("Development environment");
}
else
{
    Console.WriteLine("Release environment");
}

app.MapGet("/", context =>
{
    var service = context.RequestServices.GetRequiredService<MyScopedService>();
    return context.Response.WriteAsync("Service resolved correctly!");
});

app.Run();

public class MyScopedService { }

public class AnotherService
{
    public AnotherService(BrokenService brokenService) { }
}

public class BrokenService { }

Il codice seguente disabilita ValidateScopes e ValidateOnBuild in Development:

var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsDevelopment())
{
    Console.WriteLine("Development environment");
    // Doesn't detect the validation problems because ValidateScopes is false.
    builder.Host.UseDefaultServiceProvider(options =>
    {
        options.ValidateScopes = false;
        options.ValidateOnBuild = false;
    });
}

Vedere anche

Questo documento:

Le API minime sono costituite da:

WebApplication

Il codice seguente viene generato da un modello ASP.NET Core:

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

app.MapGet("/", () => "Hello World!");

app.Run();

Il codice precedente può essere creato tramite dotnet new web la riga di comando o selezionando il modello Web vuoto in Visual Studio.

Il codice seguente crea un oggetto WebApplication (app) senza creare in modo esplicito un oggetto WebApplicationBuilder:

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run();

WebApplication.Create inizializza una nuova istanza della WebApplication classe con impostazioni predefinite preconfigurate.

WebApplication aggiunge automaticamente il middleware seguente nelle applicazioni API minime a seconda di determinate condizioni:

  • UseDeveloperExceptionPage viene aggiunto per primo quando è HostingEnvironment"Development".
  • UseRouting viene aggiunto secondo se il codice utente non ha già chiamato UseRouting e se sono stati configurati endpoint, ad esempio app.MapGet.
  • UseEndpoints viene aggiunto alla fine della pipeline middleware se sono configurati endpoint.
  • UseAuthentication viene aggiunto immediatamente dopo UseRouting se il codice utente non ha già chiamato UseAuthentication e se IAuthenticationSchemeProvider è possibile rilevare nel provider di servizi. IAuthenticationSchemeProvider viene aggiunto per impostazione predefinita quando si usano AddAuthenticationi servizi e viene rilevato tramite IServiceProviderIsService.
  • UseAuthorization viene aggiunto successivamente se il codice utente non ha già chiamato UseAuthorization e se IAuthorizationHandlerProvider è possibile rilevare nel provider di servizi. IAuthorizationHandlerProvider viene aggiunto per impostazione predefinita quando si usano AddAuthorizationi servizi e viene rilevato tramite IServiceProviderIsService.
  • Il middleware e gli endpoint configurati dall'utente vengono aggiunti tra UseRouting e UseEndpoints.

Il codice seguente è effettivamente ciò che il middleware automatico aggiunto all'app produce:

if (isDevelopment)
{
    app.UseDeveloperExceptionPage();
}

app.UseRouting();

if (isAuthenticationConfigured)
{
    app.UseAuthentication();
}

if (isAuthorizationConfigured)
{
    app.UseAuthorization();
}

// user middleware/endpoints
app.CustomMiddleware(...);
app.MapGet("/", () => "hello world");
// end user middleware/endpoints

app.UseEndpoints(e => {});

In alcuni casi, la configurazione del middleware predefinita non è corretta per l'app e richiede modifiche. Ad esempio, UseCors deve essere chiamato prima UseAuthentication di e UseAuthorization. L'app deve chiamare UseAuthentication e UseAuthorization se UseCors viene chiamato:

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

Se il middleware deve essere eseguito prima che si verifichi la corrispondenza della route, UseRouting deve essere chiamato e il middleware deve essere posizionato prima della chiamata a UseRouting. UseEndpoints in questo caso non è obbligatorio perché viene aggiunto automaticamente come descritto in precedenza:

app.Use((context, next) =>
{
    return next(context);
});

app.UseRouting();

// other middleware and endpoints

Quando si aggiunge un middleware del terminale:

  • Il middleware deve essere aggiunto dopo UseEndpoints.
  • L'app deve chiamare UseRouting e UseEndpoints in modo che il middleware del terminale possa essere posizionato nella posizione corretta.
app.UseRouting();

app.MapGet("/", () => "hello world");

app.UseEndpoints(e => {});

app.Run(context =>
{
    context.Response.StatusCode = 404;
    return Task.CompletedTask;
});

Il middleware del terminale è middleware che viene eseguito se nessun endpoint gestisce la richiesta.

Uso delle porte

Quando viene creata un'app Web con Visual Studio o dotnet new, viene creato un Properties/launchSettings.json file che specifica le porte a cui risponde l'app. Negli esempi di impostazione della porta che seguono, l'esecuzione dell'app da Visual Studio restituisce una finestra di dialogo Unable to connect to web server 'AppName'di errore. Visual Studio restituisce un errore perché prevede la porta specificata in Properties/launchSettings.json, ma l'app usa la porta specificata da app.Run("http://localhost:3000"). Eseguire i seguenti esempi di modifica della porta dalla riga di comando.

Le sezioni seguenti impostano la porta a cui risponde l'app.

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run("http://localhost:3000");

Nel codice precedente l'app risponde alla porta 3000.

Più porte

Nel codice seguente l'app risponde alla porta 3000 e 4000a .

var app = WebApplication.Create(args);

app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");

app.MapGet("/", () => "Hello World");

app.Run();

Impostare la porta dalla riga di comando

Il comando seguente rende l'app risponde alla porta 7777:

dotnet run --urls="https://localhost:7777"

Se l'endpoint Kestrel è configurato anche nel appsettings.json file, viene usato l'URL specificato dal appsettings.json file. Per altre informazioni, vedere Kestrel Configurazione dell'endpoint

Leggere la porta dall'ambiente

Il codice seguente legge la porta dall'ambiente:

var app = WebApplication.Create(args);

var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";

app.MapGet("/", () => "Hello World");

app.Run($"http://localhost:{port}");

Il modo migliore per impostare la porta dall'ambiente consiste nell'usare la ASPNETCORE_URLS variabile di ambiente, illustrata nella sezione seguente.

Impostare le porte tramite la variabile di ambiente ASPNETCORE_URLS

La ASPNETCORE_URLS variabile di ambiente è disponibile per impostare la porta:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS supporta più URL:

ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000

Ascoltare tutte le interfacce

Gli esempi seguenti illustrano l'ascolto su tutte le interfacce

http://*:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://*:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://+:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://+:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://0.0.0.0:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://0.0.0.0:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Ascoltare tutte le interfacce usando ASPNETCORE_URLS

Gli esempi precedenti possono usare ASPNETCORE_URLS

ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005

Ascoltare tutte le interfacce usando ASPNETCORE_HTTPS_PORTS

Gli esempi precedenti possono usare ASPNETCORE_HTTPS_PORTS e ASPNETCORE_HTTP_PORTS.

ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000

Per altre informazioni, vedere Configurare gli endpoint per il server Web ASP.NET Core Kestrel

Specificare HTTPS con il certificato di sviluppo

var app = WebApplication.Create(args);

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Per altre informazioni sul certificato di sviluppo, vedere Considerare attendibile il certificato di sviluppo HTTPS di ASP.NET Core in Windows e macOS.

Specificare HTTPS usando un certificato personalizzato

Le sezioni seguenti illustrano come specificare il certificato personalizzato usando il appsettings.json file e tramite la configurazione.

Specificare il certificato personalizzato con appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Kestrel": {
    "Certificates": {
      "Default": {
        "Path": "cert.pem",
        "KeyPath": "key.pem"
      }
    }
  }
}

Specificare il certificato personalizzato tramite la configurazione

var builder = WebApplication.CreateBuilder(args);

// Configure the cert and the key
builder.Configuration["Kestrel:Certificates:Default:Path"] = "cert.pem";
builder.Configuration["Kestrel:Certificates:Default:KeyPath"] = "key.pem";

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Usare le API del certificato

using System.Security.Cryptography.X509Certificates;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
    options.ConfigureHttpsDefaults(httpsOptions =>
    {
        var certPath = Path.Combine(builder.Environment.ContentRootPath, "cert.pem");
        var keyPath = Path.Combine(builder.Environment.ContentRootPath, "key.pem");

        httpsOptions.ServerCertificate = X509Certificate2.CreateFromPemFile(certPath, 
                                         keyPath);
    });
});

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Leggere l'ambiente

var app = WebApplication.Create(args);

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/oops");
}

app.MapGet("/", () => "Hello World");
app.MapGet("/oops", () => "Oops! An error happened.");

app.Run();

Per ulteriori informazioni sull'uso dell'ambiente, vedere ASP.NET Core ambienti di runtime

Configuration

Il codice seguente legge dal sistema di configurazione:

var app = WebApplication.Create(args);

var message = app.Configuration["HelloKey"] ?? "Config failed!";

app.MapGet("/", () => message);

app.Run();

Per altre informazioni, vedere Configurazione in ASP.NET Core

Logging

Il codice seguente scrive un messaggio all'avvio dell'applicazione di accesso:

var app = WebApplication.Create(args);

app.Logger.LogInformation("The app started");

app.MapGet("/", () => "Hello World");

app.Run();

Per altre informazioni, vedere Registrazione in .NET e ASP.NET Core

Accedere al contenitore di inserimento delle dipendenze

Il codice seguente illustra come ottenere servizi dal contenitore di inserimento delle dipendenze durante l'avvio dell'applicazione:


var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddScoped<SampleService>();

var app = builder.Build();

app.MapControllers();

using (var scope = app.Services.CreateScope())
{
    var sampleService = scope.ServiceProvider.GetRequiredService<SampleService>();
    sampleService.DoSomething();
}

app.Run();

Il codice seguente illustra come accedere alle chiavi dal contenitore di inserimento delle dipendenze usando l'attributo [FromKeyedServices] :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");

var app = builder.Build();

app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));

app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) => smallCache.Get("date"));

app.Run();

public interface ICache
{
    object Get(string key);
}
public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

Per altre informazioni sull'inserimento delle dipendenze, vedere Inserimento delle dipendenze in ASP.NET Core.

WebApplicationBuilder

Questa sezione contiene codice di esempio che usa WebApplicationBuilder.

Modificare la radice del contenuto, il nome dell'applicazione e l'ambiente

Il codice seguente imposta la radice del contenuto, il nome dell'applicazione e l'ambiente:

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    ApplicationName = typeof(Program).Assembly.FullName,
    ContentRootPath = Directory.GetCurrentDirectory(),
    EnvironmentName = Environments.Staging,
    WebRootPath = "customwwwroot"
});

Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");

var app = builder.Build();

WebApplication.CreateBuilder inizializza una nuova istanza della classe WebApplicationBuilder con valori predefiniti preconfigurati.

Per altre informazioni, vedere ASP.NET Panoramica dei concetti fondamentali di base

Modificare la radice del contenuto, il nome dell'app e l'ambiente usando variabili di ambiente o riga di comando

La tabella seguente illustra la variabile di ambiente e l'argomento della riga di comando usati per modificare la radice del contenuto, il nome dell'app e l'ambiente:

feature Variabile di ambiente Argomento della riga di comando
Nome applicazione ASPNETCORE_APPLICATIONNAME --applicationName
Nome dell'ambiente ASPNETCORE_ENVIRONMENT --environment
Radice del contenuto ASPNETCORE_CONTENTROOT --contentRoot

Aggiungere provider di configurazione

L'esempio seguente aggiunge il provider di configurazione INI:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddIniFile("appsettings.ini");

var app = builder.Build();

Per informazioni dettagliate, vedere Provider di configurazione file in Configurazione in ASP.NET Core.

Leggere la configurazione

Per impostazione predefinita, la WebApplicationBuilder configurazione legge da più origini, tra cui:

  • appSettings.json e appSettings.{environment}.json
  • Variabili di ambiente
  • Riga di comando

Per un elenco completo delle origini di configurazione, vedere Configurazione predefinita in Configurazione in ASP.NET Core.

Il codice seguente legge HelloKey dalla configurazione e visualizza il valore nell'endpoint / . Se il valore di configurazione è Null, "Hello" viene assegnato a message:

var builder = WebApplication.CreateBuilder(args);

var message = builder.Configuration["HelloKey"] ?? "Hello";

var app = builder.Build();

app.MapGet("/", () => message);

app.Run();

Leggere l'ambiente

var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsDevelopment())
{
    Console.WriteLine($"Running in development.");
}

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Aggiungere provider di registrazione

var builder = WebApplication.CreateBuilder(args);

// Configure JSON logging to the console.
builder.Logging.AddJsonConsole();

var app = builder.Build();

app.MapGet("/", () => "Hello JSON console!");

app.Run();

Aggiungere servizi

var builder = WebApplication.CreateBuilder(args);

// Add the memory cache services.
builder.Services.AddMemoryCache();

// Add a custom scoped service.
builder.Services.AddScoped<ITodoRepository, TodoRepository>();
var app = builder.Build();

Personalizzare IHostBuilder

È possibile accedere ai metodi di estensione esistenti in IHostBuilder usando la proprietà Host:

var builder = WebApplication.CreateBuilder(args);

// Wait 30 seconds for graceful shutdown.
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Personalizzare IWebHostBuilder

È possibile accedere ai metodi IWebHostBuilder di estensione in usando la proprietà WebApplicationBuilder.WebHost .

var builder = WebApplication.CreateBuilder(args);

// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();

var app = builder.Build();

app.MapGet("/", () => "Hello HTTP.sys");

app.Run();

Modificare la radice Web

Per impostazione predefinita, la radice Web è relativa alla radice del contenuto nella wwwroot cartella. La radice Web è la posizione in cui il middleware dei file statici cerca i file statici. La radice Web può essere modificata con WebHostOptions, la riga di comando o con il UseWebRoot metodo :

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    // Look for static files in webroot
    WebRootPath = "webroot"
});

var app = builder.Build();

app.Run();

Contenitore di inserimento delle dipendenze personalizzato

L'esempio seguente usa Autofac:

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));

var app = builder.Build();

Aggiungere middleware

Qualsiasi middleware core ASP.NET esistente può essere configurato in WebApplication:

var app = WebApplication.Create(args);

// Setup the file server to serve static files.
app.UseFileServer();

app.MapGet("/", () => "Hello World!");

app.Run();

Per altre informazioni, vedere middleware ASP.NET Core

Pagina delle eccezioni per gli sviluppatori

WebApplication.CreateBuilder inizializza una nuova istanza della WebApplicationBuilder classe con impostazioni predefinite preconfigurate. La pagina delle eccezioni per sviluppatori è abilitata nelle impostazioni predefinite preconfigurate. Quando il codice seguente viene eseguito nell'ambiente di sviluppo, spostarsi per eseguire / il rendering di una pagina descrittiva che mostra l'eccezione.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () =>
{
    throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});

app.Run();

Middleware di ASP.NET Core

La tabella seguente elenca alcuni dei middleware usati di frequente con API minime.

Middleware Description API
Authentication Offre il supporto dell'autenticazione. UseAuthentication
Authorization Fornisce il supporto per l'autorizzazione. UseAuthorization
CORS Configura la condivisione di risorse tra le origini (CORS). UseCors
Gestore eccezioni Gestisce globalmente le eccezioni generate dalla pipeline middleware. UseExceptionHandler
Intestazioni inoltrate Inoltra le intestazioni proxy nella richiesta corrente. UseForwardedHeaders
Reindirizzamento HTTPS Reindirizza tutte le richieste HTTP a HTTPS. UseHttpsRedirection
Protocollo HTTP Strict Transport Security (HSTS) Middleware di ottimizzazione della sicurezza che aggiunge un'intestazione della risposta speciale. UseHsts
Registrazione richieste Fornisce supporto per la registrazione di richieste e risposte HTTP. UseHttpLogging
Timeout delle richieste Fornisce il supporto per la configurazione dei timeout delle richieste, impostazione predefinita globale e per endpoint. UseRequestTimeouts
Registrazione delle richieste W3C Fornisce il supporto per la registrazione di richieste e risposte HTTP nel formato W3C. UseW3CLogging
Memorizzazione nella cache delle risposte Offre il supporto per la memorizzazione delle risposte nella cache. UseResponseCaching
Compressione della risposta Offre il supporto per la compressione delle risposte. UseResponseCompression
Session Offre il supporto per la gestione delle sessioni utente. UseSession
File statici Offre il supporto per la gestione di file statici e l'esplorazione directory. UseStaticFiles, UseFileServer
WebSockets Abilita il protocollo WebSocket. UseWebSockets

Le sezioni seguenti illustrano la gestione delle richieste: routing, associazione di parametri e risposte.

Routing

Un oggetto configurato supporta e dove è un metodo HTTP con maiuscole e minuscole camel, ad WebApplicationesempio , Map{Verb}MapMethods o {Verb}:GetPostPutDelete

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.

Espressione 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 dell'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 QueryPrivateTodosfactory 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

Associazione di parametri

L'associazione di parametri è il processo di conversione dei dati delle richieste in parametri fortemente tipizzato espressi dai gestori di route. Un'origine di associazione determina la posizione da cui sono associati i parametri. Le origini di associazione possono essere esplicite o dedotte in base al metodo HTTP e al tipo di parametro.

Origini di associazione supportate:

  • Valori di route
  • Stringa di query
  • Header
  • Corpo (come JSON)
  • Valori modulo
  • Servizi forniti dall'inserimento delle dipendenze
  • Custom

Il gestore di route GET seguente usa alcune di queste origini di associazione di parametri:

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();

app.MapGet("/{id}", (int id,
                     int page,
                     [FromHeader(Name = "X-CUSTOM-HEADER")] string customHeader,
                     Service service) => { });

class Service { }

Funzionalità di associazione dei parametri chiave

  • Associazione esplicita: usare attributi come [FromRoute], [FromQuery][FromHeader], [FromBody], [FromForm], e [FromServices] per specificare in modo esplicito le origini di associazione.
  • Associazione di moduli: associare i valori dei moduli usando l'attributo [FromForm] , incluso il supporto per IFormFile e IFormFileCollection per i caricamenti di file.
  • Tipi complessi: Legare alle raccolte e ai tipi complessi nei moduli, nelle stringhe di query e nelle intestazioni.
  • Associazione personalizzata: implementare la logica di associazione personalizzata usando TryParse, BindAsynco l'interfaccia IBindableFromHttpContext<T> .
  • Parametri facoltativi: supportano i tipi nullable e i valori predefiniti per i parametri facoltativi.
  • Inserimento delle dipendenze: i parametri vengono automaticamente associati ai servizi registrati nel contenitore DI.
  • Tipi speciali: associazione automatica per HttpContext, HttpRequest, HttpResponse, CancellationTokenClaimsPrincipal, Stream, e PipeReader.

Ulteriori informazioni: Per informazioni dettagliate sull'associazione di parametri, inclusi scenari avanzati, convalida, precedenza di associazione e risoluzione dei problemi, vedere Associazione di parametri nelle applicazioni API minime.

Responses

I gestori di route supportano i tipi di valori restituiti seguenti:

  1. IResult based : include Task<IResult> e ValueTask<IResult>
  2. string - Questo include Task<string> e ValueTask<string>
  3. T (Qualsiasi altro tipo) - Include Task<T> e ValueTask<T>
Valore restituito Behavior Content-Type
IResult Il framework chiama IResult.ExecuteAsync Deciso dall'implementazione IResult
string Il framework scrive la stringa direttamente nella risposta text/plain
T (Qualsiasi altro tipo) Il framework JSON serializza la risposta application/json

Per una guida più approfondita ai valori restituiti del gestore di route, vedere Creare risposte nelle applicazioni API minime

Valori restituiti di esempio

valori restituiti di stringa

app.MapGet("/hello", () => "Hello World");

Valori restituiti JSON

app.MapGet("/hello", () => new { Message = "Hello World" });

Restituisci TypedResults

Il codice seguente restituisce un oggetto TypedResults:

app.MapGet("/hello", () => TypedResults.Ok(new Message() {  Text = "Hello World!" }));

La restituzione di è preferibile TypedResultsResultsper restituire . Per altre informazioni, vedere TypedResults vs Results.

Valori restituiti IResult

app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));

Nell'esempio seguente vengono usati i tipi di risultati predefiniti per personalizzare la risposta:

app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
         await db.Todos.FindAsync(id) 
         is Todo todo
         ? Results.Ok(todo) 
         : Results.NotFound())
   .Produces<Todo>(StatusCodes.Status200OK)
   .Produces(StatusCodes.Status404NotFound);

JSON

app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));

Codice di stato personalizzato

app.MapGet("/405", () => Results.StatusCode(405));

Text

app.MapGet("/text", () => Results.Text("This is some text"));

Stream

var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () => 
{
    var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
    // Proxy the response as JSON
    return Results.Stream(stream, "application/json");
});

Per altri esempi, vedere Creare risposte nelle applicazioni API minime.

Redirect

app.MapGet("/old-path", () => Results.Redirect("/new-path"));

File

app.MapGet("/download", () => Results.File("myfile.text"));

Risultati predefiniti

Gli helper di risultati comuni esistono nelle Results classi statiche e TypedResults . La restituzione di è preferibile TypedResultsResultsper restituire . Per altre informazioni, vedere TypedResults vs Results.

Modifica delle intestazioni

Usare l'oggetto HttpResponse per modificare le intestazioni di risposta:

app.MapGet("/", (HttpContext context) => {
    // Set a custom header
    context.Response.Headers["X-Custom-Header"] = "CustomValue";

    // Set a known header
    context.Response.Headers.CacheControl = $"public,max-age=3600";

    return "Hello World";
});

Personalizzazione dei risultati

Le applicazioni possono controllare le risposte implementando un tipo personalizzato IResult . Il codice seguente è un esempio di tipo di risultato HTML:

using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
    public static IResult Html(this IResultExtensions resultExtensions, string html)
    {
        ArgumentNullException.ThrowIfNull(resultExtensions);

        return new HtmlResult(html);
    }
}

class HtmlResult : IResult
{
    private readonly string _html;

    public HtmlResult(string html)
    {
        _html = html;
    }

    public Task ExecuteAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Html;
        httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
        return httpContext.Response.WriteAsync(_html);
    }
}

È consigliabile aggiungere un metodo di estensione per Microsoft.AspNetCore.Http.IResultExtensions rendere questi risultati personalizzati più individuabili.

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

app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
    <head><title>miniHTML</title></head>
    <body>
        <h1>Hello World</h1>
        <p>The time on the server is {DateTime.Now:O}</p>
    </body>
</html>"));

app.Run();

Risultati tipizzato

L'interfaccia IResult può rappresentare i valori restituiti da API minime che non usano il supporto implicito per la serializzazione JSON dell'oggetto restituito alla risposta HTTP. La classe static Results viene usata per creare oggetti variabili IResult che rappresentano tipi diversi di risposte. Ad esempio, impostare il codice di stato della risposta o reindirizzare a un altro URL.

I tipi che implementano IResult sono pubblici, consentendo le asserzioni di tipo durante il test. Per esempio:

[TestClass()]
public class WeatherApiTests
{
    [TestMethod()]
    public void MapWeatherApiTest()
    {
        var result = WeatherApi.GetAllWeathers();
        Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
    }      
}

È possibile esaminare i tipi restituiti dei metodi corrispondenti nella classe Static TypedResults per trovare il tipo pubblico IResult corretto a cui eseguire il cast.

Per altri esempi, vedere Creare risposte nelle applicazioni API minime.

Filters

Per altre informazioni, vedere Filtri nelle app per le API minime.

Authorization

Le route possono essere protette usando i criteri di autorizzazione. Questi valori possono essere dichiarati tramite l'attributo [Authorize] o usando il RequireAuthorization metodo :

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/auth", [Authorize] () => "This endpoint requires authorization.");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

Il codice precedente può essere scritto con RequireAuthorization:

app.MapGet("/auth", () => "This endpoint requires authorization")
   .RequireAuthorization();

L'esempio seguente usa l'autorizzazione basata su criteri:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/admin", [Authorize("AdminsOnly")] () => 
                             "The /admin endpoint is for admins only.");

app.MapGet("/admin2", () => "The /admin2 endpoint is for admins only.")
   .RequireAuthorization("AdminsOnly");

app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

Consentire agli utenti non autenticati di accedere a un endpoint

[AllowAnonymous] consente agli utenti non autenticati di accedere agli endpoint:

app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");


app.MapGet("/login2", () => "This endpoint also for all roles.")
   .AllowAnonymous();

CORS

Le route possono essere abilitate per CORS usando i criteri CORS. È possibile dichiarare CORS tramite l'attributo [EnableCors] o usando il RequireCors metodo . Gli esempi seguenti abilitano CORS:

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/",() => "Hello CORS!");

app.Run();
using Microsoft.AspNetCore.Cors;

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/cors", [EnableCors(MyAllowSpecificOrigins)] () => 
                           "This endpoint allows cross origin requests!");
app.MapGet("/cors2", () => "This endpoint allows cross origin requests!")
                     .RequireCors(MyAllowSpecificOrigins);

app.Run();

Per altre informazioni, vedere Abilitare le richieste tra le origini (CORS) in ASP.NET Core

ValidateScopes e ValidateOnBuild

ValidateScopes e ValidateOnBuild sono abilitati per impostazione predefinita nell'ambiente di sviluppo , ma disabilitati in altri ambienti.

Quando ValidateOnBuild è true, il contenitore di inserimento delle dipendenze convalida la configurazione del servizio in fase di compilazione. Se la configurazione del servizio non è valida, la compilazione non riesce all'avvio dell'app, anziché in fase di esecuzione quando viene richiesto il servizio.

Quando ValidateScopes è true, il contenitore di inserimento delle dipendenze verifica che un servizio con ambito non venga risolto dall'ambito radice. La risoluzione di un servizio con ambito dall'ambito radice può causare una perdita di memoria perché il servizio viene mantenuto in memoria più lungo dell'ambito della richiesta.

ValidateScopes e ValidateOnBuild sono false per impostazione predefinita nelle modalità non di sviluppo per motivi di prestazioni.

Il codice seguente mostra ValidateScopes che è abilitato per impostazione predefinita in modalità di sviluppo, ma disabilitato in modalità di rilascio:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<MyScopedService>();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    Console.WriteLine("Development environment");
}
else
{
    Console.WriteLine("Release environment");
}

app.MapGet("/", context =>
{
    // Intentionally getting service provider from app, not from the request
    // This causes an exception from attempting to resolve a scoped service
    // outside of a scope.
    // Throws System.InvalidOperationException:
    // 'Cannot resolve scoped service 'MyScopedService' from root provider.'
    var service = app.Services.GetRequiredService<MyScopedService>();
    return context.Response.WriteAsync("Service resolved");
});

app.Run();

public class MyScopedService { }

Il codice seguente mostra ValidateOnBuild che è abilitato per impostazione predefinita in modalità di sviluppo, ma disabilitato in modalità di rilascio:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<MyScopedService>();
builder.Services.AddScoped<AnotherService>();

// System.AggregateException: 'Some services are not able to be constructed (Error
// while validating the service descriptor 'ServiceType: AnotherService Lifetime:
// Scoped ImplementationType: AnotherService': Unable to resolve service for type
// 'BrokenService' while attempting to activate 'AnotherService'.)'
var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    Console.WriteLine("Development environment");
}
else
{
    Console.WriteLine("Release environment");
}

app.MapGet("/", context =>
{
    var service = context.RequestServices.GetRequiredService<MyScopedService>();
    return context.Response.WriteAsync("Service resolved correctly!");
});

app.Run();

public class MyScopedService { }

public class AnotherService
{
    public AnotherService(BrokenService brokenService) { }
}

public class BrokenService { }

Il codice seguente disabilita ValidateScopes e ValidateOnBuild in Development:

var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsDevelopment())
{
    Console.WriteLine("Development environment");
    // Doesn't detect the validation problems because ValidateScopes is false.
    builder.Host.UseDefaultServiceProvider(options =>
    {
        options.ValidateScopes = false;
        options.ValidateOnBuild = false;
    });
}

Vedere anche

Questo documento:

Le API minime sono costituite da:

WebApplication

Il codice seguente viene generato da un modello ASP.NET Core:

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

app.MapGet("/", () => "Hello World!");

app.Run();

Il codice precedente può essere creato tramite dotnet new web la riga di comando o selezionando il modello Web vuoto in Visual Studio.

Il codice seguente crea un oggetto WebApplication (app) senza creare in modo esplicito un oggetto WebApplicationBuilder:

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run();

WebApplication.Create inizializza una nuova istanza della WebApplication classe con impostazioni predefinite preconfigurate.

WebApplication aggiunge automaticamente il middleware seguente nelle applicazioni API minime a seconda di determinate condizioni:

  • UseDeveloperExceptionPage viene aggiunto per primo quando è HostingEnvironment"Development".
  • UseRouting viene aggiunto secondo se il codice utente non ha già chiamato UseRouting e se sono stati configurati endpoint, ad esempio app.MapGet.
  • UseEndpoints viene aggiunto alla fine della pipeline middleware se sono configurati endpoint.
  • UseAuthentication viene aggiunto immediatamente dopo UseRouting se il codice utente non ha già chiamato UseAuthentication e se IAuthenticationSchemeProvider è possibile rilevare nel provider di servizi. IAuthenticationSchemeProvider viene aggiunto per impostazione predefinita quando si usano AddAuthenticationi servizi e viene rilevato tramite IServiceProviderIsService.
  • UseAuthorization viene aggiunto successivamente se il codice utente non ha già chiamato UseAuthorization e se IAuthorizationHandlerProvider è possibile rilevare nel provider di servizi. IAuthorizationHandlerProvider viene aggiunto per impostazione predefinita quando si usano AddAuthorizationi servizi e viene rilevato tramite IServiceProviderIsService.
  • Il middleware e gli endpoint configurati dall'utente vengono aggiunti tra UseRouting e UseEndpoints.

Il codice seguente è effettivamente ciò che il middleware automatico aggiunto all'app produce:

if (isDevelopment)
{
    app.UseDeveloperExceptionPage();
}

app.UseRouting();

if (isAuthenticationConfigured)
{
    app.UseAuthentication();
}

if (isAuthorizationConfigured)
{
    app.UseAuthorization();
}

// user middleware/endpoints
app.CustomMiddleware(...);
app.MapGet("/", () => "hello world");
// end user middleware/endpoints

app.UseEndpoints(e => {});

In alcuni casi, la configurazione del middleware predefinita non è corretta per l'app e richiede modifiche. Ad esempio, UseCors deve essere chiamato prima UseAuthentication di e UseAuthorization. L'app deve chiamare UseAuthentication e UseAuthorization se UseCors viene chiamato:

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

Se il middleware deve essere eseguito prima che si verifichi la corrispondenza della route, UseRouting deve essere chiamato e il middleware deve essere posizionato prima della chiamata a UseRouting. UseEndpoints in questo caso non è obbligatorio perché viene aggiunto automaticamente come descritto in precedenza:

app.Use((context, next) =>
{
    return next(context);
});

app.UseRouting();

// other middleware and endpoints

Quando si aggiunge un middleware del terminale:

  • Il middleware deve essere aggiunto dopo UseEndpoints.
  • L'app deve chiamare UseRouting e UseEndpoints in modo che il middleware del terminale possa essere posizionato nella posizione corretta.
app.UseRouting();

app.MapGet("/", () => "hello world");

app.UseEndpoints(e => {});

app.Run(context =>
{
    context.Response.StatusCode = 404;
    return Task.CompletedTask;
});

Il middleware del terminale è middleware che viene eseguito se nessun endpoint gestisce la richiesta.

Uso delle porte

Quando viene creata un'app Web con Visual Studio o dotnet new, viene creato un Properties/launchSettings.json file che specifica le porte a cui risponde l'app. Negli esempi di impostazione della porta che seguono, l'esecuzione dell'app da Visual Studio restituisce una finestra di dialogo Unable to connect to web server 'AppName'di errore. Visual Studio restituisce un errore perché prevede la porta specificata in Properties/launchSettings.json, ma l'app usa la porta specificata da app.Run("http://localhost:3000"). Eseguire i seguenti esempi di modifica della porta dalla riga di comando.

Le sezioni seguenti impostano la porta a cui risponde l'app.

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run("http://localhost:3000");

Nel codice precedente l'app risponde alla porta 3000.

Più porte

Nel codice seguente l'app risponde alla porta 3000 e 4000a .

var app = WebApplication.Create(args);

app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");

app.MapGet("/", () => "Hello World");

app.Run();

Impostare la porta dalla riga di comando

Il comando seguente rende l'app risponde alla porta 7777:

dotnet run --urls="https://localhost:7777"

Se l'endpoint Kestrel è configurato anche nel appsettings.json file, viene usato l'URL specificato dal appsettings.json file. Per altre informazioni, vedere Kestrel Configurazione dell'endpoint

Leggere la porta dall'ambiente

Il codice seguente legge la porta dall'ambiente:

var app = WebApplication.Create(args);

var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";

app.MapGet("/", () => "Hello World");

app.Run($"http://localhost:{port}");

Il modo migliore per impostare la porta dall'ambiente consiste nell'usare la ASPNETCORE_URLS variabile di ambiente, illustrata nella sezione seguente.

Impostare le porte tramite la variabile di ambiente ASPNETCORE_URLS

La ASPNETCORE_URLS variabile di ambiente è disponibile per impostare la porta:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS supporta più URL:

ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000

Ascoltare tutte le interfacce

Gli esempi seguenti illustrano l'ascolto su tutte le interfacce

http://*:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://*:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://+:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://+:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://0.0.0.0:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://0.0.0.0:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Ascoltare tutte le interfacce usando ASPNETCORE_URLS

Gli esempi precedenti possono usare ASPNETCORE_URLS

ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005

Ascoltare tutte le interfacce usando ASPNETCORE_HTTPS_PORTS

Gli esempi precedenti possono usare ASPNETCORE_HTTPS_PORTS e ASPNETCORE_HTTP_PORTS.

ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000

Per altre informazioni, vedere Configurare gli endpoint per il server Web ASP.NET Core Kestrel

Specificare HTTPS con il certificato di sviluppo

var app = WebApplication.Create(args);

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Per altre informazioni sul certificato di sviluppo, vedere Considerare attendibile il certificato di sviluppo HTTPS di ASP.NET Core in Windows e macOS.

Specificare HTTPS usando un certificato personalizzato

Le sezioni seguenti illustrano come specificare il certificato personalizzato usando il appsettings.json file e tramite la configurazione.

Specificare il certificato personalizzato con appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Kestrel": {
    "Certificates": {
      "Default": {
        "Path": "cert.pem",
        "KeyPath": "key.pem"
      }
    }
  }
}

Specificare il certificato personalizzato tramite la configurazione

var builder = WebApplication.CreateBuilder(args);

// Configure the cert and the key
builder.Configuration["Kestrel:Certificates:Default:Path"] = "cert.pem";
builder.Configuration["Kestrel:Certificates:Default:KeyPath"] = "key.pem";

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Usare le API del certificato

using System.Security.Cryptography.X509Certificates;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
    options.ConfigureHttpsDefaults(httpsOptions =>
    {
        var certPath = Path.Combine(builder.Environment.ContentRootPath, "cert.pem");
        var keyPath = Path.Combine(builder.Environment.ContentRootPath, "key.pem");

        httpsOptions.ServerCertificate = X509Certificate2.CreateFromPemFile(certPath, 
                                         keyPath);
    });
});

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Leggere l'ambiente

var app = WebApplication.Create(args);

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/oops");
}

app.MapGet("/", () => "Hello World");
app.MapGet("/oops", () => "Oops! An error happened.");

app.Run();

Per ulteriori informazioni sull'uso dell'ambiente, vedere ASP.NET Core ambienti di runtime

Configuration

Il codice seguente legge dal sistema di configurazione:

var app = WebApplication.Create(args);

var message = app.Configuration["HelloKey"] ?? "Config failed!";

app.MapGet("/", () => message);

app.Run();

Per altre informazioni, vedere Configurazione in ASP.NET Core

Logging

Il codice seguente scrive un messaggio all'avvio dell'applicazione di accesso:

var app = WebApplication.Create(args);

app.Logger.LogInformation("The app started");

app.MapGet("/", () => "Hello World");

app.Run();

Per altre informazioni, vedere Registrazione in .NET e ASP.NET Core

Accedere al contenitore di inserimento delle dipendenze

Il codice seguente illustra come ottenere servizi dal contenitore di inserimento delle dipendenze durante l'avvio dell'applicazione:


var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddScoped<SampleService>();

var app = builder.Build();

app.MapControllers();

using (var scope = app.Services.CreateScope())
{
    var sampleService = scope.ServiceProvider.GetRequiredService<SampleService>();
    sampleService.DoSomething();
}

app.Run();

Il codice seguente illustra come accedere alle chiavi dal contenitore di inserimento delle dipendenze usando l'attributo [FromKeyedServices] :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");

var app = builder.Build();

app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));

app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) => smallCache.Get("date"));

app.Run();

public interface ICache
{
    object Get(string key);
}
public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

Per altre informazioni sull'inserimento delle dipendenze, vedere Inserimento delle dipendenze in ASP.NET Core.

WebApplicationBuilder

Questa sezione contiene codice di esempio che usa WebApplicationBuilder.

Modificare la radice del contenuto, il nome dell'applicazione e l'ambiente

Il codice seguente imposta la radice del contenuto, il nome dell'applicazione e l'ambiente:

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    ApplicationName = typeof(Program).Assembly.FullName,
    ContentRootPath = Directory.GetCurrentDirectory(),
    EnvironmentName = Environments.Staging,
    WebRootPath = "customwwwroot"
});

Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");

var app = builder.Build();

WebApplication.CreateBuilder inizializza una nuova istanza della classe WebApplicationBuilder con valori predefiniti preconfigurati.

Per altre informazioni, vedere ASP.NET Panoramica dei concetti fondamentali di base

Modificare la radice del contenuto, il nome dell'app e l'ambiente usando variabili di ambiente o riga di comando

La tabella seguente illustra la variabile di ambiente e l'argomento della riga di comando usati per modificare la radice del contenuto, il nome dell'app e l'ambiente:

feature Variabile di ambiente Argomento della riga di comando
Nome applicazione ASPNETCORE_APPLICATIONNAME --applicationName
Nome dell'ambiente ASPNETCORE_ENVIRONMENT --environment
Radice del contenuto ASPNETCORE_CONTENTROOT --contentRoot

Aggiungere provider di configurazione

L'esempio seguente aggiunge il provider di configurazione INI:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddIniFile("appsettings.ini");

var app = builder.Build();

Per informazioni dettagliate, vedere Provider di configurazione file in Configurazione in ASP.NET Core.

Leggere la configurazione

Per impostazione predefinita, la WebApplicationBuilder configurazione legge da più origini, tra cui:

  • appSettings.json e appSettings.{environment}.json
  • Variabili di ambiente
  • Riga di comando

Per un elenco completo delle origini di configurazione, vedere Configurazione predefinita in Configurazione in ASP.NET Core.

Il codice seguente legge HelloKey dalla configurazione e visualizza il valore nell'endpoint / . Se il valore di configurazione è Null, "Hello" viene assegnato a message:

var builder = WebApplication.CreateBuilder(args);

var message = builder.Configuration["HelloKey"] ?? "Hello";

var app = builder.Build();

app.MapGet("/", () => message);

app.Run();

Leggere l'ambiente

var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsDevelopment())
{
    Console.WriteLine($"Running in development.");
}

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Aggiungere provider di registrazione

var builder = WebApplication.CreateBuilder(args);

// Configure JSON logging to the console.
builder.Logging.AddJsonConsole();

var app = builder.Build();

app.MapGet("/", () => "Hello JSON console!");

app.Run();

Aggiungere servizi

var builder = WebApplication.CreateBuilder(args);

// Add the memory cache services.
builder.Services.AddMemoryCache();

// Add a custom scoped service.
builder.Services.AddScoped<ITodoRepository, TodoRepository>();
var app = builder.Build();

Personalizzare IHostBuilder

È possibile accedere ai metodi di estensione esistenti in IHostBuilder usando la proprietà Host:

var builder = WebApplication.CreateBuilder(args);

// Wait 30 seconds for graceful shutdown.
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Personalizzare IWebHostBuilder

È possibile accedere ai metodi IWebHostBuilder di estensione in usando la proprietà WebApplicationBuilder.WebHost .

var builder = WebApplication.CreateBuilder(args);

// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();

var app = builder.Build();

app.MapGet("/", () => "Hello HTTP.sys");

app.Run();

Modificare la radice Web

Per impostazione predefinita, la radice Web è relativa alla radice del contenuto nella wwwroot cartella. La radice Web è la posizione in cui il middleware dei file statici cerca i file statici. La radice Web può essere modificata con WebHostOptions, la riga di comando o con il UseWebRoot metodo :

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    // Look for static files in webroot
    WebRootPath = "webroot"
});

var app = builder.Build();

app.Run();

Contenitore di inserimento delle dipendenze personalizzato

L'esempio seguente usa Autofac:

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));

var app = builder.Build();

Aggiungere middleware

Qualsiasi middleware core ASP.NET esistente può essere configurato in WebApplication:

var app = WebApplication.Create(args);

// Setup the file server to serve static files.
app.UseFileServer();

app.MapGet("/", () => "Hello World!");

app.Run();

Per altre informazioni, vedere middleware ASP.NET Core

Pagina delle eccezioni per gli sviluppatori

WebApplication.CreateBuilder inizializza una nuova istanza della WebApplicationBuilder classe con impostazioni predefinite preconfigurate. La pagina delle eccezioni per sviluppatori è abilitata nelle impostazioni predefinite preconfigurate. Quando il codice seguente viene eseguito nell'ambiente di sviluppo, spostarsi per eseguire / il rendering di una pagina descrittiva che mostra l'eccezione.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () =>
{
    throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});

app.Run();

Middleware di ASP.NET Core

La tabella seguente elenca alcuni dei middleware usati di frequente con API minime.

Middleware Description API
Authentication Offre il supporto dell'autenticazione. UseAuthentication
Authorization Fornisce il supporto per l'autorizzazione. UseAuthorization
CORS Configura la condivisione di risorse tra le origini (CORS). UseCors
Gestore eccezioni Gestisce globalmente le eccezioni generate dalla pipeline middleware. UseExceptionHandler
Intestazioni inoltrate Inoltra le intestazioni proxy nella richiesta corrente. UseForwardedHeaders
Reindirizzamento HTTPS Reindirizza tutte le richieste HTTP a HTTPS. UseHttpsRedirection
Protocollo HTTP Strict Transport Security (HSTS) Middleware di ottimizzazione della sicurezza che aggiunge un'intestazione della risposta speciale. UseHsts
Registrazione richieste Fornisce supporto per la registrazione di richieste e risposte HTTP. UseHttpLogging
Timeout delle richieste Fornisce il supporto per la configurazione dei timeout delle richieste, impostazione predefinita globale e per endpoint. UseRequestTimeouts
Registrazione delle richieste W3C Fornisce il supporto per la registrazione di richieste e risposte HTTP nel formato W3C. UseW3CLogging
Memorizzazione nella cache delle risposte Offre il supporto per la memorizzazione delle risposte nella cache. UseResponseCaching
Compressione della risposta Offre il supporto per la compressione delle risposte. UseResponseCompression
Session Offre il supporto per la gestione delle sessioni utente. UseSession
File statici Offre il supporto per la gestione di file statici e l'esplorazione directory. UseStaticFiles, UseFileServer
WebSockets Abilita il protocollo WebSocket. UseWebSockets

Le sezioni seguenti illustrano la gestione delle richieste: routing, associazione di parametri e risposte.

Routing

Un oggetto configurato supporta e dove è un metodo HTTP con maiuscole e minuscole camel, ad WebApplicationesempio , Map{Verb}MapMethods o {Verb}:GetPostPutDelete

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.

Espressione 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 dell'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 QueryPrivateTodosfactory 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

Associazione di parametri

L'associazione di parametri è il processo di conversione dei dati delle richieste in parametri fortemente tipizzato espressi dai gestori di route. Un'origine di associazione determina la posizione da cui sono associati i parametri. Le origini di associazione possono essere esplicite o dedotte in base al metodo HTTP e al tipo di parametro.

Origini di associazione supportate:

  • Valori di route
  • Stringa di query
  • Header
  • Corpo (come JSON)
  • Valori modulo
  • Servizi forniti dall'inserimento delle dipendenze
  • Custom

Il gestore di route GET seguente usa alcune di queste origini di associazione di parametri:

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();

app.MapGet("/{id}", (int id,
                     int page,
                     [FromHeader(Name = "X-CUSTOM-HEADER")] string customHeader,
                     Service service) => { });

class Service { }

Funzionalità di associazione dei parametri chiave

  • Associazione esplicita: usare attributi come [FromRoute], [FromQuery][FromHeader], [FromBody], [FromForm], e [FromServices] per specificare in modo esplicito le origini di associazione.
  • Associazione di moduli: associare i valori dei moduli usando l'attributo [FromForm] , incluso il supporto per IFormFile e IFormFileCollection per i caricamenti di file.
  • Tipi complessi: Legare alle raccolte e ai tipi complessi nei moduli, nelle stringhe di query e nelle intestazioni.
  • Associazione personalizzata: implementare la logica di associazione personalizzata usando TryParse, BindAsynco l'interfaccia IBindableFromHttpContext<T> .
  • Parametri facoltativi: supportano i tipi nullable e i valori predefiniti per i parametri facoltativi.
  • Inserimento delle dipendenze: i parametri vengono automaticamente associati ai servizi registrati nel contenitore DI.
  • Tipi speciali: associazione automatica per HttpContext, HttpRequest, HttpResponse, CancellationTokenClaimsPrincipal, Stream, e PipeReader.

Ulteriori informazioni: Per informazioni dettagliate sull'associazione di parametri, inclusi scenari avanzati, convalida, precedenza di associazione e risoluzione dei problemi, vedere Associazione di parametri nelle applicazioni API minime.

Responses

I gestori di route supportano i tipi di valori restituiti seguenti:

  1. IResult based : include Task<IResult> e ValueTask<IResult>
  2. string - Questo include Task<string> e ValueTask<string>
  3. T (Qualsiasi altro tipo) - Include Task<T> e ValueTask<T>
Valore restituito Behavior Content-Type
IResult Il framework chiama IResult.ExecuteAsync Deciso dall'implementazione IResult
string Il framework scrive la stringa direttamente nella risposta text/plain
T (Qualsiasi altro tipo) Il framework JSON serializza la risposta application/json

Per una guida più approfondita ai valori restituiti del gestore di route, vedere Creare risposte nelle applicazioni API minime

Valori restituiti di esempio

valori restituiti di stringa

app.MapGet("/hello", () => "Hello World");

Valori restituiti JSON

app.MapGet("/hello", () => new { Message = "Hello World" });

Restituisci TypedResults

Il codice seguente restituisce un oggetto TypedResults:

app.MapGet("/hello", () => TypedResults.Ok(new Message() {  Text = "Hello World!" }));

La restituzione di è preferibile TypedResultsResultsper restituire . Per altre informazioni, vedere TypedResults vs Results.

Valori restituiti IResult

app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));

Nell'esempio seguente vengono usati i tipi di risultati predefiniti per personalizzare la risposta:

app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
         await db.Todos.FindAsync(id) 
         is Todo todo
         ? Results.Ok(todo) 
         : Results.NotFound())
   .Produces<Todo>(StatusCodes.Status200OK)
   .Produces(StatusCodes.Status404NotFound);

JSON

app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));

Codice di stato personalizzato

app.MapGet("/405", () => Results.StatusCode(405));

Text

app.MapGet("/text", () => Results.Text("This is some text"));

Stream

var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () => 
{
    var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
    // Proxy the response as JSON
    return Results.Stream(stream, "application/json");
});

Per altri esempi, vedere Creare risposte nelle applicazioni API minime.

Redirect

app.MapGet("/old-path", () => Results.Redirect("/new-path"));

File

app.MapGet("/download", () => Results.File("myfile.text"));

Risultati predefiniti

Gli helper di risultati comuni esistono nelle Results classi statiche e TypedResults . La restituzione di è preferibile TypedResultsResultsper restituire . Per altre informazioni, vedere TypedResults vs Results.

Modifica delle intestazioni

Usare l'oggetto HttpResponse per modificare le intestazioni di risposta:

app.MapGet("/", (HttpContext context) => {
    // Set a custom header
    context.Response.Headers["X-Custom-Header"] = "CustomValue";

    // Set a known header
    context.Response.Headers.CacheControl = $"public,max-age=3600";

    return "Hello World";
});

Personalizzazione dei risultati

Le applicazioni possono controllare le risposte implementando un tipo personalizzato IResult . Il codice seguente è un esempio di tipo di risultato HTML:

using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
    public static IResult Html(this IResultExtensions resultExtensions, string html)
    {
        ArgumentNullException.ThrowIfNull(resultExtensions);

        return new HtmlResult(html);
    }
}

class HtmlResult : IResult
{
    private readonly string _html;

    public HtmlResult(string html)
    {
        _html = html;
    }

    public Task ExecuteAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Html;
        httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
        return httpContext.Response.WriteAsync(_html);
    }
}

È consigliabile aggiungere un metodo di estensione per Microsoft.AspNetCore.Http.IResultExtensions rendere questi risultati personalizzati più individuabili.

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

app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
    <head><title>miniHTML</title></head>
    <body>
        <h1>Hello World</h1>
        <p>The time on the server is {DateTime.Now:O}</p>
    </body>
</html>"));

app.Run();

Risultati tipizzato

L'interfaccia IResult può rappresentare i valori restituiti da API minime che non usano il supporto implicito per la serializzazione JSON dell'oggetto restituito alla risposta HTTP. La classe static Results viene usata per creare oggetti variabili IResult che rappresentano tipi diversi di risposte. Ad esempio, impostare il codice di stato della risposta o reindirizzare a un altro URL.

I tipi che implementano IResult sono pubblici, consentendo le asserzioni di tipo durante il test. Per esempio:

[TestClass()]
public class WeatherApiTests
{
    [TestMethod()]
    public void MapWeatherApiTest()
    {
        var result = WeatherApi.GetAllWeathers();
        Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
    }      
}

È possibile esaminare i tipi restituiti dei metodi corrispondenti nella classe Static TypedResults per trovare il tipo pubblico IResult corretto a cui eseguire il cast.

Per altri esempi, vedere Creare risposte nelle applicazioni API minime.

Filters

Per altre informazioni, vedere Filtri nelle app per le API minime.

Authorization

Le route possono essere protette usando i criteri di autorizzazione. Questi valori possono essere dichiarati tramite l'attributo [Authorize] o usando il RequireAuthorization metodo :

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/auth", [Authorize] () => "This endpoint requires authorization.");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

Il codice precedente può essere scritto con RequireAuthorization:

app.MapGet("/auth", () => "This endpoint requires authorization")
   .RequireAuthorization();

L'esempio seguente usa l'autorizzazione basata su criteri:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/admin", [Authorize("AdminsOnly")] () => 
                             "The /admin endpoint is for admins only.");

app.MapGet("/admin2", () => "The /admin2 endpoint is for admins only.")
   .RequireAuthorization("AdminsOnly");

app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

Consentire agli utenti non autenticati di accedere a un endpoint

[AllowAnonymous] consente agli utenti non autenticati di accedere agli endpoint:

app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");


app.MapGet("/login2", () => "This endpoint also for all roles.")
   .AllowAnonymous();

CORS

Le route possono essere abilitate per CORS usando i criteri CORS. È possibile dichiarare CORS tramite l'attributo [EnableCors] o usando il RequireCors metodo . Gli esempi seguenti abilitano CORS:

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/",() => "Hello CORS!");

app.Run();
using Microsoft.AspNetCore.Cors;

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/cors", [EnableCors(MyAllowSpecificOrigins)] () => 
                           "This endpoint allows cross origin requests!");
app.MapGet("/cors2", () => "This endpoint allows cross origin requests!")
                     .RequireCors(MyAllowSpecificOrigins);

app.Run();

Per altre informazioni, vedere Abilitare le richieste tra le origini (CORS) in ASP.NET Core

ValidateScopes e ValidateOnBuild

ValidateScopes e ValidateOnBuild sono abilitati per impostazione predefinita nell'ambiente di sviluppo , ma disabilitati in altri ambienti.

Quando ValidateOnBuild è true, il contenitore di inserimento delle dipendenze convalida la configurazione del servizio in fase di compilazione. Se la configurazione del servizio non è valida, la compilazione non riesce all'avvio dell'app, anziché in fase di esecuzione quando viene richiesto il servizio.

Quando ValidateScopes è true, il contenitore di inserimento delle dipendenze verifica che un servizio con ambito non venga risolto dall'ambito radice. La risoluzione di un servizio con ambito dall'ambito radice può causare una perdita di memoria perché il servizio viene mantenuto in memoria più lungo dell'ambito della richiesta.

ValidateScopes e ValidateOnBuild sono false per impostazione predefinita nelle modalità non di sviluppo per motivi di prestazioni.

Il codice seguente mostra ValidateScopes che è abilitato per impostazione predefinita in modalità di sviluppo, ma disabilitato in modalità di rilascio:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<MyScopedService>();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    Console.WriteLine("Development environment");
}
else
{
    Console.WriteLine("Release environment");
}

app.MapGet("/", context =>
{
    // Intentionally getting service provider from app, not from the request
    // This causes an exception from attempting to resolve a scoped service
    // outside of a scope.
    // Throws System.InvalidOperationException:
    // 'Cannot resolve scoped service 'MyScopedService' from root provider.'
    var service = app.Services.GetRequiredService<MyScopedService>();
    return context.Response.WriteAsync("Service resolved");
});

app.Run();

public class MyScopedService { }

Il codice seguente mostra ValidateOnBuild che è abilitato per impostazione predefinita in modalità di sviluppo, ma disabilitato in modalità di rilascio:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<MyScopedService>();
builder.Services.AddScoped<AnotherService>();

// System.AggregateException: 'Some services are not able to be constructed (Error
// while validating the service descriptor 'ServiceType: AnotherService Lifetime:
// Scoped ImplementationType: AnotherService': Unable to resolve service for type
// 'BrokenService' while attempting to activate 'AnotherService'.)'
var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    Console.WriteLine("Development environment");
}
else
{
    Console.WriteLine("Release environment");
}

app.MapGet("/", context =>
{
    var service = context.RequestServices.GetRequiredService<MyScopedService>();
    return context.Response.WriteAsync("Service resolved correctly!");
});

app.Run();

public class MyScopedService { }

public class AnotherService
{
    public AnotherService(BrokenService brokenService) { }
}

public class BrokenService { }

Il codice seguente disabilita ValidateScopes e ValidateOnBuild in Development:

var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsDevelopment())
{
    Console.WriteLine("Development environment");
    // Doesn't detect the validation problems because ValidateScopes is false.
    builder.Host.UseDefaultServiceProvider(options =>
    {
        options.ValidateScopes = false;
        options.ValidateOnBuild = false;
    });
}

Vedere anche

Questo documento:

Le API minime sono costituite da:

WebApplication

Il codice seguente viene generato da un modello ASP.NET Core:

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

app.MapGet("/", () => "Hello World!");

app.Run();

Il codice precedente può essere creato tramite dotnet new web la riga di comando o selezionando il modello Web vuoto in Visual Studio.

Il codice seguente crea un oggetto WebApplication (app) senza creare in modo esplicito un oggetto WebApplicationBuilder:

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run();

WebApplication.Create inizializza una nuova istanza della WebApplication classe con impostazioni predefinite preconfigurate.

WebApplication aggiunge automaticamente il middleware seguente nelle applicazioni API minime a seconda di determinate condizioni:

  • UseDeveloperExceptionPage viene aggiunto per primo quando è HostingEnvironment"Development".
  • UseRouting viene aggiunto secondo se il codice utente non ha già chiamato UseRouting e se sono stati configurati endpoint, ad esempio app.MapGet.
  • UseEndpoints viene aggiunto alla fine della pipeline middleware se sono configurati endpoint.
  • UseAuthentication viene aggiunto immediatamente dopo UseRouting se il codice utente non ha già chiamato UseAuthentication e se IAuthenticationSchemeProvider è possibile rilevare nel provider di servizi. IAuthenticationSchemeProvider viene aggiunto per impostazione predefinita quando si usano AddAuthenticationi servizi e viene rilevato tramite IServiceProviderIsService.
  • UseAuthorization viene aggiunto successivamente se il codice utente non ha già chiamato UseAuthorization e se IAuthorizationHandlerProvider è possibile rilevare nel provider di servizi. IAuthorizationHandlerProvider viene aggiunto per impostazione predefinita quando si usano AddAuthorizationi servizi e viene rilevato tramite IServiceProviderIsService.
  • Il middleware e gli endpoint configurati dall'utente vengono aggiunti tra UseRouting e UseEndpoints.

Il codice seguente è effettivamente ciò che il middleware automatico aggiunto all'app produce:

if (isDevelopment)
{
    app.UseDeveloperExceptionPage();
}

app.UseRouting();

if (isAuthenticationConfigured)
{
    app.UseAuthentication();
}

if (isAuthorizationConfigured)
{
    app.UseAuthorization();
}

// user middleware/endpoints
app.CustomMiddleware(...);
app.MapGet("/", () => "hello world");
// end user middleware/endpoints

app.UseEndpoints(e => {});

In alcuni casi, la configurazione del middleware predefinita non è corretta per l'app e richiede modifiche. Ad esempio, UseCors deve essere chiamato prima UseAuthentication di e UseAuthorization. L'app deve chiamare UseAuthentication e UseAuthorization se UseCors viene chiamato:

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

Se il middleware deve essere eseguito prima che si verifichi la corrispondenza della route, UseRouting deve essere chiamato e il middleware deve essere posizionato prima della chiamata a UseRouting. UseEndpoints in questo caso non è obbligatorio perché viene aggiunto automaticamente come descritto in precedenza:

app.Use((context, next) =>
{
    return next(context);
});

app.UseRouting();

// other middleware and endpoints

Quando si aggiunge un middleware del terminale:

  • Il middleware deve essere aggiunto dopo UseEndpoints.
  • L'app deve chiamare UseRouting e UseEndpoints in modo che il middleware del terminale possa essere posizionato nella posizione corretta.
app.UseRouting();

app.MapGet("/", () => "hello world");

app.UseEndpoints(e => {});

app.Run(context =>
{
    context.Response.StatusCode = 404;
    return Task.CompletedTask;
});

Il middleware del terminale è middleware che viene eseguito se nessun endpoint gestisce la richiesta.

Uso delle porte

Quando viene creata un'app Web con Visual Studio o dotnet new, viene creato un Properties/launchSettings.json file che specifica le porte a cui risponde l'app. Negli esempi di impostazione della porta che seguono, l'esecuzione dell'app da Visual Studio restituisce una finestra di dialogo Unable to connect to web server 'AppName'di errore. Visual Studio restituisce un errore perché prevede la porta specificata in Properties/launchSettings.json, ma l'app usa la porta specificata da app.Run("http://localhost:3000"). Eseguire i seguenti esempi di modifica della porta dalla riga di comando.

Le sezioni seguenti impostano la porta a cui risponde l'app.

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run("http://localhost:3000");

Nel codice precedente l'app risponde alla porta 3000.

Più porte

Nel codice seguente l'app risponde alla porta 3000 e 4000a .

var app = WebApplication.Create(args);

app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");

app.MapGet("/", () => "Hello World");

app.Run();

Impostare la porta dalla riga di comando

Il comando seguente rende l'app risponde alla porta 7777:

dotnet run --urls="https://localhost:7777"

Se l'endpoint Kestrel è configurato anche nel appsettings.json file, viene usato l'URL specificato dal appsettings.json file. Per altre informazioni, vedere Kestrel Configurazione dell'endpoint

Leggere la porta dall'ambiente

Il codice seguente legge la porta dall'ambiente:

var app = WebApplication.Create(args);

var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";

app.MapGet("/", () => "Hello World");

app.Run($"http://localhost:{port}");

Il modo migliore per impostare la porta dall'ambiente consiste nell'usare la ASPNETCORE_URLS variabile di ambiente, illustrata nella sezione seguente.

Impostare le porte tramite la variabile di ambiente ASPNETCORE_URLS

La ASPNETCORE_URLS variabile di ambiente è disponibile per impostare la porta:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS supporta più URL:

ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000

Per ulteriori informazioni sull'uso dell'ambiente, vedere ASP.NET Core ambienti di runtime

Ascoltare tutte le interfacce

Gli esempi seguenti illustrano l'ascolto su tutte le interfacce

http://*:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://*:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://+:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://+:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://0.0.0.0:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://0.0.0.0:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Ascoltare tutte le interfacce usando ASPNETCORE_URLS

Gli esempi precedenti possono usare ASPNETCORE_URLS

ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005

Specificare HTTPS con il certificato di sviluppo

var app = WebApplication.Create(args);

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Per altre informazioni sul certificato di sviluppo, vedere Considerare attendibile il certificato di sviluppo HTTPS di ASP.NET Core in Windows e macOS.

Specificare HTTPS usando un certificato personalizzato

Le sezioni seguenti illustrano come specificare il certificato personalizzato usando il appsettings.json file e tramite la configurazione.

Specificare il certificato personalizzato con appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Kestrel": {
    "Certificates": {
      "Default": {
        "Path": "cert.pem",
        "KeyPath": "key.pem"
      }
    }
  }
}

Specificare il certificato personalizzato tramite la configurazione

var builder = WebApplication.CreateBuilder(args);

// Configure the cert and the key
builder.Configuration["Kestrel:Certificates:Default:Path"] = "cert.pem";
builder.Configuration["Kestrel:Certificates:Default:KeyPath"] = "key.pem";

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Usare le API del certificato

using System.Security.Cryptography.X509Certificates;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
    options.ConfigureHttpsDefaults(httpsOptions =>
    {
        var certPath = Path.Combine(builder.Environment.ContentRootPath, "cert.pem");
        var keyPath = Path.Combine(builder.Environment.ContentRootPath, "key.pem");

        httpsOptions.ServerCertificate = X509Certificate2.CreateFromPemFile(certPath, 
                                         keyPath);
    });
});

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Configuration

Il codice seguente legge dal sistema di configurazione:

var app = WebApplication.Create(args);

var message = app.Configuration["HelloKey"] ?? "Config failed!";

app.MapGet("/", () => message);

app.Run();

Per altre informazioni, vedere Configurazione in ASP.NET Core

Logging

Il codice seguente scrive un messaggio all'avvio dell'applicazione di accesso:

var app = WebApplication.Create(args);

app.Logger.LogInformation("The app started");

app.MapGet("/", () => "Hello World");

app.Run();

Per altre informazioni, vedere Registrazione in .NET e ASP.NET Core

Accedere al contenitore di inserimento delle dipendenze

Il codice seguente illustra come ottenere servizi dal contenitore di inserimento delle dipendenze durante l'avvio dell'applicazione:


var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddScoped<SampleService>();

var app = builder.Build();

app.MapControllers();

using (var scope = app.Services.CreateScope())
{
    var sampleService = scope.ServiceProvider.GetRequiredService<SampleService>();
    sampleService.DoSomething();
}

app.Run();

Per altre informazioni, vedere Inserimento di dipendenze in ASP.NET Core.

WebApplicationBuilder

Questa sezione contiene codice di esempio che usa WebApplicationBuilder.

Modificare la radice del contenuto, il nome dell'applicazione e l'ambiente

Il codice seguente imposta la radice del contenuto, il nome dell'applicazione e l'ambiente:

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    ApplicationName = typeof(Program).Assembly.FullName,
    ContentRootPath = Directory.GetCurrentDirectory(),
    EnvironmentName = Environments.Staging,
    WebRootPath = "customwwwroot"
});

Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");

var app = builder.Build();

WebApplication.CreateBuilder inizializza una nuova istanza della classe WebApplicationBuilder con valori predefiniti preconfigurati.

Per altre informazioni, vedere ASP.NET Panoramica dei concetti fondamentali di base

Modificare la radice del contenuto, il nome dell'app e l'ambiente in base alle variabili di ambiente o alla riga di comando

La tabella seguente illustra la variabile di ambiente e l'argomento della riga di comando usati per modificare la radice del contenuto, il nome dell'app e l'ambiente:

feature Variabile di ambiente Argomento della riga di comando
Nome applicazione ASPNETCORE_APPLICATIONNAME --applicationName
Nome dell'ambiente ASPNETCORE_ENVIRONMENT --environment
Radice del contenuto ASPNETCORE_CONTENTROOT --contentRoot

Aggiungere provider di configurazione

L'esempio seguente aggiunge il provider di configurazione INI:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddIniFile("appsettings.ini");

var app = builder.Build();

Per informazioni dettagliate, vedere Provider di configurazione file in Configurazione in ASP.NET Core.

Leggere la configurazione

Per impostazione predefinita, la WebApplicationBuilder configurazione legge da più origini, tra cui:

  • appSettings.json e appSettings.{environment}.json
  • Variabili di ambiente
  • Riga di comando

Il codice seguente legge HelloKey dalla configurazione e visualizza il valore nell'endpoint / . Se il valore di configurazione è Null, "Hello" viene assegnato a message:

var builder = WebApplication.CreateBuilder(args);

var message = builder.Configuration["HelloKey"] ?? "Hello";

var app = builder.Build();

app.MapGet("/", () => message);

app.Run();

Per un elenco completo delle origini di configurazione lette, vedere Configurazione predefinita in Configurazione in ASP.NET Core

Aggiungere provider di registrazione

var builder = WebApplication.CreateBuilder(args);

// Configure JSON logging to the console.
builder.Logging.AddJsonConsole();

var app = builder.Build();

app.MapGet("/", () => "Hello JSON console!");

app.Run();

Aggiungere servizi

var builder = WebApplication.CreateBuilder(args);

// Add the memory cache services.
builder.Services.AddMemoryCache();

// Add a custom scoped service.
builder.Services.AddScoped<ITodoRepository, TodoRepository>();
var app = builder.Build();

Personalizzare IHostBuilder

È possibile accedere ai metodi di estensione esistenti in IHostBuilder usando la proprietà Host:

var builder = WebApplication.CreateBuilder(args);

// Wait 30 seconds for graceful shutdown.
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Personalizzare IWebHostBuilder

È possibile accedere ai metodi IWebHostBuilder di estensione in usando la proprietà WebApplicationBuilder.WebHost .

var builder = WebApplication.CreateBuilder(args);

// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();

var app = builder.Build();

app.MapGet("/", () => "Hello HTTP.sys");

app.Run();

Modificare la radice Web

Per impostazione predefinita, la radice Web è relativa alla radice del contenuto nella wwwroot cartella. La radice Web è la posizione in cui il middleware dei file statici cerca i file statici. La radice Web può essere modificata con WebHostOptions, la riga di comando o con il UseWebRoot metodo :

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    // Look for static files in webroot
    WebRootPath = "webroot"
});

var app = builder.Build();

app.Run();

Contenitore di inserimento delle dipendenze personalizzato

L'esempio seguente usa Autofac:

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));

var app = builder.Build();

Aggiungere middleware

Qualsiasi middleware core ASP.NET esistente può essere configurato in WebApplication:

var app = WebApplication.Create(args);

// Setup the file server to serve static files.
app.UseFileServer();

app.MapGet("/", () => "Hello World!");

app.Run();

Per altre informazioni, vedere middleware ASP.NET Core

Pagina delle eccezioni per gli sviluppatori

WebApplication.CreateBuilder inizializza una nuova istanza della WebApplicationBuilder classe con impostazioni predefinite preconfigurate. La pagina delle eccezioni per sviluppatori è abilitata nelle impostazioni predefinite preconfigurate. Quando il codice seguente viene eseguito nell'ambiente di sviluppo, spostarsi per eseguire / il rendering di una pagina descrittiva che mostra l'eccezione.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () =>
{
    throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});

app.Run();

Middleware di ASP.NET Core

La tabella seguente elenca alcuni dei middleware usati di frequente con API minime.

Middleware Description API
Authentication Offre il supporto dell'autenticazione. UseAuthentication
Authorization Fornisce il supporto per l'autorizzazione. UseAuthorization
CORS Configura la condivisione di risorse tra le origini (CORS). UseCors
Gestore eccezioni Gestisce globalmente le eccezioni generate dalla pipeline middleware. UseExceptionHandler
Intestazioni inoltrate Inoltra le intestazioni proxy nella richiesta corrente. UseForwardedHeaders
Reindirizzamento HTTPS Reindirizza tutte le richieste HTTP a HTTPS. UseHttpsRedirection
Protocollo HTTP Strict Transport Security (HSTS) Middleware di ottimizzazione della sicurezza che aggiunge un'intestazione della risposta speciale. UseHsts
Registrazione richieste Fornisce supporto per la registrazione di richieste e risposte HTTP. UseHttpLogging
Timeout delle richieste Fornisce il supporto per la configurazione dei timeout delle richieste, impostazione predefinita globale e per endpoint. UseRequestTimeouts
Registrazione delle richieste W3C Fornisce il supporto per la registrazione di richieste e risposte HTTP nel formato W3C. UseW3CLogging
Memorizzazione nella cache delle risposte Offre il supporto per la memorizzazione delle risposte nella cache. UseResponseCaching
Compressione della risposta Offre il supporto per la compressione delle risposte. UseResponseCompression
Session Offre il supporto per la gestione delle sessioni utente. UseSession
File statici Offre il supporto per la gestione di file statici e l'esplorazione directory. UseStaticFiles, UseFileServer
WebSockets Abilita il protocollo WebSocket. UseWebSockets

Le sezioni seguenti illustrano la gestione delle richieste: routing, associazione di parametri e risposte.

Routing

Un oggetto configurato supporta e dove è un metodo HTTP con maiuscole e minuscole camel, ad WebApplicationesempio , Map{Verb}MapMethods o {Verb}:GetPostPutDelete

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.

Espressione 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 dell'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 QueryPrivateTodosfactory 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

Associazione di parametri

L'associazione di parametri è il processo di conversione dei dati delle richieste in parametri fortemente tipizzato espressi dai gestori di route. Un'origine di associazione determina la posizione da cui sono associati i parametri. Le origini di associazione possono essere esplicite o dedotte in base al metodo HTTP e al tipo di parametro.

Origini di associazione supportate:

  • Valori di route
  • Stringa di query
  • Header
  • Corpo (come JSON)
  • Servizi forniti dall'inserimento delle dipendenze
  • Custom

L'associazione dai valori del modulo non è supportata in modo nativo in .NET 6 e 7.

Il gestore di route seguente GET usa alcune di queste origini di associazione di parametri:

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();

app.MapGet("/{id}", (int id,
                     int page,
                     [FromHeader(Name = "X-CUSTOM-HEADER")] string customHeader,
                     Service service) => { });

class Service { }

Nella tabella seguente viene illustrata la relazione tra i parametri usati nell'esempio precedente e le origini di associazione associate.

Parameter Origine binding
id valore di route
page stringa di query
customHeader header
service Fornito dall'inserimento delle dipendenze

I metodi GETHTTP , HEAD, OPTIONSe DELETE non si associano in modo implicito dal corpo. Per eseguire l'associazione dal corpo (come JSON) per questi metodi HTTP, associare in modo esplicito[FromBody] o leggere da HttpRequest.

Il gestore di route POST di esempio seguente usa un'origine di associazione del corpo (come JSON) per il person parametro :

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapPost("/", (Person person) => { });

record Person(string Name, int Age);

I parametri negli esempi precedenti sono associati automaticamente dai dati della richiesta. Per illustrare la praticità fornita dall'associazione di parametri, i gestori di route seguenti illustrano come leggere i dati delle richieste direttamente dalla richiesta:

app.MapGet("/{id}", (HttpRequest request) =>
{
    var id = request.RouteValues["id"];
    var page = request.Query["page"];
    var customHeader = request.Headers["X-CUSTOM-HEADER"];

    // ...
});

app.MapPost("/", async (HttpRequest request) =>
{
    var person = await request.ReadFromJsonAsync<Person>();

    // ...
});

Associazione di parametri esplicita

Gli attributi possono essere usati per dichiarare in modo esplicito il percorso da cui sono associati i parametri.

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();


app.MapGet("/{id}", ([FromRoute] int id,
                     [FromQuery(Name = "p")] int page,
                     [FromServices] Service service,
                     [FromHeader(Name = "Content-Type")] string contentType) 
                     => {});

class Service { }

record Person(string Name, int Age);
Parameter Origine binding
id valore di route con il nome id
page stringa di query con il nome "p"
service Fornito dall'inserimento delle dipendenze
contentType intestazione con il nome "Content-Type"

Note

L'associazione dai valori del modulo non è supportata in modo nativo in .NET 6 e 7.

Associazione di parametri con inserimento delle dipendenze

L'associazione di parametri per api minime associa i parametri tramite l'inserimento delle dipendenze quando il tipo è configurato come servizio. Non è necessario applicare in modo esplicito l'attributo [FromServices] a un parametro. Nel codice seguente entrambe le azioni restituiscono l'ora:

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IDateTime, SystemDateTime>();

var app = builder.Build();

app.MapGet("/",   (               IDateTime dateTime) => dateTime.Now);
app.MapGet("/fs", ([FromServices] IDateTime dateTime) => dateTime.Now);
app.Run();

Parametri facoltativi

I parametri dichiarati nei gestori di route vengono considerati come obbligatori:

  • Se una richiesta corrisponde alla route, il gestore di route viene eseguito solo se nella richiesta vengono forniti tutti i parametri obbligatori.
  • Se non si specificano tutti i parametri obbligatori, viene generato un errore.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");

app.Run();
URI result
/products?pageNumber=3 3 restituiti
/products BadHttpRequestException: parametro obbligatorio "int pageNumber" non fornito dalla stringa di query.
/products/1 Errore HTTP 404, nessuna route corrispondente

Per rendere pageNumber facoltativo, definire il tipo come facoltativo o specificare un valore predefinito:

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

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

string ListProducts(int pageNumber = 1) => $"Requesting page {pageNumber}";

app.MapGet("/products2", ListProducts);

app.Run();
URI result
/products?pageNumber=3 3 restituiti
/products 1 restituito
/products2 1 restituito

Il valore predefinito e nullable precedente si applica a tutte le origini:

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

app.MapPost("/products", (Product? product) => { });

app.Run();

Il codice precedente chiama il metodo con un prodotto Null se non viene inviato alcun corpo della richiesta.

NOTA: se vengono forniti dati non validi e il parametro è nullable, il gestore di route non viene eseguito.

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

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

app.Run();
URI result
/products?pageNumber=3 3 ritornato
/products 1 ritornato
/products?pageNumber=two BadHttpRequestException: impossibile associare il parametro "Nullable<int> pageNumber" da "two".
/products/two Errore HTTP 404, nessuna route corrispondente

Per altre informazioni, vedere la sezione Errori di binding .

Tipi speciali

I tipi seguenti sono associati senza attributi espliciti:

  • HttpContext: contesto che contiene tutte le informazioni sulla richiesta o la risposta HTTP corrente:

    app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
    
  • HttpRequest e HttpResponse: la richiesta HTTP e la risposta HTTP:

    app.MapGet("/", (HttpRequest request, HttpResponse response) =>
        response.WriteAsync($"Hello World {request.Query["name"]}"));
    
  • CancellationToken: token di annullamento associato alla richiesta HTTP corrente:

    app.MapGet("/", async (CancellationToken cancellationToken) => 
        await MakeLongRunningRequestAsync(cancellationToken));
    
  • ClaimsPrincipal: l'utente associato alla richiesta, associato da HttpContext.User:

    app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
    

Associare il corpo della richiesta come o StreamPipeReader

Il corpo della richiesta può essere associato come o StreamPipeReader per supportare in modo efficiente gli scenari in cui l'utente deve elaborare i dati e:

  • Archiviare i dati nell'archivio BLOB o accodare i dati a un provider di code.
  • Elaborare i dati archiviati con un processo di lavoro o una funzione cloud.

Ad esempio, i dati potrebbero essere accodati all'archiviazione code di Azure o archiviati nell'archivio BLOB di Azure.

Il codice seguente implementa una coda in background:

using System.Text.Json;
using System.Threading.Channels;

namespace BackgroundQueueService;

class BackgroundQueue : BackgroundService
{
    private readonly Channel<ReadOnlyMemory<byte>> _queue;
    private readonly ILogger<BackgroundQueue> _logger;

    public BackgroundQueue(Channel<ReadOnlyMemory<byte>> queue,
                               ILogger<BackgroundQueue> logger)
    {
        _queue = queue;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await foreach (var dataStream in _queue.Reader.ReadAllAsync(stoppingToken))
        {
            try
            {
                var person = JsonSerializer.Deserialize<Person>(dataStream.Span)!;
                _logger.LogInformation($"{person.Name} is {person.Age} " +
                                       $"years and from {person.Country}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
            }
        }
    }
}

class Person
{
    public string Name { get; set; } = String.Empty;
    public int Age { get; set; }
    public string Country { get; set; } = String.Empty;
}

Il codice seguente associa il corpo della richiesta a un Streamoggetto :

app.MapPost("/register", async (HttpRequest req, Stream body,
                                 Channel<ReadOnlyMemory<byte>> queue) =>
{
    if (req.ContentLength is not null && req.ContentLength > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // We're not above the message size and we have a content length, or
    // we're a chunked request and we're going to read up to the maxMessageSize + 1. 
    // We add one to the message size so that we can detect when a chunked request body
    // is bigger than our configured max.
    var readSize = (int?)req.ContentLength ?? (maxMessageSize + 1);

    var buffer = new byte[readSize];

    // Read at least that many bytes from the body.
    var read = await body.ReadAtLeastAsync(buffer, readSize, throwOnEndOfStream: false);

    // We read more than the max, so this is a bad request.
    if (read > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // Attempt to send the buffer to the background queue.
    if (queue.Writer.TryWrite(buffer.AsMemory(0..read)))
    {
        return Results.Accepted();
    }

    // We couldn't accept the message since we're overloaded.
    return Results.StatusCode(StatusCodes.Status429TooManyRequests);
});

Il codice seguente mostra il file completo Program.cs :

using System.Threading.Channels;
using BackgroundQueueService;

var builder = WebApplication.CreateBuilder(args);
// The max memory to use for the upload endpoint on this instance.
var maxMemory = 500 * 1024 * 1024;

// The max size of a single message, staying below the default LOH size of 85K.
var maxMessageSize = 80 * 1024;

// The max size of the queue based on those restrictions
var maxQueueSize = maxMemory / maxMessageSize;

// Create a channel to send data to the background queue.
builder.Services.AddSingleton<Channel<ReadOnlyMemory<byte>>>((_) =>
                     Channel.CreateBounded<ReadOnlyMemory<byte>>(maxQueueSize));

// Create a background queue service.
builder.Services.AddHostedService<BackgroundQueue>();
var app = builder.Build();

// curl --request POST 'https://localhost:<port>/register' --header 'Content-Type: application/json' --data-raw '{ "Name":"Samson", "Age": 23, "Country":"Nigeria" }'
// curl --request POST "https://localhost:<port>/register" --header "Content-Type: application/json" --data-raw "{ \"Name\":\"Samson\", \"Age\": 23, \"Country\":\"Nigeria\" }"
app.MapPost("/register", async (HttpRequest req, Stream body,
                                 Channel<ReadOnlyMemory<byte>> queue) =>
{
    if (req.ContentLength is not null && req.ContentLength > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // We're not above the message size and we have a content length, or
    // we're a chunked request and we're going to read up to the maxMessageSize + 1. 
    // We add one to the message size so that we can detect when a chunked request body
    // is bigger than our configured max.
    var readSize = (int?)req.ContentLength ?? (maxMessageSize + 1);

    var buffer = new byte[readSize];

    // Read at least that many bytes from the body.
    var read = await body.ReadAtLeastAsync(buffer, readSize, throwOnEndOfStream: false);

    // We read more than the max, so this is a bad request.
    if (read > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // Attempt to send the buffer to the background queue.
    if (queue.Writer.TryWrite(buffer.AsMemory(0..read)))
    {
        return Results.Accepted();
    }

    // We couldn't accept the message since we're overloaded.
    return Results.StatusCode(StatusCodes.Status429TooManyRequests);
});

app.Run();
  • Durante la lettura dei dati, è Stream lo stesso oggetto di HttpRequest.Body.
  • Il corpo della richiesta non viene memorizzato nel buffer per impostazione predefinita. Dopo aver letto il corpo, non è riavvolgibile. Il flusso non può essere letto più volte.
  • e StreamPipeReader non sono utilizzabili al di fuori del gestore di azioni minimo perché i buffer sottostanti verranno eliminati o riutilizzati.

Caricamenti di file con IFormFile e IFormFileCollection

Il codice seguente usa IFormFile e IFormFileCollection per caricare il file:

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

app.MapGet("/", () => "Hello World!");

app.MapPost("/upload", async (IFormFile file) =>
{
    var tempFile = Path.GetTempFileName();
    app.Logger.LogInformation(tempFile);
    using var stream = File.OpenWrite(tempFile);
    await file.CopyToAsync(stream);
});

app.MapPost("/upload_many", async (IFormFileCollection myFiles) =>
{
    foreach (var file in myFiles)
    {
        var tempFile = Path.GetTempFileName();
        app.Logger.LogInformation(tempFile);
        using var stream = File.OpenWrite(tempFile);
        await file.CopyToAsync(stream);
    }
});

app.Run();

Le richieste di caricamento di file autenticate sono supportate tramite un'intestazione di autorizzazione, un certificato client o un'intestazionecookie.

Non è disponibile alcun supporto predefinito per antifalsificazione in ASP.NET Core in .NET 7. L'anticontraffazione è disponibile in ASP.NET Core in .NET 8 o versione successiva. Tuttavia, può essere implementata usando il IAntiforgery servizio .

Associare matrici e valori stringa da intestazioni e stringhe di query

Il codice seguente illustra l'associazione di stringhe di query a una matrice di tipi primitivi, matrici di stringhe e StringValues:

// Bind query string values to a primitive type array.
// GET  /tags?q=1&q=2&q=3
app.MapGet("/tags", (int[] q) =>
                      $"tag1: {q[0]} , tag2: {q[1]}, tag3: {q[2]}");

// Bind to a string array.
// GET /tags2?names=john&names=jack&names=jane
app.MapGet("/tags2", (string[] names) =>
            $"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

// Bind to StringValues.
// GET /tags3?names=john&names=jack&names=jane
app.MapGet("/tags3", (StringValues names) =>
            $"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

L'associazione di stringhe di query o valori di intestazione a una matrice di tipi complessi è supportata quando il tipo è TryParse stato implementato. Il codice seguente viene associato a una matrice di stringhe e restituisce tutti gli elementi con i tag specificati:

// GET /todoitems/tags?tags=home&tags=work
app.MapGet("/todoitems/tags", async (Tag[] tags, TodoDb db) =>
{
    return await db.Todos
        .Where(t => tags.Select(i => i.Name).Contains(t.Tag.Name))
        .ToListAsync();
});

Il codice seguente illustra il modello e l'implementazione necessaria TryParse :

public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }

    // This is an owned entity. 
    public Tag Tag { get; set; } = new();
}

[Owned]
public class Tag
{
    public string? Name { get; set; } = "n/a";

    public static bool TryParse(string? name, out Tag tag)
    {
        if (name is null)
        {
            tag = default!;
            return false;
        }

        tag = new Tag { Name = name };
        return true;
    }
}

Il codice seguente viene associato a una int matrice:

// GET /todoitems/query-string-ids?ids=1&ids=3
app.MapGet("/todoitems/query-string-ids", async (int[] ids, TodoDb db) =>
{
    return await db.Todos
        .Where(t => ids.Contains(t.Id))
        .ToListAsync();
});

Per testare il codice precedente, aggiungere l'endpoint seguente per popolare il database con Todo elementi:

// POST /todoitems/batch
app.MapPost("/todoitems/batch", async (Todo[] todos, TodoDb db) =>
{
    await db.Todos.AddRangeAsync(todos);
    await db.SaveChangesAsync();

    return Results.Ok(todos);
});

Usare uno strumento di test api come HttpRepl per passare i dati seguenti all'endpoint precedente:

[
    {
        "id": 1,
        "name": "Have Breakfast",
        "isComplete": true,
        "tag": {
            "name": "home"
        }
    },
    {
        "id": 2,
        "name": "Have Lunch",
        "isComplete": true,
        "tag": {
            "name": "work"
        }
    },
    {
        "id": 3,
        "name": "Have Supper",
        "isComplete": true,
        "tag": {
            "name": "home"
        }
    },
    {
        "id": 4,
        "name": "Have Snacks",
        "isComplete": true,
        "tag": {
            "name": "N/A"
        }
    }
]

Il codice seguente viene associato alla chiave X-Todo-Id di intestazione e restituisce gli Todo elementi con valori corrispondenti Id :

// GET /todoitems/header-ids
// The keys of the headers should all be X-Todo-Id with different values
app.MapGet("/todoitems/header-ids", async ([FromHeader(Name = "X-Todo-Id")] int[] ids, TodoDb db) =>
{
    return await db.Todos
        .Where(t => ids.Contains(t.Id))
        .ToListAsync();
});

Note

Quando si associa un oggetto string[] da una stringa di query, l'assenza di qualsiasi valore della stringa di query corrispondente genererà una matrice vuota anziché un valore Null.

Associazione di parametri per gli elenchi di argomenti con [AsParameters]

AsParametersAttribute consente l'associazione di parametri semplice ai tipi e non l'associazione di modelli complessi o ricorsivi.

Si consideri il seguente codice :

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.Select(x => new TodoItemDTO(x)).ToListAsync());

app.MapGet("/todoitems/{id}",
                             async (int Id, TodoDb Db) =>
    await Db.Todos.FindAsync(Id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());
// Remaining code removed for brevity.

Si consideri l'endpoint seguente GET :

app.MapGet("/todoitems/{id}",
                             async (int Id, TodoDb Db) =>
    await Db.Todos.FindAsync(Id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());

Per sostituire i parametri evidenziati precedenti, è possibile usare quanto segue struct :

struct TodoItemRequest
{
    public int Id { get; set; }
    public TodoDb Db { get; set; }
}

L'endpoint sottoposto a refactoring GET usa il precedente struct con l'attributo AsParameters :

app.MapGet("/ap/todoitems/{id}",
                                async ([AsParameters] TodoItemRequest request) =>
    await request.Db.Todos.FindAsync(request.Id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());

Il codice seguente mostra altri endpoint nell'app:

app.MapPost("/todoitems", async (TodoItemDTO Dto, TodoDb Db) =>
{
    var todoItem = new Todo
    {
        IsComplete = Dto.IsComplete,
        Name = Dto.Name
    };

    Db.Todos.Add(todoItem);
    await Db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});

app.MapPut("/todoitems/{id}", async (int Id, TodoItemDTO Dto, TodoDb Db) =>
{
    var todo = await Db.Todos.FindAsync(Id);

    if (todo is null) return Results.NotFound();

    todo.Name = Dto.Name;
    todo.IsComplete = Dto.IsComplete;

    await Db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/todoitems/{id}", async (int Id, TodoDb Db) =>
{
    if (await Db.Todos.FindAsync(Id) is Todo todo)
    {
        Db.Todos.Remove(todo);
        await Db.SaveChangesAsync();
        return Results.Ok(new TodoItemDTO(todo));
    }

    return Results.NotFound();
});

Per effettuare il refactoring degli elenchi di parametri vengono usate le classi seguenti:

class CreateTodoItemRequest
{
    public TodoItemDTO Dto { get; set; } = default!;
    public TodoDb Db { get; set; } = default!;
}

class EditTodoItemRequest
{
    public int Id { get; set; }
    public TodoItemDTO Dto { get; set; } = default!;
    public TodoDb Db { get; set; } = default!;
}

Il codice seguente illustra gli endpoint di refactoring che usano AsParameters e le classi e precedenti struct :

app.MapPost("/ap/todoitems", async ([AsParameters] CreateTodoItemRequest request) =>
{
    var todoItem = new Todo
    {
        IsComplete = request.Dto.IsComplete,
        Name = request.Dto.Name
    };

    request.Db.Todos.Add(todoItem);
    await request.Db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});

app.MapPut("/ap/todoitems/{id}", async ([AsParameters] EditTodoItemRequest request) =>
{
    var todo = await request.Db.Todos.FindAsync(request.Id);

    if (todo is null) return Results.NotFound();

    todo.Name = request.Dto.Name;
    todo.IsComplete = request.Dto.IsComplete;

    await request.Db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/ap/todoitems/{id}", async ([AsParameters] TodoItemRequest request) =>
{
    if (await request.Db.Todos.FindAsync(request.Id) is Todo todo)
    {
        request.Db.Todos.Remove(todo);
        await request.Db.SaveChangesAsync();
        return Results.Ok(new TodoItemDTO(todo));
    }

    return Results.NotFound();
});

Per sostituire i parametri precedenti, è possibile usare i tipi seguenti record :

record TodoItemRequest(int Id, TodoDb Db);
record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);

L'uso di con structAsParameters può essere più efficiente rispetto all'uso di un record tipo .

Codice di esempio completo nel repository AspNetCore.Docs.Samples .

Associazione personalizzata

Esistono tre modi per personalizzare l'associazione di parametri:

  1. Per le origini di associazione di route, query e intestazione, associare tipi personalizzati aggiungendo un metodo statico TryParse per il tipo.
  2. Controllare il processo di associazione implementando un BindAsync metodo su un tipo.
  3. Per gli scenari avanzati, implementare l'interfaccia IBindableFromHttpContext<TSelf> per fornire logica di associazione personalizzata direttamente da HttpContext.

TryParse

TryParse ha due API:

public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);

Il codice seguente viene visualizzato Point: 12.3, 10.1 con l'URI /map?Point=12.3,10.1:

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

// GET /map?Point=12.3,10.1
app.MapGet("/map", (Point point) => $"Point: {point.X}, {point.Y}");

app.Run();

public class Point
{
    public double X { get; set; }
    public double Y { get; set; }

    public static bool TryParse(string? value, IFormatProvider? provider,
                                out Point? point)
    {
        // Format is "(12.3,10.1)"
        var trimmedValue = value?.TrimStart('(').TrimEnd(')');
        var segments = trimmedValue?.Split(',',
                StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (segments?.Length == 2
            && double.TryParse(segments[0], out var x)
            && double.TryParse(segments[1], out var y))
        {
            point = new Point { X = x, Y = y };
            return true;
        }

        point = null;
        return false;
    }
}

BindAsync

BindAsync include le API seguenti:

public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);

Il codice seguente viene visualizzato SortBy:xyz, SortDirection:Desc, CurrentPage:99 con l'URI /products?SortBy=xyz&SortDir=Desc&Page=99:

using System.Reflection;

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

// GET /products?SortBy=xyz&SortDir=Desc&Page=99
app.MapGet("/products", (PagingData pageData) => $"SortBy:{pageData.SortBy}, " +
       $"SortDirection:{pageData.SortDirection}, CurrentPage:{pageData.CurrentPage}");

app.Run();

public class PagingData
{
    public string? SortBy { get; init; }
    public SortDirection SortDirection { get; init; }
    public int CurrentPage { get; init; } = 1;

    public static ValueTask<PagingData?> BindAsync(HttpContext context,
                                                   ParameterInfo parameter)
    {
        const string sortByKey = "sortBy";
        const string sortDirectionKey = "sortDir";
        const string currentPageKey = "page";

        Enum.TryParse<SortDirection>(context.Request.Query[sortDirectionKey],
                                     ignoreCase: true, out var sortDirection);
        int.TryParse(context.Request.Query[currentPageKey], out var page);
        page = page == 0 ? 1 : page;

        var result = new PagingData
        {
            SortBy = context.Request.Query[sortByKey],
            SortDirection = sortDirection,
            CurrentPage = page
        };

        return ValueTask.FromResult<PagingData?>(result);
    }
}

public enum SortDirection
{
    Default,
    Asc,
    Desc
}

Associazione di parametri personalizzata con IBindableFromHttpContext

ASP.NET Core fornisce il supporto per l'associazione di parametri personalizzati nelle API minime usando l'interfaccia IBindableFromHttpContext<TSelf> . Questa interfaccia, introdotta con i membri astratti statici di C# 11, consente di creare tipi che possono essere associati da un contesto HTTP direttamente nei parametri del gestore di route.

public interface IBindableFromHttpContext<TSelf>
    where TSelf : class, IBindableFromHttpContext<TSelf>
{
    static abstract ValueTask<TSelf?> BindAsync(HttpContext context, ParameterInfo parameter);
}

Implementando l'interfaccia IBindableFromHttpContext<TSelf> , è possibile creare tipi personalizzati che gestiscono la propria logica di associazione da HttpContext. Quando un gestore di route include un parametro di questo tipo, il framework chiama automaticamente il metodo BindAsync statico per creare l'istanza:

using CustomBindingExample;

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

app.UseHttpsRedirection();

app.MapGet("/", () => "Hello, IBindableFromHttpContext example!");

app.MapGet("/custom-binding", (CustomBoundParameter param) =>
{
    return $"Value from custom binding: {param.Value}";
});

app.MapGet("/combined/{id}", (int id, CustomBoundParameter param) =>
{
    return $"ID: {id}, Custom Value: {param.Value}";
});

Di seguito è riportato un esempio di implementazione di un parametro personalizzato che viene associato da un'intestazione HTTP:

using System.Reflection;

namespace CustomBindingExample;

public class CustomBoundParameter : IBindableFromHttpContext<CustomBoundParameter>
{
    public string Value { get; init; } = default!;

    public static ValueTask<CustomBoundParameter?> BindAsync(HttpContext context, ParameterInfo parameter)
    {
        // Custom binding logic here
        // This example reads from a custom header
        var value = context.Request.Headers["X-Custom-Header"].ToString();
        
        // If no header was provided, you could fall back to a query parameter
        if (string.IsNullOrEmpty(value))
        {
            value = context.Request.Query["customValue"].ToString();
        }
        
        return ValueTask.FromResult<CustomBoundParameter?>(new CustomBoundParameter 
        {
            Value = value
        });
    }
}

È anche possibile implementare la convalida all'interno della logica di associazione personalizzata:

app.MapGet("/validated", (ValidatedParameter param) =>
{
    if (string.IsNullOrEmpty(param.Value))
    {
        return Results.BadRequest("Value cannot be empty");
    }
    
    return Results.Ok($"Validated value: {param.Value}");
});

Visualizzare o scaricare il codice di esempio (come scaricare)

Errori di associazione

Quando l'associazione non riesce, il framework registra un messaggio di debug e restituisce vari codici di stato al client a seconda della modalità di errore.

Modalità errore Tipo di parametro Nullable Origine binding Codice di stato
{ParameterType}.TryParse restituisce false yes route/query/header 400
{ParameterType}.BindAsync restituisce null yes custom 400
{ParameterType}.BindAsync getta non importa custom 500
Errore di deserializzazione del corpo JSON non importa body 400
Tipo di contenuto errato (non application/json) non importa body 415

Precedenza di binding

Regole per determinare un'origine di associazione da un parametro:

  1. Attributo esplicito definito per il parametro (attributi From*) nell'ordine seguente:
    1. Valori di route: [FromRoute]
    2. Stringa di query: [FromQuery]
    3. Intestazione: [FromHeader]
    4. Corpo: [FromBody]
    5. Servizio: [FromServices]
    6. Valori dei parametri: [AsParameters]
  2. Tipi speciali
    1. HttpContext
    2. HttpRequest (HttpContext.Request)
    3. HttpResponse (HttpContext.Response)
    4. ClaimsPrincipal (HttpContext.User)
    5. CancellationToken (HttpContext.RequestAborted)
    6. IFormFileCollection (HttpContext.Request.Form.Files)
    7. IFormFile (HttpContext.Request.Form.Files[paramName])
    8. Stream (HttpContext.Request.Body)
    9. PipeReader (HttpContext.Request.BodyReader)
  3. Il tipo di parametro ha un metodo statico BindAsync valido.
  4. Il tipo di parametro è una stringa o ha un metodo statico TryParse valido.
    1. Se il nome del parametro esiste nel modello di route. app.Map("/todo/{id}", (int id) => {}); In idè associato dalla route.
    2. Associato dalla stringa di query.
  5. Se il tipo di parametro è un servizio fornito dall'inserimento delle dipendenze, usa tale servizio come origine.
  6. Il parametro proviene dal corpo.

Configurare le opzioni di deserializzazione JSON per l'associazione del corpo

L'origine di associazione del corpo usa System.Text.Json per la deserializzazione. Non è possibile modificare questa impostazione predefinita, ma è possibile configurare le opzioni di serializzazione e deserializzazione JSON.

Configurare le opzioni di deserializzazione JSON a livello globale

Le opzioni applicabili a livello globale per un'app possono essere configurate richiamando ConfigureHttpJsonOptions. L'esempio seguente include campi pubblici e formatta l'output JSON.

var builder = WebApplication.CreateBuilder(args);

builder.Services.ConfigureHttpJsonOptions(options => {
    options.SerializerOptions.WriteIndented = true;
    options.SerializerOptions.IncludeFields = true;
});

var app = builder.Build();

app.MapPost("/", (Todo todo) => {
    if (todo is not null) {
        todo.Name = todo.NameField;
    }
    return todo;
});

app.Run();

class Todo {
    public string? Name { get; set; }
    public string? NameField;
    public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
//    "name":"Walk dog",
//    "nameField":"Walk dog",
//    "isComplete":false
// }

Poiché il codice di esempio configura sia la serializzazione che la deserializzazione, può leggere NameField e includere NameField nel codice JSON di output.

Configurare le opzioni di deserializzazione JSON per un endpoint

ReadFromJsonAsync dispone di overload che accettano un JsonSerializerOptions oggetto . L'esempio seguente include campi pubblici e formatta l'output JSON.

using System.Text.Json;

var app = WebApplication.Create();

var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) { 
    IncludeFields = true, 
    WriteIndented = true
};

app.MapPost("/", async (HttpContext context) => {
    if (context.Request.HasJsonContentType()) {
        var todo = await context.Request.ReadFromJsonAsync<Todo>(options);
        if (todo is not null) {
            todo.Name = todo.NameField;
        }
        return Results.Ok(todo);
    }
    else {
        return Results.BadRequest();
    }
});

app.Run();

class Todo
{
    public string? Name { get; set; }
    public string? NameField;
    public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
//    "name":"Walk dog",
//    "isComplete":false
// }

Poiché il codice precedente applica le opzioni personalizzate solo alla deserializzazione, il codice JSON di output esclude NameField.

Leggere il corpo della richiesta

Leggere il corpo della richiesta direttamente usando un HttpContext parametro o HttpRequest :

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

app.MapPost("/uploadstream", async (IConfiguration config, HttpRequest request) =>
{
    var filePath = Path.Combine(config["StoredFilesPath"], Path.GetRandomFileName());

    await using var writeStream = File.Create(filePath);
    await request.BodyReader.CopyToAsync(writeStream);
});

app.Run();

Il codice precedente:

  • Accede al corpo della richiesta usando HttpRequest.BodyReader.
  • Copia il corpo della richiesta in un file locale.

Responses

I gestori di route supportano i tipi di valori restituiti seguenti:

  1. IResult based : include Task<IResult> e ValueTask<IResult>
  2. string - Questo include Task<string> e ValueTask<string>
  3. T (Qualsiasi altro tipo) - Include Task<T> e ValueTask<T>
Valore restituito Behavior Content-Type
IResult Il framework chiama IResult.ExecuteAsync Deciso dall'implementazione IResult
string Il framework scrive la stringa direttamente nella risposta text/plain
T (Qualsiasi altro tipo) Il framework JSON serializza la risposta application/json

Per una guida più approfondita ai valori restituiti del gestore di route, vedere Creare risposte nelle applicazioni API minime

Valori restituiti di esempio

valori restituiti di stringa

app.MapGet("/hello", () => "Hello World");

Valori restituiti JSON

app.MapGet("/hello", () => new { Message = "Hello World" });

Restituisci TypedResults

Il codice seguente restituisce un oggetto TypedResults:

app.MapGet("/hello", () => TypedResults.Ok(new Message() {  Text = "Hello World!" }));

La restituzione di è preferibile TypedResultsResultsper restituire . Per altre informazioni, vedere TypedResults vs Results.

Valori restituiti IResult

app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));

Nell'esempio seguente vengono usati i tipi di risultati predefiniti per personalizzare la risposta:

app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
         await db.Todos.FindAsync(id) 
         is Todo todo
         ? Results.Ok(todo) 
         : Results.NotFound())
   .Produces<Todo>(StatusCodes.Status200OK)
   .Produces(StatusCodes.Status404NotFound);

JSON

app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));

Codice di stato personalizzato

app.MapGet("/405", () => Results.StatusCode(405));

Text

app.MapGet("/text", () => Results.Text("This is some text"));

Stream

var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () => 
{
    var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
    // Proxy the response as JSON
    return Results.Stream(stream, "application/json");
});

Per altri esempi, vedere Creare risposte nelle applicazioni API minime.

Redirect

app.MapGet("/old-path", () => Results.Redirect("/new-path"));

File

app.MapGet("/download", () => Results.File("myfile.text"));

Risultati predefiniti

Gli helper di risultati comuni esistono nelle Results classi statiche e TypedResults . La restituzione di è preferibile TypedResultsResultsper restituire . Per altre informazioni, vedere TypedResults vs Results.

Personalizzazione dei risultati

Le applicazioni possono controllare le risposte implementando un tipo personalizzato IResult . Il codice seguente è un esempio di tipo di risultato HTML:

using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
    public static IResult Html(this IResultExtensions resultExtensions, string html)
    {
        ArgumentNullException.ThrowIfNull(resultExtensions);

        return new HtmlResult(html);
    }
}

class HtmlResult : IResult
{
    private readonly string _html;

    public HtmlResult(string html)
    {
        _html = html;
    }

    public Task ExecuteAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Html;
        httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
        return httpContext.Response.WriteAsync(_html);
    }
}

È consigliabile aggiungere un metodo di estensione per Microsoft.AspNetCore.Http.IResultExtensions rendere questi risultati personalizzati più individuabili.

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

app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
    <head><title>miniHTML</title></head>
    <body>
        <h1>Hello World</h1>
        <p>The time on the server is {DateTime.Now:O}</p>
    </body>
</html>"));

app.Run();

Risultati tipizzato

L'interfaccia IResult può rappresentare i valori restituiti da API minime che non usano il supporto implicito per la serializzazione JSON dell'oggetto restituito alla risposta HTTP. La classe static Results viene usata per creare oggetti variabili IResult che rappresentano tipi diversi di risposte. Ad esempio, impostare il codice di stato della risposta o reindirizzare a un altro URL.

I tipi che implementano IResult sono pubblici, consentendo le asserzioni di tipo durante il test. Per esempio:

[TestClass()]
public class WeatherApiTests
{
    [TestMethod()]
    public void MapWeatherApiTest()
    {
        var result = WeatherApi.GetAllWeathers();
        Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
    }      
}

È possibile esaminare i tipi restituiti dei metodi corrispondenti nella classe Static TypedResults per trovare il tipo pubblico IResult corretto a cui eseguire il cast.

Per altri esempi, vedere Creare risposte nelle applicazioni API minime.

Filters

Vedere Filtri nelle app per le API minime

Authorization

Le route possono essere protette usando i criteri di autorizzazione. Questi valori possono essere dichiarati tramite l'attributo [Authorize] o usando il RequireAuthorization metodo :

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/auth", [Authorize] () => "This endpoint requires authorization.");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

Il codice precedente può essere scritto con RequireAuthorization:

app.MapGet("/auth", () => "This endpoint requires authorization")
   .RequireAuthorization();

L'esempio seguente usa l'autorizzazione basata su criteri:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/admin", [Authorize("AdminsOnly")] () => 
                             "The /admin endpoint is for admins only.");

app.MapGet("/admin2", () => "The /admin2 endpoint is for admins only.")
   .RequireAuthorization("AdminsOnly");

app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

Consentire agli utenti non autenticati di accedere a un endpoint

[AllowAnonymous] consente agli utenti non autenticati di accedere agli endpoint:

app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");


app.MapGet("/login2", () => "This endpoint also for all roles.")
   .AllowAnonymous();

CORS

Le route possono essere abilitate per CORS usando i criteri CORS. È possibile dichiarare CORS tramite l'attributo [EnableCors] o usando il RequireCors metodo . Gli esempi seguenti abilitano CORS:

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/",() => "Hello CORS!");

app.Run();
using Microsoft.AspNetCore.Cors;

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/cors", [EnableCors(MyAllowSpecificOrigins)] () => 
                           "This endpoint allows cross origin requests!");
app.MapGet("/cors2", () => "This endpoint allows cross origin requests!")
                     .RequireCors(MyAllowSpecificOrigins);

app.Run();

Per altre informazioni, vedere Abilitare le richieste tra le origini (CORS) in ASP.NET Core

Vedere anche

Questo documento:

Le API minime sono costituite da:

WebApplication

Il codice seguente viene generato da un modello ASP.NET Core:

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

app.MapGet("/", () => "Hello World!");

app.Run();

Il codice precedente può essere creato tramite dotnet new web la riga di comando o selezionando il modello Web vuoto in Visual Studio.

Il codice seguente crea un oggetto WebApplication (app) senza creare in modo esplicito un oggetto WebApplicationBuilder:

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run();

WebApplication.Create inizializza una nuova istanza della WebApplication classe con impostazioni predefinite preconfigurate.

Uso delle porte

Quando viene creata un'app Web con Visual Studio o dotnet new, viene creato un Properties/launchSettings.json file che specifica le porte a cui risponde l'app. Negli esempi di impostazione della porta che seguono, l'esecuzione dell'app da Visual Studio restituisce una finestra di dialogo Unable to connect to web server 'AppName'di errore. Eseguire i seguenti esempi di modifica della porta dalla riga di comando.

Le sezioni seguenti impostano la porta a cui risponde l'app.

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run("http://localhost:3000");

Nel codice precedente l'app risponde alla porta 3000.

Più porte

Nel codice seguente l'app risponde alla porta 3000 e 4000a .

var app = WebApplication.Create(args);

app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");

app.MapGet("/", () => "Hello World");

app.Run();

Impostare la porta dalla riga di comando

Il comando seguente rende l'app risponde alla porta 7777:

dotnet run --urls="https://localhost:7777"

Se l'endpoint Kestrel è configurato anche nel appsettings.json file, viene usato l'URL specificato dal appsettings.json file. Per altre informazioni, vedere Kestrel Configurazione dell'endpoint

Leggere la porta dall'ambiente

Il codice seguente legge la porta dall'ambiente:

var app = WebApplication.Create(args);

var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";

app.MapGet("/", () => "Hello World");

app.Run($"http://localhost:{port}");

Il modo migliore per impostare la porta dall'ambiente consiste nell'usare la ASPNETCORE_URLS variabile di ambiente, illustrata nella sezione seguente.

Impostare le porte tramite la variabile di ambiente ASPNETCORE_URLS

La ASPNETCORE_URLS variabile di ambiente è disponibile per impostare la porta:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS supporta più URL:

ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000

Ascoltare tutte le interfacce

Gli esempi seguenti illustrano l'ascolto su tutte le interfacce

http://*:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://*:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://+:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://+:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://0.0.0.0:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://0.0.0.0:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Ascoltare tutte le interfacce usando ASPNETCORE_URLS

Gli esempi precedenti possono usare ASPNETCORE_URLS

ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005

Specificare HTTPS con il certificato di sviluppo

var app = WebApplication.Create(args);

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Per altre informazioni sul certificato di sviluppo, vedere Considerare attendibile il certificato di sviluppo HTTPS di ASP.NET Core in Windows e macOS.

Specificare HTTPS usando un certificato personalizzato

Le sezioni seguenti illustrano come specificare il certificato personalizzato usando il appsettings.json file e tramite la configurazione.

Specificare il certificato personalizzato con appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Kestrel": {
    "Certificates": {
      "Default": {
        "Path": "cert.pem",
        "KeyPath": "key.pem"
      }
    }
  }
}

Specificare il certificato personalizzato tramite la configurazione

var builder = WebApplication.CreateBuilder(args);

// Configure the cert and the key
builder.Configuration["Kestrel:Certificates:Default:Path"] = "cert.pem";
builder.Configuration["Kestrel:Certificates:Default:KeyPath"] = "key.pem";

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Usare le API del certificato

using System.Security.Cryptography.X509Certificates;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
    options.ConfigureHttpsDefaults(httpsOptions =>
    {
        var certPath = Path.Combine(builder.Environment.ContentRootPath, "cert.pem");
        var keyPath = Path.Combine(builder.Environment.ContentRootPath, "key.pem");

        httpsOptions.ServerCertificate = X509Certificate2.CreateFromPemFile(certPath, 
                                         keyPath);
    });
});

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Leggere l'ambiente

var app = WebApplication.Create(args);

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/oops");
}

app.MapGet("/", () => "Hello World");
app.MapGet("/oops", () => "Oops! An error happened.");

app.Run();

Per ulteriori informazioni sull'uso dell'ambiente, vedere ASP.NET Core ambienti di runtime

Configuration

Il codice seguente legge dal sistema di configurazione:

var app = WebApplication.Create(args);

var message = app.Configuration["HelloKey"] ?? "Hello";

app.MapGet("/", () => message);

app.Run();

Per altre informazioni, vedere Configurazione in ASP.NET Core

Logging

Il codice seguente scrive un messaggio all'avvio dell'applicazione di accesso:

var app = WebApplication.Create(args);

app.Logger.LogInformation("The app started");

app.MapGet("/", () => "Hello World");

app.Run();

Per altre informazioni, vedere Registrazione in .NET e ASP.NET Core

Accedere al contenitore di inserimento delle dipendenze

Il codice seguente illustra come ottenere servizi dal contenitore di inserimento delle dipendenze durante l'avvio dell'applicazione:


var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddScoped<SampleService>();

var app = builder.Build();

app.MapControllers();

using (var scope = app.Services.CreateScope())
{
    var sampleService = scope.ServiceProvider.GetRequiredService<SampleService>();
    sampleService.DoSomething();
}

app.Run();

Per altre informazioni, vedere Inserimento di dipendenze in ASP.NET Core.

WebApplicationBuilder

Questa sezione contiene codice di esempio che usa WebApplicationBuilder.

Modificare la radice del contenuto, il nome dell'applicazione e l'ambiente

Il codice seguente imposta la radice del contenuto, il nome dell'applicazione e l'ambiente:

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    ApplicationName = typeof(Program).Assembly.FullName,
    ContentRootPath = Directory.GetCurrentDirectory(),
    EnvironmentName = Environments.Staging,
    WebRootPath = "customwwwroot"
});

Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");

var app = builder.Build();

WebApplication.CreateBuilder inizializza una nuova istanza della classe WebApplicationBuilder con valori predefiniti preconfigurati.

Per altre informazioni, vedere ASP.NET Panoramica dei concetti fondamentali di base

Modificare la radice del contenuto, il nome dell'app e l'ambiente in base alle variabili di ambiente o alla riga di comando

La tabella seguente illustra la variabile di ambiente e l'argomento della riga di comando usati per modificare la radice del contenuto, il nome dell'app e l'ambiente:

feature Variabile di ambiente Argomento della riga di comando
Nome applicazione ASPNETCORE_APPLICATIONNAME --applicationName
Nome dell'ambiente ASPNETCORE_ENVIRONMENT --environment
Radice del contenuto ASPNETCORE_CONTENTROOT --contentRoot

Aggiungere provider di configurazione

L'esempio seguente aggiunge il provider di configurazione INI:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddIniFile("appsettings.ini");

var app = builder.Build();

Per informazioni dettagliate, vedere Provider di configurazione file in Configurazione in ASP.NET Core.

Leggere la configurazione

Per impostazione predefinita, la WebApplicationBuilder configurazione legge da più origini, tra cui:

  • appSettings.json e appSettings.{environment}.json
  • Variabili di ambiente
  • Riga di comando

Per un elenco completo delle origini di configurazione lette, vedere Configurazione predefinita in Configurazione in ASP.NET Core

Il codice seguente legge HelloKey dalla configurazione e visualizza il valore nell'endpoint / . Se il valore di configurazione è Null, "Hello" viene assegnato a message:

var builder = WebApplication.CreateBuilder(args);

var message = builder.Configuration["HelloKey"] ?? "Hello";

var app = builder.Build();

app.MapGet("/", () => message);

app.Run();

Leggere l'ambiente

var builder = WebApplication.CreateBuilder(args);

var message = builder.Configuration["HelloKey"] ?? "Hello";

var app = builder.Build();

app.MapGet("/", () => message);

app.Run();

Aggiungere provider di registrazione

var builder = WebApplication.CreateBuilder(args);

// Configure JSON logging to the console.
builder.Logging.AddJsonConsole();

var app = builder.Build();

app.MapGet("/", () => "Hello JSON console!");

app.Run();

Aggiungere servizi

var builder = WebApplication.CreateBuilder(args);

// Add the memory cache services.
builder.Services.AddMemoryCache();

// Add a custom scoped service.
builder.Services.AddScoped<ITodoRepository, TodoRepository>();
var app = builder.Build();

Personalizzare IHostBuilder

È possibile accedere ai metodi di estensione esistenti in IHostBuilder usando la proprietà Host:

var builder = WebApplication.CreateBuilder(args);

// Wait 30 seconds for graceful shutdown.
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Personalizzare IWebHostBuilder

È possibile accedere ai metodi IWebHostBuilder di estensione in usando la proprietà WebApplicationBuilder.WebHost .

var builder = WebApplication.CreateBuilder(args);

// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();

var app = builder.Build();

app.MapGet("/", () => "Hello HTTP.sys");

app.Run();

Modificare la radice Web

Per impostazione predefinita, la radice Web è relativa alla radice del contenuto nella wwwroot cartella. La radice Web è la posizione in cui il middleware dei file statici cerca i file statici. La radice Web può essere modificata con WebHostOptions, la riga di comando o con il UseWebRoot metodo :

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    // Look for static files in webroot
    WebRootPath = "webroot"
});

var app = builder.Build();

app.Run();

Contenitore di inserimento delle dipendenze personalizzato

L'esempio seguente usa Autofac:

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));

var app = builder.Build();

Aggiungere middleware

Qualsiasi middleware core ASP.NET esistente può essere configurato in WebApplication:

var app = WebApplication.Create(args);

// Setup the file server to serve static files.
app.UseFileServer();

app.MapGet("/", () => "Hello World!");

app.Run();

Per altre informazioni, vedere middleware ASP.NET Core

Pagina delle eccezioni per gli sviluppatori

WebApplication.CreateBuilder inizializza una nuova istanza della WebApplicationBuilder classe con impostazioni predefinite preconfigurate. La pagina delle eccezioni per sviluppatori è abilitata nelle impostazioni predefinite preconfigurate. Quando il codice seguente viene eseguito nell'ambiente di sviluppo, spostarsi per eseguire / il rendering di una pagina descrittiva che mostra l'eccezione.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () =>
{
    throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});

app.Run();

Middleware di ASP.NET Core

La tabella seguente elenca alcuni dei middleware usati di frequente con API minime.

Middleware Description API
Authentication Offre il supporto dell'autenticazione. UseAuthentication
Authorization Fornisce il supporto per l'autorizzazione. UseAuthorization
CORS Configura la condivisione di risorse tra le origini (CORS). UseCors
Gestore eccezioni Gestisce globalmente le eccezioni generate dalla pipeline middleware. UseExceptionHandler
Intestazioni inoltrate Inoltra le intestazioni proxy nella richiesta corrente. UseForwardedHeaders
Reindirizzamento HTTPS Reindirizza tutte le richieste HTTP a HTTPS. UseHttpsRedirection
Protocollo HTTP Strict Transport Security (HSTS) Middleware di ottimizzazione della sicurezza che aggiunge un'intestazione della risposta speciale. UseHsts
Registrazione richieste Fornisce supporto per la registrazione di richieste e risposte HTTP. UseHttpLogging
Registrazione delle richieste W3C Fornisce il supporto per la registrazione di richieste e risposte HTTP nel formato W3C. UseW3CLogging
Memorizzazione nella cache delle risposte Offre il supporto per la memorizzazione delle risposte nella cache. UseResponseCaching
Compressione della risposta Offre il supporto per la compressione delle risposte. UseResponseCompression
Session Offre il supporto per la gestione delle sessioni utente. UseSession
File statici Offre il supporto per la gestione di file statici e l'esplorazione directory. UseStaticFiles, UseFileServer
WebSockets Abilita il protocollo WebSocket. UseWebSockets

Gestione delle richieste

Le sezioni seguenti illustrano il routing, l'associazione di parametri e le risposte.

Routing

Un oggetto configurato WebApplication supporta Map{Verb} e MapMethods:

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

Gestori di route

I gestori di route sono metodi eseguiti quando la route corrisponde. I gestori di route possono essere una funzione di qualsiasi forma, tra cui sincrona o asincrona. I gestori di route possono essere un'espressione lambda, una funzione locale, un metodo di istanza o un metodo statico.

Espressione 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 dell'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";
    }
}

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 endpoint 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 una richiesta viene effettuata 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.

Associazione di parametri

L'associazione di parametri è il processo di conversione dei dati delle richieste in parametri fortemente tipizzato espressi dai gestori di route. Un'origine di associazione determina la posizione da cui sono associati i parametri. Le origini di associazione possono essere esplicite o dedotte in base al metodo HTTP e al tipo di parametro.

Origini di associazione supportate:

  • Valori di route
  • Stringa di query
  • Header
  • Corpo (come JSON)
  • Servizi forniti dall'inserimento delle dipendenze
  • Custom

Note

L'associazione dai valori del modulo non è supportata in modo nativo in .NET.

Nell'esempio seguente il gestore di route GET usa alcune di queste origini di associazione di parametri:

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();

app.MapGet("/{id}", (int id,
                     int page,
                     [FromHeader(Name = "X-CUSTOM-HEADER")] string customHeader,
                     Service service) => { });

class Service { }

Nella tabella seguente viene illustrata la relazione tra i parametri usati nell'esempio precedente e le origini di associazione associate.

Parameter Origine binding
id valore di route
page stringa di query
customHeader header
service Fornito dall'inserimento delle dipendenze

I metodi GETHTTP , HEAD, OPTIONSe DELETE non si associano in modo implicito dal corpo. Per eseguire l'associazione dal corpo (come JSON) per questi metodi HTTP, associare in modo esplicito[FromBody] o leggere da HttpRequest.

Il gestore di route POST di esempio seguente usa un'origine di associazione del corpo (come JSON) per il person parametro :

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapPost("/", (Person person) => { });

record Person(string Name, int Age);

I parametri negli esempi precedenti sono associati automaticamente dai dati della richiesta. Per illustrare la praticità fornita dall'associazione di parametri, i gestori di route di esempio seguenti illustrano come leggere i dati delle richieste direttamente dalla richiesta:

app.MapGet("/{id}", (HttpRequest request) =>
{
    var id = request.RouteValues["id"];
    var page = request.Query["page"];
    var customHeader = request.Headers["X-CUSTOM-HEADER"];

    // ...
});

app.MapPost("/", async (HttpRequest request) =>
{
    var person = await request.ReadFromJsonAsync<Person>();

    // ...
});

Associazione di parametri esplicita

Gli attributi possono essere usati per dichiarare in modo esplicito il percorso da cui sono associati i parametri.

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();


app.MapGet("/{id}", ([FromRoute] int id,
                     [FromQuery(Name = "p")] int page,
                     [FromServices] Service service,
                     [FromHeader(Name = "Content-Type")] string contentType) 
                     => {});

class Service { }

record Person(string Name, int Age);
Parameter Origine binding
id valore di route con il nome id
page stringa di query con il nome "p"
service Fornito dall'inserimento delle dipendenze
contentType intestazione con il nome "Content-Type"

Note

L'associazione dai valori del modulo non è supportata in modo nativo in .NET.

Associazione di parametri con di inserimento delle dipendenze

L'associazione di parametri per api minime associa i parametri tramite l'inserimento delle dipendenze quando il tipo è configurato come servizio. Non è necessario applicare in modo esplicito l'attributo [FromServices] a un parametro. Nel codice seguente entrambe le azioni restituiscono l'ora:

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IDateTime, SystemDateTime>();

var app = builder.Build();

app.MapGet("/",   (               IDateTime dateTime) => dateTime.Now);
app.MapGet("/fs", ([FromServices] IDateTime dateTime) => dateTime.Now);
app.Run();

Parametri facoltativi

I parametri dichiarati nei gestori di route vengono considerati come obbligatori:

  • Se una richiesta corrisponde alla route, il gestore di route viene eseguito solo se nella richiesta vengono forniti tutti i parametri obbligatori.
  • Se non si specificano tutti i parametri obbligatori, viene generato un errore.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");

app.Run();
URI result
/products?pageNumber=3 3 restituiti
/products BadHttpRequestException: parametro obbligatorio "int pageNumber" non fornito dalla stringa di query.
/products/1 Errore HTTP 404, nessuna route corrispondente

Per rendere pageNumber facoltativo, definire il tipo come facoltativo o specificare un valore predefinito:

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

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

string ListProducts(int pageNumber = 1) => $"Requesting page {pageNumber}";

app.MapGet("/products2", ListProducts);

app.Run();
URI result
/products?pageNumber=3 3 restituiti
/products 1 restituito
/products2 1 restituito

Il valore predefinito e nullable precedente si applica a tutte le origini:

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

app.MapPost("/products", (Product? product) => { });

app.Run();

Il codice precedente chiama il metodo con un prodotto Null se non viene inviato alcun corpo della richiesta.

NOTA: se vengono forniti dati non validi e il parametro è nullable, il gestore di route non viene eseguito.

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

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

app.Run();
URI result
/products?pageNumber=3 3 ritornato
/products 1 ritornato
/products?pageNumber=two BadHttpRequestException: impossibile associare il parametro "Nullable<int> pageNumber" da "two".
/products/two Errore HTTP 404, nessuna route corrispondente

Per altre informazioni, vedere la sezione Errori di binding .

Tipi speciali

I tipi seguenti sono associati senza attributi espliciti:

  • HttpContext: contesto che contiene tutte le informazioni sulla richiesta o la risposta HTTP corrente:

    app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
    
  • HttpRequest e HttpResponse: la richiesta HTTP e la risposta HTTP:

    app.MapGet("/", (HttpRequest request, HttpResponse response) =>
        response.WriteAsync($"Hello World {request.Query["name"]}"));
    
  • CancellationToken: token di annullamento associato alla richiesta HTTP corrente:

    app.MapGet("/", async (CancellationToken cancellationToken) => 
        await MakeLongRunningRequestAsync(cancellationToken));
    
  • ClaimsPrincipal: l'utente associato alla richiesta, associato da HttpContext.User:

    app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
    

Associazione personalizzata

Esistono due modi per personalizzare l'associazione di parametri:

  1. Per le origini di associazione di route, query e intestazione, associare tipi personalizzati aggiungendo un metodo statico TryParse per il tipo.
  2. Controllare il processo di associazione implementando un BindAsync metodo su un tipo.

TryParse

TryParse ha due API:

public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);

Il codice seguente viene visualizzato Point: 12.3, 10.1 con l'URI /map?Point=12.3,10.1:

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

// GET /map?Point=12.3,10.1
app.MapGet("/map", (Point point) => $"Point: {point.X}, {point.Y}");

app.Run();

public class Point
{
    public double X { get; set; }
    public double Y { get; set; }

    public static bool TryParse(string? value, IFormatProvider? provider,
                                out Point? point)
    {
        // Format is "(12.3,10.1)"
        var trimmedValue = value?.TrimStart('(').TrimEnd(')');
        var segments = trimmedValue?.Split(',',
                StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (segments?.Length == 2
            && double.TryParse(segments[0], out var x)
            && double.TryParse(segments[1], out var y))
        {
            point = new Point { X = x, Y = y };
            return true;
        }

        point = null;
        return false;
    }
}

BindAsync

BindAsync include le API seguenti:

public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);

Il codice seguente viene visualizzato SortBy:xyz, SortDirection:Desc, CurrentPage:99 con l'URI /products?SortBy=xyz&SortDir=Desc&Page=99:

using System.Reflection;

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

// GET /products?SortBy=xyz&SortDir=Desc&Page=99
app.MapGet("/products", (PagingData pageData) => $"SortBy:{pageData.SortBy}, " +
       $"SortDirection:{pageData.SortDirection}, CurrentPage:{pageData.CurrentPage}");

app.Run();

public class PagingData
{
    public string? SortBy { get; init; }
    public SortDirection SortDirection { get; init; }
    public int CurrentPage { get; init; } = 1;

    public static ValueTask<PagingData?> BindAsync(HttpContext context,
                                                   ParameterInfo parameter)
    {
        const string sortByKey = "sortBy";
        const string sortDirectionKey = "sortDir";
        const string currentPageKey = "page";

        Enum.TryParse<SortDirection>(context.Request.Query[sortDirectionKey],
                                     ignoreCase: true, out var sortDirection);
        int.TryParse(context.Request.Query[currentPageKey], out var page);
        page = page == 0 ? 1 : page;

        var result = new PagingData
        {
            SortBy = context.Request.Query[sortByKey],
            SortDirection = sortDirection,
            CurrentPage = page
        };

        return ValueTask.FromResult<PagingData?>(result);
    }
}

public enum SortDirection
{
    Default,
    Asc,
    Desc
}

Errori di associazione

Quando l'associazione non riesce, il framework registra un messaggio di debug e restituisce vari codici di stato al client a seconda della modalità di errore.

Modalità errore Tipo di parametro Nullable Origine binding Codice di stato
{ParameterType}.TryParse restituisce false yes route/query/header 400
{ParameterType}.BindAsync restituisce null yes custom 400
{ParameterType}.BindAsync getta non importa custom 500
Errore di deserializzazione del corpo JSON non importa body 400
Tipo di contenuto errato (non application/json) non importa body 415

Precedenza di binding

Regole per determinare un'origine di associazione da un parametro:

  1. Attributo esplicito definito per il parametro (attributi From*) nell'ordine seguente:
    1. Valori di route: [FromRoute]
    2. Stringa di query: [FromQuery]
    3. Intestazione: [FromHeader]
    4. Corpo: [FromBody]
    5. Servizio: [FromServices]
  2. Tipi speciali
    1. HttpContext
    2. HttpRequest (HttpContext.Request)
    3. HttpResponse (HttpContext.Response)
    4. ClaimsPrincipal (HttpContext.User)
    5. CancellationToken (HttpContext.RequestAborted)
  3. Il tipo di parametro ha un metodo valido BindAsync .
  4. Il tipo di parametro è una stringa o ha un metodo valido TryParse .
    1. Se il nome del parametro esiste nel modello di route. app.Map("/todo/{id}", (int id) => {}); In idè associato dalla route.
    2. Associato dalla stringa di query.
  5. Se il tipo di parametro è un servizio fornito dall'inserimento delle dipendenze, usa tale servizio come origine.
  6. Il parametro proviene dal corpo.

Personalizzare l'associazione JSON

L'origine dell'associazione del corpo usa System.Text.Json per la de-serializzazione. Non è possibile modificare questa impostazione predefinita, ma l'associazione può essere personalizzata usando altre tecniche descritte in precedenza. Per personalizzare le opzioni del serializzatore JSON, usare codice simile al seguente:

using Microsoft.AspNetCore.Http.Json;

var builder = WebApplication.CreateBuilder(args);

// Configure JSON options.
builder.Services.Configure<JsonOptions>(options =>
{
    options.SerializerOptions.IncludeFields = true;
});

var app = builder.Build();

app.MapPost("/products", (Product product) => product);

app.Run();

class Product
{
    // These are public fields, not properties.
    public int Id;
    public string? Name;
}

Il codice precedente:

  • Configura sia le opzioni JSON predefinite di input che di output.
  • Restituisce il codice JSON seguente
    {
      "id": 1,
      "name": "Joe Smith"
    }
    
    Durante la registrazione
    {
      "Id": 1,
      "Name": "Joe Smith"
    }
    

Leggere il corpo della richiesta

Leggere il corpo della richiesta direttamente usando un HttpContext parametro o HttpRequest :

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

app.MapPost("/uploadstream", async (IConfiguration config, HttpRequest request) =>
{
    var filePath = Path.Combine(config["StoredFilesPath"], Path.GetRandomFileName());

    await using var writeStream = File.Create(filePath);
    await request.BodyReader.CopyToAsync(writeStream);
});

app.Run();

Il codice precedente:

  • Accede al corpo della richiesta usando HttpRequest.BodyReader.
  • Copia il corpo della richiesta in un file locale.

Responses

I gestori di route supportano i tipi di valori restituiti seguenti:

  1. IResult based : include Task<IResult> e ValueTask<IResult>
  2. string - Questo include Task<string> e ValueTask<string>
  3. T (Qualsiasi altro tipo) - Include Task<T> e ValueTask<T>
Valore restituito Behavior Content-Type
IResult Il framework chiama IResult.ExecuteAsync Deciso dall'implementazione IResult
string Il framework scrive la stringa direttamente nella risposta text/plain
T (Qualsiasi altro tipo) Il framework serializzerà la risposta in formato JSON application/json

Valori restituiti di esempio

valori restituiti di stringa

app.MapGet("/hello", () => "Hello World");

Valori restituiti JSON

app.MapGet("/hello", () => new { Message = "Hello World" });

Valori restituiti IResult

app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));

Nell'esempio seguente vengono usati i tipi di risultati predefiniti per personalizzare la risposta:

app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
         await db.Todos.FindAsync(id) 
         is Todo todo
         ? Results.Ok(todo) 
         : Results.NotFound())
   .Produces<Todo>(StatusCodes.Status200OK)
   .Produces(StatusCodes.Status404NotFound);
JSON
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
Codice di stato personalizzato
app.MapGet("/405", () => Results.StatusCode(405));
Text
app.MapGet("/text", () => Results.Text("This is some text"));
Stream
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () => 
{
    var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
    // Proxy the response as JSON
    return Results.Stream(stream, "application/json");
});

app.Run();
Redirect
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
File
app.MapGet("/download", () => Results.File("myfile.text"));

Risultati predefiniti

Gli helper di risultati comuni sono presenti nella Microsoft.AspNetCore.Http.Results classe statica.

Description Tipo di risposta Codice di stato API
Scrivere una risposta JSON con opzioni avanzate application/json 200 Results.Json
Scrivere una risposta JSON application/json 200 Results.Ok
Scrivere una risposta di testo text/plain (impostazione predefinita), configurabile 200 Results.Text
Scrivere la risposta come byte application/octet-stream (impostazione predefinita), configurabile 200 Results.Bytes
Scrivere un flusso di byte nella risposta application/octet-stream (impostazione predefinita), configurabile 200 Results.Stream
Trasmettere un file alla risposta per il download con l'intestazione content-disposition application/octet-stream (impostazione predefinita), configurabile 200 Results.File
Impostare il codice di stato su 404, con una risposta JSON facoltativa N/A 404 Results.NotFound
Impostare il codice di stato su 204 N/A 204 Results.NoContent
Impostare il codice di stato su 422, con una risposta JSON facoltativa N/A 422 Results.UnprocessableEntity
Impostare il codice di stato su 400, con una risposta JSON facoltativa N/A 400 Results.BadRequest
Impostare il codice di stato su 409, con una risposta JSON facoltativa N/A 409 Results.Conflict
Scrivere un oggetto JSON dei dettagli del problema nella risposta N/A 500 (impostazione predefinita), configurabile Results.Problem
Scrivere un oggetto JSON dei dettagli del problema nella risposta con errori di convalida N/A N/D, configurabile Results.ValidationProblem

Personalizzazione dei risultati

Le applicazioni possono controllare le risposte implementando un tipo personalizzato IResult . Il codice seguente è un esempio di tipo di risultato HTML:

using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
    public static IResult Html(this IResultExtensions resultExtensions, string html)
    {
        ArgumentNullException.ThrowIfNull(resultExtensions);

        return new HtmlResult(html);
    }
}

class HtmlResult : IResult
{
    private readonly string _html;

    public HtmlResult(string html)
    {
        _html = html;
    }

    public Task ExecuteAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Html;
        httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
        return httpContext.Response.WriteAsync(_html);
    }
}

È consigliabile aggiungere un metodo di estensione per Microsoft.AspNetCore.Http.IResultExtensions rendere questi risultati personalizzati più individuabili.

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

app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
    <head><title>miniHTML</title></head>
    <body>
        <h1>Hello World</h1>
        <p>The time on the server is {DateTime.Now:O}</p>
    </body>
</html>"));

app.Run();

Authorization

Le route possono essere protette usando i criteri di autorizzazione. Questi valori possono essere dichiarati tramite l'attributo [Authorize] o usando il RequireAuthorization metodo :

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/auth", [Authorize] () => "This endpoint requires authorization.");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

Il codice precedente può essere scritto con RequireAuthorization:

app.MapGet("/auth", () => "This endpoint requires authorization")
   .RequireAuthorization();

L'esempio seguente usa l'autorizzazione basata su criteri:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/admin", [Authorize("AdminsOnly")] () => 
                             "The /admin endpoint is for admins only.");

app.MapGet("/admin2", () => "The /admin2 endpoint is for admins only.")
   .RequireAuthorization("AdminsOnly");

app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

Consentire agli utenti non autenticati di accedere a un endpoint

[AllowAnonymous] consente agli utenti non autenticati di accedere agli endpoint:

app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");


app.MapGet("/login2", () => "This endpoint also for all roles.")
   .AllowAnonymous();

CORS

Le route possono essere abilitate per CORS usando i criteri CORS. È possibile dichiarare CORS tramite l'attributo [EnableCors] o usando il RequireCors metodo . Gli esempi seguenti abilitano CORS:

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/",() => "Hello CORS!");

app.Run();
using Microsoft.AspNetCore.Cors;

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/cors", [EnableCors(MyAllowSpecificOrigins)] () => 
                           "This endpoint allows cross origin requests!");
app.MapGet("/cors2", () => "This endpoint allows cross origin requests!")
                     .RequireCors(MyAllowSpecificOrigins);

app.Run();

Per altre informazioni, vedere Abilitare le richieste tra le origini (CORS) in ASP.NET Core

Vedere anche

Supporto openAPI nelle API minime