Partage via


Informations de référence rapides sur les API minimales

Note

Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 10 de cet article.

Warning

Cette version d’ASP.NET Core n’est plus prise en charge. Pour plus d’informations, consultez la stratégie de support .NET et .NET Core. Pour la version actuelle, consultez la version .NET 9 de cet article.

Ce document :

Les API minimales se composent des éléments suivants :

WebApplication

Le code suivant est généré par un modèle ASP.NET Core :

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

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

app.Run();

Le code précédent peut être créé via dotnet new web sur la ligne de commande ou en sélectionnant le modèle web Vide dans Visual Studio.

Le code suivant crée un WebApplication (app) sans créer explicitement de WebApplicationBuilder :

var app = WebApplication.Create(args);

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

app.Run();

WebApplication.Create initialise une nouvelle instance de la classe WebApplication avec les valeurs par défaut préconfigurées.

WebApplication ajoute automatiquement le middleware suivant dans les applications API minimales en fonction de certaines conditions :

  • UseDeveloperExceptionPage est ajouté en premier lorsque HostingEnvironment est "Development".
  • UseRouting est ajouté ensuite si le code utilisateur n’a pas déjà appelé UseRouting et s’il existe des points de terminaison configurés, par exemple app.MapGet.
  • UseEndpoints est ajouté à la fin du pipeline d’intergiciel si des points de terminaison sont configurés.
  • UseAuthentication est ajouté immédiatement après UseRouting, si le code utilisateur n’a pas déjà appelé UseAuthentication et si IAuthenticationSchemeProvider peut être détecté dans le fournisseur de services. IAuthenticationSchemeProvider est ajouté par défaut lors de l’utilisation de AddAuthentication, et les services sont détectés à l’aide de IServiceProviderIsService.
  • UseAuthorization est ajouté après, si le code utilisateur n’a pas déjà appelé UseAuthorization et si IAuthorizationHandlerProvider peut être détecté dans le fournisseur de services. IAuthorizationHandlerProvider est ajouté par défaut lors de l’utilisation de AddAuthorization, et les services sont détectés à l’aide de IServiceProviderIsService.
  • Les intergiciels et les points de terminaison configurés par l’utilisateur sont ajoutés entre UseRouting et UseEndpoints.

Le code suivant est effectivement ce qu’un intergiciel automatique ajouté à l’application produit :

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

Dans certains cas, la configuration de l’intergiciel par défaut n’est pas correcte pour l’application et exige une modification. Par exemple, UseCors doit être appelé avant UseAuthentication et UseAuthorization. L’application doit appeler UseAuthentication et UseAuthorization, si UseCors est appelé :

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

Si l’intergiciel doit être exécuté avant l’exécution de la correspondance d’itinéraire, appeler UseRouting et placer l’intergiciel avant l’appel à UseRouting. UseEndpoints n’est pas obligatoire dans ce cas, car il est automatiquement ajouté comme décrit précédemment :

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

app.UseRouting();

// other middleware and endpoints

Lors de l’ajout d’un intergiciel de terminal :

  • L’intergiciel doit être ajouté après UseEndpoints.
  • L’application doit appeler UseRouting et UseEndpoints pour que l’intergiciel de terminal puisse être placé à l’emplacement approprié.
app.UseRouting();

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

app.UseEndpoints(e => {});

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

Un intergiciel de terminal est un intergiciel qui s’exécute si aucun point de terminaison ne gère la requête.

Utilisation des ports

Lorsqu’une application web est créée avec Visual Studio ou dotnet new, un fichier Properties/launchSettings.json est créé et spécifie les ports auxquels l’application répond. Dans les exemples de paramètres de port qui suivent, l’exécution de l’application à partir de Visual Studio renvoie une boîte de dialogue d’erreur Unable to connect to web server 'AppName'. Visual Studio retourne une erreur, car il attend le port spécifié dans Properties/launchSettings.json, mais l’application utilise le port spécifié par app.Run("http://localhost:3000"). Exécutez les exemples de modification de port suivants à partir de la ligne de commande.

Les sections suivantes définissent le port auquel l’application répond.

var app = WebApplication.Create(args);

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

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

Dans le code précédent, l’application répond au port 3000.

Plusieurs ports

Dans le code suivant, l’application répond aux ports 3000 et 4000.

var app = WebApplication.Create(args);

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

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

app.Run();

Définir le port à partir de la ligne de commande

La commande suivante permet à l’application de répondre au port 7777 :

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

Si le point de terminaison Kestrel est également configuré dans le fichier appsettings.json, l’URL spécifiée par le fichier appsettings.json est utilisée. Pour plus d’informations, consultez Configuration du point de terminaison Kestrel

Lire le port à partir de l’environnement

Le code suivant lit le port à partir de l’environnement :

var app = WebApplication.Create(args);

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

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

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

La méthode recommandée pour définir le port à partir de l’environnement consiste à utiliser la variable d’environnement ASPNETCORE_URLS, comme indiqué dans la section suivante.

Définir les ports via la variable d’environnement ASPNETCORE_URLS

La variable d’environnement ASPNETCORE_URLS est disponible pour définir le port :

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS prend en charge plusieurs URL :

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

Écouter sur toutes les interfaces

Les exemples suivants illustrent l’écoute sur toutes les interfaces

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

Écoutez toutes les interfaces à l’aide d’ASPNETCORE_URLS

Les exemples précédents peuvent utiliser ASPNETCORE_URLS

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

Écoutez toutes les interfaces à l’aide d’ASPNETCORE_HTTPS_PORTS

Les exemples précédents peuvent utiliser ASPNETCORE_HTTPS_PORTS et ASPNETCORE_HTTP_PORTS.

ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000

Pour plus d’informations, consultez Configurer des points de terminaison pour le serveur web ASP.NET Core Kestrel

Spécifier HTTPS avec un certificat de développement

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations sur le certificat de développement, consultez Approuver le certificat de développement HTTPS ASP.NET Core sur Windows et macOS.

Spécifier HTTPS à l’aide d’un certificat personnalisé

Les sections suivantes montrent comment spécifier le certificat personnalisé à l’aide du fichier appsettings.json et via la configuration.

Spécifier le certificat personnalisé avec appsettings.json

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

Spécifier le certificat personnalisé via la configuration

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

Utiliser les API de certificat

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

Lire l’environnement

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

Pour plus d’informations sur l’utilisation de l’environnement, consultez ASP.NET environnements d’exécution Core

Configuration

Le code suivant est lu à partir du système de configuration :

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations, consultez Configuration dans ASP.NET Core

Logging

Le code suivant écrit un message dans le journal au démarrage de l’application :

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations, consultez Journalisation dans .NET et ASP.NET Core

Accéder au conteneur d’injection de dépendances (DI)

Le code suivant montre comment obtenir des services à partir du conteneur d’authentification unique au démarrage de l’application :


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

Le code suivant montre comment accéder aux clés d’accès à partir du conteneur d’injection de dépendances (DI) en utilisant l’attribut [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.";
}

Pour obtenir plus d’informations sur le DI, consultez Injection de dépendances dans ASP.NET Core.

WebApplicationBuilder

Cette section contient un exemple de code utilisant WebApplicationBuilder.

Modifier la racine du contenu, le nom de l’application et l’environnement

Le code suivant définit la racine du contenu, le nom de l’application et l’environnement :

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 initialise une nouvelle instance de la classe WebApplicationBuilder avec les valeurs par défaut préconfigurées.

Pour plus d’informations, consultez Vue d’ensemble des principes de base d’ASP.NET Core

Modifier la racine du contenu, le nom de l’application et l’environnement avec des variables d’environnement ou la ligne de commande

Le tableau suivant montre la variable d’environnement et l’argument de ligne de commande utilisés pour modifier la racine du contenu, le nom de l’application et l’environnement :

feature Variable d’environnement Argument de ligne de commande
Nom de l’application ASPNETCORE_APPLICATIONNAME --applicationName
Nom de l’environnement ASPNETCORE_ENVIRONMENT --environment
Racine du contenu ASPNETCORE_CONTENTROOT --contentRoot

Ajouter des fournisseurs de configuration

L’exemple suivant ajoute le fournisseur de configuration INI :

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

Pour plus d’informations, consultez Fournisseurs de configuration de fichiers dans Configuration dans ASP.NET Core.

Lire la configuration

Par défaut, WebApplicationBuilder lit la configuration à partir de plusieurs sources, notamment :

  • appSettings.json et appSettings.{environment}.json
  • Variables d'environnement
  • Ligne de commande

Pour obtenir la liste complète des sources de configuration lues, consultez Configuration par défaut dans Configuration dans ASP.NET Core.

Le code suivant lit HelloKey à partir de la configuration et affiche la valeur au niveau du point de terminaison /. Si la valeur de configuration est null, « Hello » est affecté à message :

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Lire l’environnement

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Ajouter des fournisseurs de journalisation

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

Ajouter des services

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

Personnaliser IHostBuilder

Les méthodes d’extension existantes sur IHostBuilder sont accessibles à l’aide de la propriété 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();

Personnaliser IWebHostBuilder

Les méthodes d’extension sur IWebHostBuilder sont accessibles à l’aide de la propriété 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();

Modifier la racine web

Par défaut, la racine web est relative à la racine de contenu dans le dossier wwwroot. La racine web est l’emplacement où le middleware de fichiers statiques recherche des fichiers statiques. La racine web peut être modifiée avec WebHostOptions, la ligne de commande ou avec la méthode UseWebRoot :

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

var app = builder.Build();

app.Run();

Conteneur d’injection de dépendances (DI) personnalisé

L’exemple suivant utilise 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();

Ajouter un intergiciel

Tout intergiciel ASP.NET Core existant peut être configuré sur WebApplication :

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations, consultez Intergiciel (middleware) ASP.NET Core

Page d’exceptions du développeur

WebApplication.CreateBuilder initialise une nouvelle instance de la classe WebApplicationBuilder avec les valeurs par défaut préconfigurées. La page d’exception du développeur est activée dans les valeurs par défaut préconfigurées. Lorsque le code suivant est exécuté dans l’environnement de développement, la navigation vers / présente une page conviviale qui affiche l’exception.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Run();

Intergiciel (middleware) ASP.NET Core

Le tableau suivant répertorie certains des intergiciels fréquemment utilisés avec des API minimales.

Middleware Description API
Authentication Prend en charge l’authentification. UseAuthentication
Authorization Fournit la prise en charge des autorisations. UseAuthorization
CORS Configure le partage des ressources cross-origin (CORS). UseCors
Gestionnaire d’exceptions Gère globalement les exceptions levées par le pipeline d’intergiciels. UseExceptionHandler
En-têtes transférés Transfère les en-têtes en proxy vers la requête actuelle. UseForwardedHeaders
HTTPS Redirection Redirige toutes les requêtes HTTP vers HTTPS. UseHttpsRedirection
HSTS (HTTP Strict Transport Security) Middleware d’amélioration de la sécurité qui ajoute un en-tête de réponse spécial. UseHsts
Journalisation des demandes Prend en charge la journalisation des requêtes et des réponses HTTP. UseHttpLogging
Délais d’expiration des requêtes Permet de configurer les délais d'attente des requêtes, par défaut global et par point d'extrémité. UseRequestTimeouts
Journalisation des requêtes W3C Prend en charge la journalisation des requêtes et des réponses HTTP au format W3C. UseW3CLogging
Mise en cache des réponses Prend en charge la mise en cache des réponses. UseResponseCaching
Compression de la réponse Prend en charge la compression des réponses. UseResponseCompression
Session Prend en charge la gestion des sessions utilisateur. UseSession
Fichiers statiques Prend en charge le traitement des fichiers statiques et l’exploration des répertoires. UseStaticFiles, UseFileServer
WebSockets Autorise le protocole WebSockets. UseWebSockets

Les sections suivantes couvrent la gestion des requêtes : routage, liaison de paramètres et réponses.

Routing

Une méthode HTTP configurée WebApplication prend en charge et Map{Verb}MapMethods se trouve {Verb} une méthode HTTP en cas de chameau comme Get, , PostPutou Delete:

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

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

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

app.Run();

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

Gestionnaires d’itinéraires

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

Expression lambda

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

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

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

app.MapGet("/", handler);

app.Run();

Fonction locale

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

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

app.MapGet("/", LocalFunction);

app.Run();

Méthode d’instance

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

var handler = new HelloHandler();

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

app.Run();

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

Méthode statique

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

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

app.Run();

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

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

Les API minimales n’ont pas besoin d’être situées à l’emplacement Program.cs.

Program.cs

using MinAPISeparateFile;

var builder = WebApplication.CreateSlimBuilder(args);

var app = builder.Build();

TodoEndpoints.Map(app);

app.Run();

TodoEndpoints.cs

namespace MinAPISeparateFile;

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

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

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

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

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

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

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

app.Run();

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

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

Noms de points de terminaison :

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

Paramètres de routage

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

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

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

app.Run();

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

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

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

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

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

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

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

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

app.Run();

Contraintes d'itinéraire

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

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

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

app.Run();

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

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

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

Groupes de routage

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

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

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

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


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

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

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

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

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

    return group;
}

Dans ce scénario, vous pouvez utiliser une adresse relative pour l’en-tête Location dans le résultat 201 Created :

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

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

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

La QueryPrivateTodosfabrique de filtre de point de terminaison est une fonction locale qui modifie les paramètres TodoDb du gestionnaire d’itinéraires pour permettre l’accès et le stockage de données todo privées.

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

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

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

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

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

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

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

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

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

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

/outer group filter
/inner group filter
MapGet filter

Liaison de paramètres

La liaison de paramètres est le processus de conversion des données de requête en paramètres fortement typés qui sont exprimés par les gestionnaires de routes. Une source de liaison détermine à partir d’où les paramètres sont liés. Les sources de liaison peuvent être explicites ou déduites en fonction de la méthode HTTP et du type de paramètre.

Sources de liaison prises en charge :

  • Valeurs de routage
  • Chaîne de requête
  • Header
  • Corps (JSON)
  • Valeurs de formulaire
  • Services fournis par l’injection de dépendances
  • Custom

Le gestionnaire de routage GET suivant utilise certaines de ces sources de liaison de paramètres :

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

Fonctionnalités de liaison de paramètres clés

  • Liaison explicite : utilisez des attributs tels que [FromRoute], , [FromQuery][FromHeader], [FromBody], , [FromForm]et [FromServices] pour spécifier explicitement des sources de liaison.
  • Liaison de données de formulaire : lier des valeurs de formulaire à l’aide d’attribut de [FromForm], prenant en charge IFormFile et IFormFileCollection pour le téléversement de fichiers.
  • Types complexes : lier des collections et des types complexes à partir de formulaires, de chaînes de requête et d’en-têtes.
  • Liaison personnalisée : implémentez une logique de liaison personnalisée à l'aide de TryParse, de BindAsync, ou de l’interface IBindableFromHttpContext<T>.
  • Paramètres facultatifs : prend en charge les types nullables et les valeurs par défaut pour les paramètres facultatifs.
  • Injection de dépendances : les paramètres sont automatiquement liés à partir des services enregistrés dans le conteneur DI.
  • Types spéciaux : liaison automatique pour HttpContext, , HttpRequestHttpResponseCancellationToken, ClaimsPrincipal, , Streamet .PipeReader

Pour en savoir plus: Pour plus d’informations sur la liaison de paramètres, notamment les scénarios avancés, la validation, la priorité de liaison et la résolution des problèmes, consultez Liaison de paramètre dans les applications API minimales.

Désérialisation Json+PipeReader dans des API minimales

À compter de .NET 10, les zones fonctionnelles suivantes de ASP.NET Core utilisent des surcharges basées JsonSerializer.DeserializeAsync sur PipeReader au lieu de Stream :

  • API minimales (liaison de paramètre, corps de la demande de lecture)
  • MVC (formateur d’entrée, modèle)
  • Méthodes HttpRequestJsonExtensions d’extension pour lire le corps de la requête en tant que JSON.

Pour la plupart des applications, une transition entre Stream et PipeReader offre de meilleures performances sans nécessiter de modifications dans le code de l’application. Toutefois, si votre application a un convertisseur personnalisé, le convertisseur peut ne pas gérer Utf8JsonReader.HasValueSequence correctement. Si ce n’est pas le cas, le résultat peut être des erreurs telles que ArgumentOutOfRangeException ou des données manquantes lors de la désérialisation. Vous disposez des options suivantes pour que votre convertisseur fonctionne sans erreurs liées à PipeReader.

Option 1 : Solution de contournement temporaire

La solution rapide consiste à revenir à l'utilisation de Stream sans la prise en charge de PipeReader. Pour implémenter cette option, définissez le commutateur AppContext « Microsoft.AspNetCore.UseStreamBasedJsonParsing » sur « true ». Nous vous recommandons de procéder uniquement comme solution de contournement temporaire et de mettre à jour votre convertisseur pour prendre en charge HasValueSequence dès que possible. Le commutateur peut être supprimé dans .NET 11. Son seul objectif était de donner aux développeurs le temps d’obtenir leurs convertisseurs mis à jour.

Option 2 : Un correctif rapide pour les implémentations JsonConverter

Pour ce correctif, vous allouez un tableau de données à partir du ReadOnlySequence. Cet exemple montre à quoi ressemblerait le code :

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

Option 3 : Un correctif plus compliqué mais plus performant

Ce correctif implique la configuration d’un chemin de code distinct pour la ReadOnlySequence gestion :

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

Pour plus d'informations, consultez la rubrique

Prise en charge de la validation dans les API minimales

L’activation de la validation permet au runtime ASP.NET Core d’effectuer des validations définies sur les éléments suivants :

  • Query
  • Header
  • Corps de la requête

Les validations sont définies à l’aide d’attributs dans l’espace de noms DataAnnotations.

Lorsqu’un paramètre à un point de terminaison d’API minimal est un type de classe ou d’enregistrement, les attributs de validation sont automatiquement appliqués. Par exemple:

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

Les développeurs personnalisent le comportement du système de validation en :

En cas d’échec de la validation, le runtime retourne une réponse 400 - Demande incorrecte avec des détails sur les erreurs de validation.

Activer la prise en charge intégrée de la validation pour les API minimales

Activez la prise en charge intégrée de la validation pour les API minimales en appelant la AddValidation méthode d’extension pour inscrire les services requis dans le conteneur de services de votre application :

builder.Services.AddValidation();

L’implémentation découvre automatiquement les types définis dans les gestionnaires d’API minimal ou en tant que types de base définis dans les gestionnaires d’API minimal. Un filtre de point de terminaison effectue la validation sur ces types et est ajouté pour chaque point de terminaison.

La validation peut être désactivée pour des points de terminaison spécifiques à l’aide de la DisableValidation méthode d’extension, comme dans l’exemple suivant :

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

Personnaliser les réponses d’erreur de validation à l’aide d’IProblemDetailsService

Personnalisez les réponses d’erreur à partir d’une logique de validation d’API minimale avec une IProblemDetailsService implémentation. Inscrivez ce service dans la collection de services de votre application pour permettre des réponses d’erreur plus cohérentes et spécifiques à l’utilisateur. La prise en charge de la validation d’API minimale a été introduite dans ASP.NET Core dans .NET 10.

Pour implémenter des réponses d’erreur de validation personnalisées :

  • Implémenter IProblemDetailsService ou utiliser l’implémentation par défaut
  • Inscrire le service dans le conteneur d’adresses di
  • Le système de validation utilise automatiquement le service inscrit pour mettre en forme les réponses d’erreur de validation

Pour plus d’informations sur la personnalisation des réponses d’erreur de validation avec IProblemDetailsService, consultez Créer des réponses dans les applications API minimales.

Responses

Les gestionnaires de routes prennent en charge les types de valeurs de retour suivants :

  1. Basé sur IResult : cela inclut Task<IResult> et ValueTask<IResult>
  2. string - Cela comprend Task<string> et ValueTask<string>
  3. T (Tout autre type) : cela inclut Task<T> et ValueTask<T>
Valeur retournée Behavior Content-Type
IResult Le framework appelle IResult.ExecuteAsync Décidé par l’implémentation IResult
string Le framework écrit la chaîne directement dans la réponse text/plain
T (Tout autre type) Le JSON du framework sérialise la réponse application/json

Pour obtenir un guide plus détaillé sur les valeurs de retour du gestionnaire de routes, consultez Créer des réponses dans les applications API minimales.

Exemples de valeurs de retour

Valeurs de retour string

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

Valeurs de retour JSON

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

Retour TypedResults

Le code suivant retourne TypedResults :

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

Le retour de TypedResults est préférable au retour Results. Pour plus d'informations, consultez TypedResults vs Results.

Valeurs de retour IResult

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

L’exemple suivant utilise les types de résultats intégrés pour personnaliser la réponse :

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

Code d’état personnalisé

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

Pour plus d’exemples, consultez Créer des réponses dans les applications API minimales.

Redirect

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

File

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

Résultats intégrés

Des assistants de résultats communs existent dans les classes Results et TypedResults statiques. Le retour de TypedResults est préférable au retour Results. Pour plus d'informations, consultez TypedResults vs Results.

Modification des en-têtes

Utilisez l’objet HttpResponse pour modifier les en-têtes de réponse :

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

Personnalisation des résultats

Les applications peuvent contrôler les réponses en implémentant un type IResult personnalisé. Le code suivant est un exemple de type de résultat 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);
    }
}

Nous vous recommandons d’ajouter une méthode d’extension à Microsoft.AspNetCore.Http.IResultExtensions pour rendre ces résultats personnalisés plus détectables.

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

Résultats typés

L’interface IResult peut représenter des valeurs retournées à partir d’API minimales qui n’utilisent pas la prise en charge implicite de la sérialisation JSON de l’objet retourné vers la réponse HTTP. La classe statique Results est utilisée pour créer différents objets IResult qui représentent différents types de réponses. Par exemple, la définition du code d’état de la réponse ou la redirection vers une autre URL.

Les types implémentant IResult sont publics, ce qui permet les assertions de type lors des tests. Par exemple:

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

Vous pouvez examiner les types de retour des méthodes correspondantes sur la classe statique TypedResults pour trouver le type IResult public approprié vers lequel effectuer la conversion.

Pour plus d’exemples, consultez Créer des réponses dans les applications API minimales.

Filters

Pour plus d’informations, consultez Filtres dans les applications API minimales.

Authorization

Les itinéraires peuvent être protégés à l’aide de stratégies d’autorisation. Celles-ci peuvent être déclarées via l’attribut [Authorize] ou à l’aide de la méthode RequireAuthorization :

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

Le code précédent peut être écrit avec RequireAuthorization :

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

L’exemple suivant utilise l’autorisation basée sur la stratégie :

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

Autoriser les utilisateurs non authentifiés à accéder à un point de terminaison

[AllowAnonymous] permet aux utilisateurs non authentifiés d’accéder aux points de terminaison :

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


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

CORS

Les itinéraires peuvent avoir CORS activé à l’aide de stratégies CORS. CORS peut être déclaré via l’attribut [EnableCors] ou à l’aide de la méthode RequireCors. Les exemples suivants activent 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();

Pour plus d’informations, consultez Activation les requêtes cross-origin (CORS) dans ASP.NET Core

ValidateScopes et ValidateOnBuild

ValidateScopes et ValidateOnBuild sont activés par défaut dans l’environnement de Développement, mais désactivés dans d’autres environnements.

Quand ValidateOnBuild est true, le conteneur d’injection de dépendances valide la configuration du service au moment de la génération. Si la configuration du service n’est pas valide, la génération échoue au démarrage de l’application, plutôt qu’au moment de l’exécution (runtime) lorsque le service est demandé.

Quand ValidateScopes est true, le conteneur d’injection de dépendances valide qu’un service délimité n’est pas résolu à partir de l’étendue racine. La résolution d’un service délimité à partir de l’étendue racine peut entraîner une fuite de mémoire, car le service est conservé en mémoire plus longtemps que l’étendue de la requête.

ValidateScopes et ValidateOnBuild ont la valeur false par défaut dans les modes autres que Développement pour des raisons de performances.

Le code suivant montre que ValidateScopes est activé par défaut en mode développement, mais désactivé en mode mise en production :

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

Le code suivant montre que ValidateOnBuild est activé par défaut en mode développement, mais désactivé en mode mise en production :

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

Le code suivant désactive ValidateScopes et ValidateOnBuild dans 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;
    });
}

Voir aussi

Ce document :

Les API minimales sont les suivantes :

WebApplication

Le code suivant est généré par un modèle ASP.NET Core :

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

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

app.Run();

Le code précédent peut être créé via dotnet new web sur la ligne de commande ou en sélectionnant le modèle web Vide dans Visual Studio.

Le code suivant crée un WebApplication (app) sans créer explicitement de WebApplicationBuilder :

var app = WebApplication.Create(args);

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

app.Run();

WebApplication.Create initialise une nouvelle instance de la classe WebApplication avec les valeurs par défaut préconfigurées.

WebApplication ajoute automatiquement le middleware suivant dans les applications API minimales en fonction de certaines conditions :

  • UseDeveloperExceptionPage est ajouté en premier lorsque HostingEnvironment est "Development".
  • UseRouting est ajouté ensuite si le code utilisateur n’a pas déjà appelé UseRouting et s’il existe des points de terminaison configurés, par exemple app.MapGet.
  • UseEndpoints est ajouté à la fin du pipeline d’intergiciel si des points de terminaison sont configurés.
  • UseAuthentication est ajouté immédiatement après UseRouting, si le code utilisateur n’a pas déjà appelé UseAuthentication et si IAuthenticationSchemeProvider peut être détecté dans le fournisseur de services. IAuthenticationSchemeProvider est ajouté par défaut lors de l’utilisation de AddAuthentication, et les services sont détectés à l’aide de IServiceProviderIsService.
  • UseAuthorization est ajouté après, si le code utilisateur n’a pas déjà appelé UseAuthorization et si IAuthorizationHandlerProvider peut être détecté dans le fournisseur de services. IAuthorizationHandlerProvider est ajouté par défaut lors de l’utilisation de AddAuthorization, et les services sont détectés à l’aide de IServiceProviderIsService.
  • Les intergiciels et les points de terminaison configurés par l’utilisateur sont ajoutés entre UseRouting et UseEndpoints.

Le code suivant est effectivement ce qu’un intergiciel automatique ajouté à l’application produit :

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

Dans certains cas, la configuration de l’intergiciel par défaut n’est pas correcte pour l’application et exige une modification. Par exemple, UseCors doit être appelé avant UseAuthentication et UseAuthorization. L’application doit appeler UseAuthentication et UseAuthorization, si UseCors est appelé :

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

Si l’intergiciel doit être exécuté avant l’exécution de la correspondance d’itinéraire, appeler UseRouting et placer l’intergiciel avant l’appel à UseRouting. UseEndpoints n’est pas obligatoire dans ce cas, car il est automatiquement ajouté comme décrit précédemment :

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

app.UseRouting();

// other middleware and endpoints

Lors de l’ajout d’un intergiciel de terminal :

  • L’intergiciel doit être ajouté après UseEndpoints.
  • L’application doit appeler UseRouting et UseEndpoints pour que l’intergiciel de terminal puisse être placé à l’emplacement approprié.
app.UseRouting();

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

app.UseEndpoints(e => {});

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

Un intergiciel de terminal est un intergiciel qui s’exécute si aucun point de terminaison ne gère la requête.

Utilisation des ports

Lorsqu’une application web est créée avec Visual Studio ou dotnet new, un fichier Properties/launchSettings.json est créé et spécifie les ports auxquels l’application répond. Dans les exemples de paramètres de port qui suivent, l’exécution de l’application à partir de Visual Studio renvoie une boîte de dialogue d’erreur Unable to connect to web server 'AppName'. Visual Studio retourne une erreur, car il attend le port spécifié dans Properties/launchSettings.json, mais l’application utilise le port spécifié par app.Run("http://localhost:3000"). Exécutez les exemples de modification de port suivants à partir de la ligne de commande.

Les sections suivantes définissent le port auquel l’application répond.

var app = WebApplication.Create(args);

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

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

Dans le code précédent, l’application répond au port 3000.

Plusieurs ports

Dans le code suivant, l’application répond aux ports 3000 et 4000.

var app = WebApplication.Create(args);

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

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

app.Run();

Définir le port à partir de la ligne de commande

La commande suivante permet à l’application de répondre au port 7777 :

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

Si le point de terminaison Kestrel est également configuré dans le fichier appsettings.json, l’URL spécifiée par le fichier appsettings.json est utilisée. Pour plus d’informations, consultez Configuration du point de terminaison Kestrel

Lire le port à partir de l’environnement

Le code suivant lit le port à partir de l’environnement :

var app = WebApplication.Create(args);

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

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

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

La méthode recommandée pour définir le port à partir de l’environnement consiste à utiliser la variable d’environnement ASPNETCORE_URLS, comme indiqué dans la section suivante.

Définir les ports via la variable d’environnement ASPNETCORE_URLS

La variable d’environnement ASPNETCORE_URLS est disponible pour définir le port :

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS prend en charge plusieurs URL :

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

Écouter sur toutes les interfaces

Les exemples suivants illustrent l’écoute sur toutes les interfaces

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

Écoutez toutes les interfaces à l’aide d’ASPNETCORE_URLS

Les exemples précédents peuvent utiliser ASPNETCORE_URLS

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

Écoutez toutes les interfaces à l’aide d’ASPNETCORE_HTTPS_PORTS

Les exemples précédents peuvent utiliser ASPNETCORE_HTTPS_PORTS et ASPNETCORE_HTTP_PORTS.

ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000

Pour plus d’informations, consultez Configurer des points de terminaison pour le serveur web ASP.NET Core Kestrel

Spécifier HTTPS avec un certificat de développement

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations sur le certificat de développement, consultez Approuver le certificat de développement HTTPS ASP.NET Core sur Windows et macOS.

Spécifier HTTPS à l’aide d’un certificat personnalisé

Les sections suivantes montrent comment spécifier le certificat personnalisé à l’aide du fichier appsettings.json et via la configuration.

Spécifier le certificat personnalisé avec appsettings.json

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

Spécifier le certificat personnalisé via la configuration

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

Utiliser les API de certificat

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

Lire l’environnement

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

Pour plus d’informations sur l’utilisation de l’environnement, consultez ASP.NET environnements d’exécution Core

Configuration

Le code suivant est lu à partir du système de configuration :

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations, consultez Configuration dans ASP.NET Core

Logging

Le code suivant écrit un message dans le journal au démarrage de l’application :

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations, consultez Journalisation dans .NET et ASP.NET Core

Accéder au conteneur d’injection de dépendances (DI)

Le code suivant montre comment obtenir des services à partir du conteneur d’authentification unique au démarrage de l’application :


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

Le code suivant montre comment accéder aux clés d’accès à partir du conteneur d’injection de dépendances (DI) en utilisant l’attribut [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.";
}

Pour obtenir plus d’informations sur le DI, consultez Injection de dépendances dans ASP.NET Core.

WebApplicationBuilder

Cette section contient un exemple de code utilisant WebApplicationBuilder.

Modifier la racine du contenu, le nom de l’application et l’environnement

Le code suivant définit la racine du contenu, le nom de l’application et l’environnement :

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 initialise une nouvelle instance de la classe WebApplicationBuilder avec les valeurs par défaut préconfigurées.

Pour plus d’informations, consultez Vue d’ensemble des principes de base d’ASP.NET Core

Modifier la racine du contenu, le nom de l’application et l’environnement avec des variables d’environnement ou la ligne de commande

Le tableau suivant montre la variable d’environnement et l’argument de ligne de commande utilisés pour modifier la racine du contenu, le nom de l’application et l’environnement :

feature Variable d’environnement Argument de ligne de commande
Nom de l’application ASPNETCORE_APPLICATIONNAME --applicationName
Nom de l’environnement ASPNETCORE_ENVIRONMENT --environment
Racine du contenu ASPNETCORE_CONTENTROOT --contentRoot

Ajouter des fournisseurs de configuration

L’exemple suivant ajoute le fournisseur de configuration INI :

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

Pour plus d’informations, consultez Fournisseurs de configuration de fichiers dans Configuration dans ASP.NET Core.

Lire la configuration

Par défaut, WebApplicationBuilder lit la configuration à partir de plusieurs sources, notamment :

  • appSettings.json et appSettings.{environment}.json
  • Variables d'environnement
  • Ligne de commande

Pour obtenir la liste complète des sources de configuration lues, consultez Configuration par défaut dans Configuration dans ASP.NET Core.

Le code suivant lit HelloKey à partir de la configuration et affiche la valeur au niveau du point de terminaison /. Si la valeur de configuration est null, « Hello » est affecté à message :

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Lire l’environnement

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Ajouter des fournisseurs de journalisation

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

Ajouter des services

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

Personnaliser IHostBuilder

Les méthodes d’extension existantes sur IHostBuilder sont accessibles à l’aide de la propriété 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();

Personnaliser IWebHostBuilder

Les méthodes d’extension sur IWebHostBuilder sont accessibles à l’aide de la propriété 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();

Modifier la racine web

Par défaut, la racine web est relative à la racine de contenu dans le dossier wwwroot. La racine web est l’emplacement où le middleware de fichiers statiques recherche des fichiers statiques. La racine web peut être modifiée avec WebHostOptions, la ligne de commande ou avec la méthode UseWebRoot :

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

var app = builder.Build();

app.Run();

Conteneur d’injection de dépendances (DI) personnalisé

L’exemple suivant utilise 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();

Ajouter un intergiciel

Tout intergiciel ASP.NET Core existant peut être configuré sur WebApplication :

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations, consultez Intergiciel (middleware) ASP.NET Core

Page d’exceptions du développeur

WebApplication.CreateBuilder initialise une nouvelle instance de la classe WebApplicationBuilder avec les valeurs par défaut préconfigurées. La page d’exception du développeur est activée dans les valeurs par défaut préconfigurées. Lorsque le code suivant est exécuté dans l’environnement de développement, la navigation vers / présente une page conviviale qui affiche l’exception.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Run();

Intergiciel (middleware) ASP.NET Core

Le tableau suivant répertorie certains des intergiciels fréquemment utilisés avec les API minimales.

Middleware Description API
Authentication Prend en charge l’authentification. UseAuthentication
Authorization Fournit la prise en charge des autorisations. UseAuthorization
CORS Configure le partage des ressources cross-origin (CORS). UseCors
Gestionnaire d’exceptions Gère globalement les exceptions levées par le pipeline d’intergiciels. UseExceptionHandler
En-têtes transférés Transfère les en-têtes en proxy vers la requête actuelle. UseForwardedHeaders
HTTPS Redirection Redirige toutes les requêtes HTTP vers HTTPS. UseHttpsRedirection
HSTS (HTTP Strict Transport Security) Middleware d’amélioration de la sécurité qui ajoute un en-tête de réponse spécial. UseHsts
Journalisation des demandes Prend en charge la journalisation des requêtes et des réponses HTTP. UseHttpLogging
Délais d’expiration des requêtes Permet de configurer les délais d'attente des requêtes, par défaut global et par point d'extrémité. UseRequestTimeouts
Journalisation des requêtes W3C Prend en charge la journalisation des requêtes et des réponses HTTP au format W3C. UseW3CLogging
Mise en cache des réponses Prend en charge la mise en cache des réponses. UseResponseCaching
Compression de la réponse Prend en charge la compression des réponses. UseResponseCompression
Session Prend en charge la gestion des sessions utilisateur. UseSession
Fichiers statiques Prend en charge le traitement des fichiers statiques et l’exploration des répertoires. UseStaticFiles, UseFileServer
WebSockets Autorise le protocole WebSockets. UseWebSockets

Les sections suivantes couvrent la gestion des requêtes : routage, liaison de paramètres et réponses.

Routing

Un WebApplication configuré prend en charge Map{Verb} et MapMethods, où {Verb} est une méthode HTTP en casse mixte, comme Get, Post, Put ou Delete :

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

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

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

app.Run();

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

Gestionnaires d’itinéraires

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

Expression lambda

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

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

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

app.MapGet("/", handler);

app.Run();

Fonction locale

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

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

app.MapGet("/", LocalFunction);

app.Run();

Méthode d’instance

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

var handler = new HelloHandler();

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

app.Run();

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

Méthode statique

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

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

app.Run();

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

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

Les API minimales n’ont pas besoin d’être situées à l’emplacement Program.cs.

Program.cs

using MinAPISeparateFile;

var builder = WebApplication.CreateSlimBuilder(args);

var app = builder.Build();

TodoEndpoints.Map(app);

app.Run();

TodoEndpoints.cs

namespace MinAPISeparateFile;

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

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

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

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

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

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

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

app.Run();

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

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

Noms de points de terminaison :

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

Paramètres de routage

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

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

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

app.Run();

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

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

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

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

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

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

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

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

app.Run();

Contraintes d'itinéraire

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

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

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

app.Run();

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

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

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

Groupes de routage

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

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

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

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


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

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

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

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

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

    return group;
}

Dans ce scénario, vous pouvez utiliser une adresse relative pour l’en-tête Location dans le résultat 201 Created :

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

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

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

La QueryPrivateTodosfabrique de filtre de point de terminaison est une fonction locale qui modifie les paramètres TodoDb du gestionnaire d’itinéraires pour permettre l’accès et le stockage de données todo privées.

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

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

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

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

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

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

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

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

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

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

/outer group filter
/inner group filter
MapGet filter

Liaison de paramètres

La liaison de paramètres est le processus de conversion des données de requête en paramètres fortement typés qui sont exprimés par les gestionnaires de routes. Une source de liaison détermine à partir d’où les paramètres sont liés. Les sources de liaison peuvent être explicites ou déduites en fonction de la méthode HTTP et du type de paramètre.

Sources de liaison prises en charge :

  • Valeurs de routage
  • Chaîne de requête
  • Header
  • Corps (JSON)
  • Valeurs de formulaire
  • Services fournis par l’injection de dépendances
  • Custom

Le gestionnaire de routage GET suivant utilise certaines de ces sources de liaison de paramètres :

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

Fonctionnalités de liaison de paramètres clés

  • Liaison explicite : utilisez des attributs tels que [FromRoute], , [FromQuery][FromHeader], [FromBody], , [FromForm]et [FromServices] pour spécifier explicitement des sources de liaison.
  • Liaison de données de formulaire : lier des valeurs de formulaire à l’aide d’attribut de [FromForm], prenant en charge IFormFile et IFormFileCollection pour le téléversement de fichiers.
  • Types complexes : lier des collections et des types complexes à partir de formulaires, de chaînes de requête et d’en-têtes.
  • Liaison personnalisée : implémentez une logique de liaison personnalisée à l'aide de TryParse, de BindAsync, ou de l’interface IBindableFromHttpContext<T>.
  • Paramètres facultatifs : prend en charge les types nullables et les valeurs par défaut pour les paramètres facultatifs.
  • Injection de dépendances : les paramètres sont automatiquement liés à partir des services enregistrés dans le conteneur DI.
  • Types spéciaux : liaison automatique pour HttpContext, , HttpRequestHttpResponseCancellationToken, ClaimsPrincipal, , Streamet .PipeReader

Pour en savoir plus: Pour plus d’informations sur la liaison de paramètres, notamment les scénarios avancés, la validation, la priorité de liaison et la résolution des problèmes, consultez Liaison de paramètre dans les applications API minimales.

Responses

Les gestionnaires de routes prennent en charge les types de valeurs de retour suivants :

  1. Basé sur IResult : cela inclut Task<IResult> et ValueTask<IResult>
  2. string - Cela comprend Task<string> et ValueTask<string>
  3. T (Tout autre type) : cela inclut Task<T> et ValueTask<T>
Valeur retournée Behavior Content-Type
IResult Le framework appelle IResult.ExecuteAsync Décidé par l’implémentation IResult
string Le framework écrit la chaîne directement dans la réponse text/plain
T (Tout autre type) Le JSON du framework sérialise la réponse application/json

Pour obtenir un guide plus détaillé sur les valeurs de retour du gestionnaire de routes, consultez Créer des réponses dans les applications API minimales.

Exemples de valeurs de retour

Valeurs de retour string

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

Valeurs de retour JSON

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

Retour TypedResults

Le code suivant retourne TypedResults :

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

Le retour de TypedResults est préférable au retour Results. Pour plus d'informations, consultez TypedResults vs Results.

Valeurs de retour IResult

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

L’exemple suivant utilise les types de résultats intégrés pour personnaliser la réponse :

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

Code d’état personnalisé

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

Pour plus d’exemples, consultez Créer des réponses dans les applications API minimales.

Redirect

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

File

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

Résultats intégrés

Des assistants de résultats communs existent dans les classes Results et TypedResults statiques. Le retour de TypedResults est préférable au retour Results. Pour plus d'informations, consultez TypedResults vs Results.

Modification des en-têtes

Utilisez l’objet HttpResponse pour modifier les en-têtes de réponse :

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

Personnalisation des résultats

Les applications peuvent contrôler les réponses en implémentant un type IResult personnalisé. Le code suivant est un exemple de type de résultat 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);
    }
}

Nous vous recommandons d’ajouter une méthode d’extension à Microsoft.AspNetCore.Http.IResultExtensions pour rendre ces résultats personnalisés plus détectables.

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

Résultats typés

L’interface IResult peut représenter des valeurs retournées par les API minimales qui n’utilisent pas la prise en charge implicite de la sérialisation JSON de l’objet retourné dans la réponse HTTP. La classe statique Results est utilisée pour créer différents objets IResult qui représentent différents types de réponses. Par exemple, la définition du code d’état de la réponse ou la redirection vers une autre URL.

Les types implémentant IResult sont publics, ce qui permet les assertions de type lors des tests. Par exemple:

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

Vous pouvez examiner les types de retour des méthodes correspondantes sur la classe statique TypedResults pour trouver le type IResult public approprié vers lequel effectuer la conversion.

Pour plus d’exemples, consultez Créer des réponses dans les applications API minimales.

Filters

Pour plus d’informations, consultez Filtres dans les applications API minimales.

Authorization

Les itinéraires peuvent être protégés à l’aide de stratégies d’autorisation. Celles-ci peuvent être déclarées via l’attribut [Authorize] ou à l’aide de la méthode RequireAuthorization :

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

Le code précédent peut être écrit avec RequireAuthorization :

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

L’exemple suivant utilise l’autorisation basée sur la stratégie :

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

Autoriser les utilisateurs non authentifiés à accéder à un point de terminaison

[AllowAnonymous] permet aux utilisateurs non authentifiés d’accéder aux points de terminaison :

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


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

CORS

Les itinéraires peuvent avoir CORS activé à l’aide de stratégies CORS. CORS peut être déclaré via l’attribut [EnableCors] ou à l’aide de la méthode RequireCors. Les exemples suivants activent 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();

Pour plus d’informations, consultez Activation les requêtes cross-origin (CORS) dans ASP.NET Core

ValidateScopes et ValidateOnBuild

ValidateScopes et ValidateOnBuild sont activés par défaut dans l’environnement de Développement, mais désactivés dans d’autres environnements.

Quand ValidateOnBuild est true, le conteneur d’injection de dépendances valide la configuration du service au moment de la génération. Si la configuration du service n’est pas valide, la génération échoue au démarrage de l’application, plutôt qu’au moment de l’exécution (runtime) lorsque le service est demandé.

Quand ValidateScopes est true, le conteneur d’injection de dépendances valide qu’un service délimité n’est pas résolu à partir de l’étendue racine. La résolution d’un service délimité à partir de l’étendue racine peut entraîner une fuite de mémoire, car le service est conservé en mémoire plus longtemps que l’étendue de la requête.

ValidateScopes et ValidateOnBuild ont la valeur false par défaut dans les modes autres que Développement pour des raisons de performances.

Le code suivant montre que ValidateScopes est activé par défaut en mode développement, mais désactivé en mode mise en production :

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

Le code suivant montre que ValidateOnBuild est activé par défaut en mode développement, mais désactivé en mode mise en production :

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

Le code suivant désactive ValidateScopes et ValidateOnBuild dans 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;
    });
}

Voir aussi

Ce document :

Les API minimales sont les suivantes :

WebApplication

Le code suivant est généré par un modèle ASP.NET Core :

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

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

app.Run();

Le code précédent peut être créé via dotnet new web sur la ligne de commande ou en sélectionnant le modèle web Vide dans Visual Studio.

Le code suivant crée un WebApplication (app) sans créer explicitement de WebApplicationBuilder :

var app = WebApplication.Create(args);

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

app.Run();

WebApplication.Create initialise une nouvelle instance de la classe WebApplication avec les valeurs par défaut préconfigurées.

WebApplication ajoute automatiquement le middleware suivant dans les applications API minimales en fonction de certaines conditions :

  • UseDeveloperExceptionPage est ajouté en premier lorsque HostingEnvironment est "Development".
  • UseRouting est ajouté ensuite si le code utilisateur n’a pas déjà appelé UseRouting et s’il existe des points de terminaison configurés, par exemple app.MapGet.
  • UseEndpoints est ajouté à la fin du pipeline d’intergiciel si des points de terminaison sont configurés.
  • UseAuthentication est ajouté immédiatement après UseRouting, si le code utilisateur n’a pas déjà appelé UseAuthentication et si IAuthenticationSchemeProvider peut être détecté dans le fournisseur de services. IAuthenticationSchemeProvider est ajouté par défaut lors de l’utilisation de AddAuthentication, et les services sont détectés à l’aide de IServiceProviderIsService.
  • UseAuthorization est ajouté après, si le code utilisateur n’a pas déjà appelé UseAuthorization et si IAuthorizationHandlerProvider peut être détecté dans le fournisseur de services. IAuthorizationHandlerProvider est ajouté par défaut lors de l’utilisation de AddAuthorization, et les services sont détectés à l’aide de IServiceProviderIsService.
  • Les intergiciels et les points de terminaison configurés par l’utilisateur sont ajoutés entre UseRouting et UseEndpoints.

Le code suivant est effectivement ce qu’un intergiciel automatique ajouté à l’application produit :

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

Dans certains cas, la configuration de l’intergiciel par défaut n’est pas correcte pour l’application et exige une modification. Par exemple, UseCors doit être appelé avant UseAuthentication et UseAuthorization. L’application doit appeler UseAuthentication et UseAuthorization, si UseCors est appelé :

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

Si l’intergiciel doit être exécuté avant l’exécution de la correspondance d’itinéraire, appeler UseRouting et placer l’intergiciel avant l’appel à UseRouting. UseEndpoints n’est pas obligatoire dans ce cas, car il est automatiquement ajouté comme décrit précédemment :

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

app.UseRouting();

// other middleware and endpoints

Lors de l’ajout d’un intergiciel de terminal :

  • L’intergiciel doit être ajouté après UseEndpoints.
  • L’application doit appeler UseRouting et UseEndpoints pour que l’intergiciel de terminal puisse être placé à l’emplacement approprié.
app.UseRouting();

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

app.UseEndpoints(e => {});

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

Un intergiciel de terminal est un intergiciel qui s’exécute si aucun point de terminaison ne gère la requête.

Utilisation des ports

Lorsqu’une application web est créée avec Visual Studio ou dotnet new, un fichier Properties/launchSettings.json est créé et spécifie les ports auxquels l’application répond. Dans les exemples de paramètres de port qui suivent, l’exécution de l’application à partir de Visual Studio renvoie une boîte de dialogue d’erreur Unable to connect to web server 'AppName'. Visual Studio retourne une erreur, car il attend le port spécifié dans Properties/launchSettings.json, mais l’application utilise le port spécifié par app.Run("http://localhost:3000"). Exécutez les exemples de modification de port suivants à partir de la ligne de commande.

Les sections suivantes définissent le port auquel l’application répond.

var app = WebApplication.Create(args);

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

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

Dans le code précédent, l’application répond au port 3000.

Plusieurs ports

Dans le code suivant, l’application répond aux ports 3000 et 4000.

var app = WebApplication.Create(args);

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

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

app.Run();

Définir le port à partir de la ligne de commande

La commande suivante permet à l’application de répondre au port 7777 :

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

Si le point de terminaison Kestrel est également configuré dans le fichier appsettings.json, l’URL spécifiée par le fichier appsettings.json est utilisée. Pour plus d’informations, consultez Configuration du point de terminaison Kestrel

Lire le port à partir de l’environnement

Le code suivant lit le port à partir de l’environnement :

var app = WebApplication.Create(args);

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

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

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

La méthode recommandée pour définir le port à partir de l’environnement consiste à utiliser la variable d’environnement ASPNETCORE_URLS, comme indiqué dans la section suivante.

Définir les ports via la variable d’environnement ASPNETCORE_URLS

La variable d’environnement ASPNETCORE_URLS est disponible pour définir le port :

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS prend en charge plusieurs URL :

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

Écouter sur toutes les interfaces

Les exemples suivants illustrent l’écoute sur toutes les interfaces

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

Écoutez toutes les interfaces à l’aide d’ASPNETCORE_URLS

Les exemples précédents peuvent utiliser ASPNETCORE_URLS

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

Écoutez toutes les interfaces à l’aide d’ASPNETCORE_HTTPS_PORTS

Les exemples précédents peuvent utiliser ASPNETCORE_HTTPS_PORTS et ASPNETCORE_HTTP_PORTS.

ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000

Pour plus d’informations, consultez Configurer des points de terminaison pour le serveur web ASP.NET Core Kestrel

Spécifier HTTPS avec un certificat de développement

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations sur le certificat de développement, consultez Approuver le certificat de développement HTTPS ASP.NET Core sur Windows et macOS.

Spécifier HTTPS à l’aide d’un certificat personnalisé

Les sections suivantes montrent comment spécifier le certificat personnalisé à l’aide du fichier appsettings.json et via la configuration.

Spécifier le certificat personnalisé avec appsettings.json

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

Spécifier le certificat personnalisé via la configuration

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

Utiliser les API de certificat

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

Lire l’environnement

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

Pour plus d’informations sur l’utilisation de l’environnement, consultez ASP.NET environnements d’exécution Core

Configuration

Le code suivant est lu à partir du système de configuration :

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations, consultez Configuration dans ASP.NET Core

Logging

Le code suivant écrit un message dans le journal au démarrage de l’application :

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations, consultez Journalisation dans .NET et ASP.NET Core

Accéder au conteneur d’injection de dépendances (DI)

Le code suivant montre comment obtenir des services à partir du conteneur d’authentification unique au démarrage de l’application :


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

Le code suivant montre comment accéder aux clés d’accès à partir du conteneur d’injection de dépendances (DI) en utilisant l’attribut [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.";
}

Pour obtenir plus d’informations sur le DI, consultez Injection de dépendances dans ASP.NET Core.

WebApplicationBuilder

Cette section contient un exemple de code utilisant WebApplicationBuilder.

Modifier la racine du contenu, le nom de l’application et l’environnement

Le code suivant définit la racine du contenu, le nom de l’application et l’environnement :

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 initialise une nouvelle instance de la classe WebApplicationBuilder avec les valeurs par défaut préconfigurées.

Pour plus d’informations, consultez Vue d’ensemble des principes de base d’ASP.NET Core

Modifier la racine du contenu, le nom de l’application et l’environnement avec des variables d’environnement ou la ligne de commande

Le tableau suivant montre la variable d’environnement et l’argument de ligne de commande utilisés pour modifier la racine du contenu, le nom de l’application et l’environnement :

feature Variable d’environnement Argument de ligne de commande
Nom de l’application ASPNETCORE_APPLICATIONNAME --applicationName
Nom de l’environnement ASPNETCORE_ENVIRONMENT --environment
Racine du contenu ASPNETCORE_CONTENTROOT --contentRoot

Ajouter des fournisseurs de configuration

L’exemple suivant ajoute le fournisseur de configuration INI :

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

Pour plus d’informations, consultez Fournisseurs de configuration de fichiers dans Configuration dans ASP.NET Core.

Lire la configuration

Par défaut, WebApplicationBuilder lit la configuration à partir de plusieurs sources, notamment :

  • appSettings.json et appSettings.{environment}.json
  • Variables d'environnement
  • Ligne de commande

Pour obtenir la liste complète des sources de configuration lues, consultez Configuration par défaut dans Configuration dans ASP.NET Core.

Le code suivant lit HelloKey à partir de la configuration et affiche la valeur au niveau du point de terminaison /. Si la valeur de configuration est null, « Hello » est affecté à message :

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Lire l’environnement

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Ajouter des fournisseurs de journalisation

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

Ajouter des services

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

Personnaliser IHostBuilder

Les méthodes d’extension existantes sur IHostBuilder sont accessibles à l’aide de la propriété 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();

Personnaliser IWebHostBuilder

Les méthodes d’extension sur IWebHostBuilder sont accessibles à l’aide de la propriété 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();

Modifier la racine web

Par défaut, la racine web est relative à la racine de contenu dans le dossier wwwroot. La racine web est l’emplacement où le middleware de fichiers statiques recherche des fichiers statiques. La racine web peut être modifiée avec WebHostOptions, la ligne de commande ou avec la méthode UseWebRoot :

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

var app = builder.Build();

app.Run();

Conteneur d’injection de dépendances (DI) personnalisé

L’exemple suivant utilise 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();

Ajouter un intergiciel

Tout intergiciel ASP.NET Core existant peut être configuré sur WebApplication :

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations, consultez Intergiciel (middleware) ASP.NET Core

Page d’exceptions du développeur

WebApplication.CreateBuilder initialise une nouvelle instance de la classe WebApplicationBuilder avec les valeurs par défaut préconfigurées. La page d’exception du développeur est activée dans les valeurs par défaut préconfigurées. Lorsque le code suivant est exécuté dans l’environnement de développement, la navigation vers / présente une page conviviale qui affiche l’exception.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Run();

Intergiciel (middleware) ASP.NET Core

Le tableau suivant répertorie certains des intergiciels fréquemment utilisés avec les API minimales.

Middleware Description API
Authentication Prend en charge l’authentification. UseAuthentication
Authorization Fournit la prise en charge des autorisations. UseAuthorization
CORS Configure le partage des ressources cross-origin (CORS). UseCors
Gestionnaire d’exceptions Gère globalement les exceptions levées par le pipeline d’intergiciels. UseExceptionHandler
En-têtes transférés Transfère les en-têtes en proxy vers la requête actuelle. UseForwardedHeaders
HTTPS Redirection Redirige toutes les requêtes HTTP vers HTTPS. UseHttpsRedirection
HSTS (HTTP Strict Transport Security) Middleware d’amélioration de la sécurité qui ajoute un en-tête de réponse spécial. UseHsts
Journalisation des demandes Prend en charge la journalisation des requêtes et des réponses HTTP. UseHttpLogging
Délais d’expiration des requêtes Permet de configurer les délais d'attente des requêtes, par défaut global et par point d'extrémité. UseRequestTimeouts
Journalisation des requêtes W3C Prend en charge la journalisation des requêtes et des réponses HTTP au format W3C. UseW3CLogging
Mise en cache des réponses Prend en charge la mise en cache des réponses. UseResponseCaching
Compression de la réponse Prend en charge la compression des réponses. UseResponseCompression
Session Prend en charge la gestion des sessions utilisateur. UseSession
Fichiers statiques Prend en charge le traitement des fichiers statiques et l’exploration des répertoires. UseStaticFiles, UseFileServer
WebSockets Autorise le protocole WebSockets. UseWebSockets

Les sections suivantes couvrent la gestion des requêtes : routage, liaison de paramètres et réponses.

Routing

Un WebApplication configuré prend en charge Map{Verb} et MapMethods, où {Verb} est une méthode HTTP en casse mixte, comme Get, Post, Put ou Delete :

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

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

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

app.Run();

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

Gestionnaires d’itinéraires

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

Expression lambda

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

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

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

app.MapGet("/", handler);

app.Run();

Fonction locale

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

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

app.MapGet("/", LocalFunction);

app.Run();

Méthode d’instance

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

var handler = new HelloHandler();

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

app.Run();

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

Méthode statique

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

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

app.Run();

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

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

Les API minimales n’ont pas besoin d’être situées à l’emplacement Program.cs.

Program.cs

using MinAPISeparateFile;

var builder = WebApplication.CreateSlimBuilder(args);

var app = builder.Build();

TodoEndpoints.Map(app);

app.Run();

TodoEndpoints.cs

namespace MinAPISeparateFile;

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

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

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

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

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

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

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

app.Run();

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

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

Noms de points de terminaison :

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

Paramètres de routage

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

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

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

app.Run();

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

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

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

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

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

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

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

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

app.Run();

Contraintes d'itinéraire

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

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

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

app.Run();

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

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

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

Groupes de routage

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

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

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

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


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

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

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

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

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

    return group;
}

Dans ce scénario, vous pouvez utiliser une adresse relative pour l’en-tête Location dans le résultat 201 Created :

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

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

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

La QueryPrivateTodosfabrique de filtre de point de terminaison est une fonction locale qui modifie les paramètres TodoDb du gestionnaire d’itinéraires pour permettre l’accès et le stockage de données todo privées.

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

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

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

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

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

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

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

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

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

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

/outer group filter
/inner group filter
MapGet filter

Liaison de paramètres

La liaison de paramètres est le processus de conversion des données de requête en paramètres fortement typés qui sont exprimés par les gestionnaires de routes. Une source de liaison détermine à partir d’où les paramètres sont liés. Les sources de liaison peuvent être explicites ou déduites en fonction de la méthode HTTP et du type de paramètre.

Sources de liaison prises en charge :

  • Valeurs de routage
  • Chaîne de requête
  • Header
  • Corps (JSON)
  • Valeurs de formulaire
  • Services fournis par l’injection de dépendances
  • Custom

Le gestionnaire de routage GET suivant utilise certaines de ces sources de liaison de paramètres :

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

Fonctionnalités de liaison de paramètres clés

  • Liaison explicite : utilisez des attributs tels que [FromRoute], , [FromQuery][FromHeader], [FromBody], , [FromForm]et [FromServices] pour spécifier explicitement des sources de liaison.
  • Liaison de données de formulaire : lier des valeurs de formulaire à l’aide d’attribut de [FromForm], prenant en charge IFormFile et IFormFileCollection pour le téléversement de fichiers.
  • Types complexes : lier des collections et des types complexes à partir de formulaires, de chaînes de requête et d’en-têtes.
  • Liaison personnalisée : implémentez une logique de liaison personnalisée à l'aide de TryParse, de BindAsync, ou de l’interface IBindableFromHttpContext<T>.
  • Paramètres facultatifs : prend en charge les types nullables et les valeurs par défaut pour les paramètres facultatifs.
  • Injection de dépendances : les paramètres sont automatiquement liés à partir des services enregistrés dans le conteneur DI.
  • Types spéciaux : liaison automatique pour HttpContext, , HttpRequestHttpResponseCancellationToken, ClaimsPrincipal, , Streamet .PipeReader

Pour en savoir plus: Pour plus d’informations sur la liaison de paramètres, notamment les scénarios avancés, la validation, la priorité de liaison et la résolution des problèmes, consultez Liaison de paramètre dans les applications API minimales.

Responses

Les gestionnaires de routes prennent en charge les types de valeurs de retour suivants :

  1. Basé sur IResult : cela inclut Task<IResult> et ValueTask<IResult>
  2. string - Cela comprend Task<string> et ValueTask<string>
  3. T (Tout autre type) : cela inclut Task<T> et ValueTask<T>
Valeur retournée Behavior Content-Type
IResult Le framework appelle IResult.ExecuteAsync Décidé par l’implémentation IResult
string Le framework écrit la chaîne directement dans la réponse text/plain
T (Tout autre type) Le JSON du framework sérialise la réponse application/json

Pour obtenir un guide plus détaillé sur les valeurs de retour du gestionnaire de routes, consultez Créer des réponses dans les applications API minimales.

Exemples de valeurs de retour

Valeurs de retour string

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

Valeurs de retour JSON

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

Retour TypedResults

Le code suivant retourne TypedResults :

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

Le retour de TypedResults est préférable au retour Results. Pour plus d'informations, consultez TypedResults vs Results.

Valeurs de retour IResult

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

L’exemple suivant utilise les types de résultats intégrés pour personnaliser la réponse :

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

Code d’état personnalisé

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

Pour plus d’exemples, consultez Créer des réponses dans les applications API minimales.

Redirect

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

File

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

Résultats intégrés

Des assistants de résultats communs existent dans les classes Results et TypedResults statiques. Le retour de TypedResults est préférable au retour Results. Pour plus d'informations, consultez TypedResults vs Results.

Modification des en-têtes

Utilisez l’objet HttpResponse pour modifier les en-têtes de réponse :

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

Personnalisation des résultats

Les applications peuvent contrôler les réponses en implémentant un type IResult personnalisé. Le code suivant est un exemple de type de résultat 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);
    }
}

Nous vous recommandons d’ajouter une méthode d’extension à Microsoft.AspNetCore.Http.IResultExtensions pour rendre ces résultats personnalisés plus détectables.

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

Résultats typés

L’interface IResult peut représenter des valeurs retournées par les API minimales qui n’utilisent pas la prise en charge implicite de la sérialisation JSON de l’objet retourné dans la réponse HTTP. La classe statique Results est utilisée pour créer différents objets IResult qui représentent différents types de réponses. Par exemple, la définition du code d’état de la réponse ou la redirection vers une autre URL.

Les types implémentant IResult sont publics, ce qui permet les assertions de type lors des tests. Par exemple:

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

Vous pouvez examiner les types de retour des méthodes correspondantes sur la classe statique TypedResults pour trouver le type IResult public approprié vers lequel effectuer la conversion.

Pour plus d’exemples, consultez Créer des réponses dans les applications API minimales.

Filters

Pour plus d’informations, consultez Filtres dans les applications API minimales.

Authorization

Les itinéraires peuvent être protégés à l’aide de stratégies d’autorisation. Celles-ci peuvent être déclarées via l’attribut [Authorize] ou à l’aide de la méthode RequireAuthorization :

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

Le code précédent peut être écrit avec RequireAuthorization :

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

L’exemple suivant utilise l’autorisation basée sur la stratégie :

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

Autoriser les utilisateurs non authentifiés à accéder à un point de terminaison

[AllowAnonymous] permet aux utilisateurs non authentifiés d’accéder aux points de terminaison :

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


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

CORS

Les itinéraires peuvent avoir CORS activé à l’aide de stratégies CORS. CORS peut être déclaré via l’attribut [EnableCors] ou à l’aide de la méthode RequireCors. Les exemples suivants activent 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();

Pour plus d’informations, consultez Activation les requêtes cross-origin (CORS) dans ASP.NET Core

ValidateScopes et ValidateOnBuild

ValidateScopes et ValidateOnBuild sont activés par défaut dans l’environnement de Développement, mais désactivés dans d’autres environnements.

Quand ValidateOnBuild est true, le conteneur d’injection de dépendances valide la configuration du service au moment de la génération. Si la configuration du service n’est pas valide, la génération échoue au démarrage de l’application, plutôt qu’au moment de l’exécution (runtime) lorsque le service est demandé.

Quand ValidateScopes est true, le conteneur d’injection de dépendances valide qu’un service délimité n’est pas résolu à partir de l’étendue racine. La résolution d’un service délimité à partir de l’étendue racine peut entraîner une fuite de mémoire, car le service est conservé en mémoire plus longtemps que l’étendue de la requête.

ValidateScopes et ValidateOnBuild ont la valeur false par défaut dans les modes autres que Développement pour des raisons de performances.

Le code suivant montre que ValidateScopes est activé par défaut en mode développement, mais désactivé en mode mise en production :

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

Le code suivant montre que ValidateOnBuild est activé par défaut en mode développement, mais désactivé en mode mise en production :

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

Le code suivant désactive ValidateScopes et ValidateOnBuild dans 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;
    });
}

Voir aussi

Ce document :

Les API minimales sont les suivantes :

WebApplication

Le code suivant est généré par un modèle ASP.NET Core :

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

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

app.Run();

Le code précédent peut être créé via dotnet new web sur la ligne de commande ou en sélectionnant le modèle web Vide dans Visual Studio.

Le code suivant crée un WebApplication (app) sans créer explicitement de WebApplicationBuilder :

var app = WebApplication.Create(args);

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

app.Run();

WebApplication.Create initialise une nouvelle instance de la classe WebApplication avec les valeurs par défaut préconfigurées.

WebApplication ajoute automatiquement le middleware suivant dans les applications API minimales en fonction de certaines conditions :

  • UseDeveloperExceptionPage est ajouté en premier lorsque HostingEnvironment est "Development".
  • UseRouting est ajouté ensuite si le code utilisateur n’a pas déjà appelé UseRouting et s’il existe des points de terminaison configurés, par exemple app.MapGet.
  • UseEndpoints est ajouté à la fin du pipeline d’intergiciel si des points de terminaison sont configurés.
  • UseAuthentication est ajouté immédiatement après UseRouting, si le code utilisateur n’a pas déjà appelé UseAuthentication et si IAuthenticationSchemeProvider peut être détecté dans le fournisseur de services. IAuthenticationSchemeProvider est ajouté par défaut lors de l’utilisation de AddAuthentication, et les services sont détectés à l’aide de IServiceProviderIsService.
  • UseAuthorization est ajouté après, si le code utilisateur n’a pas déjà appelé UseAuthorization et si IAuthorizationHandlerProvider peut être détecté dans le fournisseur de services. IAuthorizationHandlerProvider est ajouté par défaut lors de l’utilisation de AddAuthorization, et les services sont détectés à l’aide de IServiceProviderIsService.
  • Les intergiciels et les points de terminaison configurés par l’utilisateur sont ajoutés entre UseRouting et UseEndpoints.

Le code suivant est effectivement ce qu’un intergiciel automatique ajouté à l’application produit :

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

Dans certains cas, la configuration de l’intergiciel par défaut n’est pas correcte pour l’application et exige une modification. Par exemple, UseCors doit être appelé avant UseAuthentication et UseAuthorization. L’application doit appeler UseAuthentication et UseAuthorization, si UseCors est appelé :

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

Si l’intergiciel doit être exécuté avant l’exécution de la correspondance d’itinéraire, appeler UseRouting et placer l’intergiciel avant l’appel à UseRouting. UseEndpoints n’est pas obligatoire dans ce cas, car il est automatiquement ajouté comme décrit précédemment :

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

app.UseRouting();

// other middleware and endpoints

Lors de l’ajout d’un intergiciel de terminal :

  • L’intergiciel doit être ajouté après UseEndpoints.
  • L’application doit appeler UseRouting et UseEndpoints pour que l’intergiciel de terminal puisse être placé à l’emplacement approprié.
app.UseRouting();

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

app.UseEndpoints(e => {});

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

Un intergiciel de terminal est un intergiciel qui s’exécute si aucun point de terminaison ne gère la requête.

Utilisation des ports

Lorsqu’une application web est créée avec Visual Studio ou dotnet new, un fichier Properties/launchSettings.json est créé et spécifie les ports auxquels l’application répond. Dans les exemples de paramètres de port qui suivent, l’exécution de l’application à partir de Visual Studio renvoie une boîte de dialogue d’erreur Unable to connect to web server 'AppName'. Visual Studio retourne une erreur, car il attend le port spécifié dans Properties/launchSettings.json, mais l’application utilise le port spécifié par app.Run("http://localhost:3000"). Exécutez les exemples de modification de port suivants à partir de la ligne de commande.

Les sections suivantes définissent le port auquel l’application répond.

var app = WebApplication.Create(args);

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

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

Dans le code précédent, l’application répond au port 3000.

Plusieurs ports

Dans le code suivant, l’application répond aux ports 3000 et 4000.

var app = WebApplication.Create(args);

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

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

app.Run();

Définir le port à partir de la ligne de commande

La commande suivante permet à l’application de répondre au port 7777 :

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

Si le point de terminaison Kestrel est également configuré dans le fichier appsettings.json, l’URL spécifiée par le fichier appsettings.json est utilisée. Pour plus d’informations, consultez Configuration du point de terminaison Kestrel

Lire le port à partir de l’environnement

Le code suivant lit le port à partir de l’environnement :

var app = WebApplication.Create(args);

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

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

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

La méthode recommandée pour définir le port à partir de l’environnement consiste à utiliser la variable d’environnement ASPNETCORE_URLS, comme indiqué dans la section suivante.

Définir les ports via la variable d’environnement ASPNETCORE_URLS

La variable d’environnement ASPNETCORE_URLS est disponible pour définir le port :

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS prend en charge plusieurs URL :

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

Pour plus d’informations sur l’utilisation de l’environnement, consultez ASP.NET environnements d’exécution Core

Écouter sur toutes les interfaces

Les exemples suivants illustrent l’écoute sur toutes les interfaces

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

Écoutez toutes les interfaces à l’aide d’ASPNETCORE_URLS

Les exemples précédents peuvent utiliser ASPNETCORE_URLS

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

Spécifier HTTPS avec un certificat de développement

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations sur le certificat de développement, consultez Approuver le certificat de développement HTTPS ASP.NET Core sur Windows et macOS.

Spécifier HTTPS à l’aide d’un certificat personnalisé

Les sections suivantes montrent comment spécifier le certificat personnalisé à l’aide du fichier appsettings.json et via la configuration.

Spécifier le certificat personnalisé avec appsettings.json

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

Spécifier le certificat personnalisé via la configuration

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

Utiliser les API de certificat

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

Le code suivant est lu à partir du système de configuration :

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations, consultez Configuration dans ASP.NET Core

Logging

Le code suivant écrit un message dans le journal au démarrage de l’application :

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations, consultez Journalisation dans .NET et ASP.NET Core

Accéder au conteneur d’injection de dépendances (DI)

Le code suivant montre comment obtenir des services à partir du conteneur d’authentification unique au démarrage de l’application :


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

Pour plus d’informations, consultez Injection de dépendances dans ASP.NET Core.

WebApplicationBuilder

Cette section contient un exemple de code utilisant WebApplicationBuilder.

Modifier la racine du contenu, le nom de l’application et l’environnement

Le code suivant définit la racine du contenu, le nom de l’application et l’environnement :

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 initialise une nouvelle instance de la classe WebApplicationBuilder avec les valeurs par défaut préconfigurées.

Pour plus d’informations, consultez Vue d’ensemble des principes de base d’ASP.NET Core

Modifier la racine du contenu, le nom de l’application et l’environnement à l’aide de variables d’environnement ou de la ligne de commande

Le tableau suivant montre la variable d’environnement et l’argument de ligne de commande utilisés pour modifier la racine du contenu, le nom de l’application et l’environnement :

feature Variable d’environnement Argument de ligne de commande
Nom de l’application ASPNETCORE_APPLICATIONNAME --applicationName
Nom de l’environnement ASPNETCORE_ENVIRONMENT --environment
Racine du contenu ASPNETCORE_CONTENTROOT --contentRoot

Ajouter des fournisseurs de configuration

L’exemple suivant ajoute le fournisseur de configuration INI :

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

Pour plus d’informations, consultez Fournisseurs de configuration de fichiers dans Configuration dans ASP.NET Core.

Lire la configuration

Par défaut, WebApplicationBuilder lit la configuration à partir de plusieurs sources, notamment :

  • appSettings.json et appSettings.{environment}.json
  • Variables d'environnement
  • Ligne de commande

Le code suivant lit HelloKey à partir de la configuration et affiche la valeur au niveau du point de terminaison /. Si la valeur de configuration est null, « Hello » est affecté à message :

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Pour obtenir la liste complète des sources de configuration lues, consultez Configuration par défaut dans Configuration dans ASP.NET Core

Ajouter des fournisseurs de journalisation

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

Ajouter des services

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

Personnaliser IHostBuilder

Les méthodes d’extension existantes sur IHostBuilder sont accessibles à l’aide de la propriété 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();

Personnaliser IWebHostBuilder

Les méthodes d’extension sur IWebHostBuilder sont accessibles à l’aide de la propriété 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();

Modifier la racine web

Par défaut, la racine web est relative à la racine de contenu dans le dossier wwwroot. La racine web est l’emplacement où le middleware de fichiers statiques recherche des fichiers statiques. La racine web peut être modifiée avec WebHostOptions, la ligne de commande ou avec la méthode UseWebRoot :

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

var app = builder.Build();

app.Run();

Conteneur d’injection de dépendances (DI) personnalisé

L’exemple suivant utilise 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();

Ajouter un intergiciel

Tout intergiciel ASP.NET Core existant peut être configuré sur WebApplication :

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations, consultez Intergiciel (middleware) ASP.NET Core

Page d’exceptions du développeur

WebApplication.CreateBuilder initialise une nouvelle instance de la classe WebApplicationBuilder avec les valeurs par défaut préconfigurées. La page d’exception du développeur est activée dans les valeurs par défaut préconfigurées. Lorsque le code suivant est exécuté dans l’environnement de développement, la navigation vers / présente une page conviviale qui affiche l’exception.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Run();

Intergiciel (middleware) ASP.NET Core

Le tableau suivant répertorie certains des intergiciels fréquemment utilisés avec les API minimales.

Middleware Description API
Authentication Prend en charge l’authentification. UseAuthentication
Authorization Fournit la prise en charge des autorisations. UseAuthorization
CORS Configure le partage des ressources cross-origin (CORS). UseCors
Gestionnaire d’exceptions Gère globalement les exceptions levées par le pipeline d’intergiciels. UseExceptionHandler
En-têtes transférés Transfère les en-têtes en proxy vers la requête actuelle. UseForwardedHeaders
HTTPS Redirection Redirige toutes les requêtes HTTP vers HTTPS. UseHttpsRedirection
HSTS (HTTP Strict Transport Security) Middleware d’amélioration de la sécurité qui ajoute un en-tête de réponse spécial. UseHsts
Journalisation des demandes Prend en charge la journalisation des requêtes et des réponses HTTP. UseHttpLogging
Délais d’expiration des requêtes Permet de configurer les délais d'attente des requêtes, par défaut global et par point d'extrémité. UseRequestTimeouts
Journalisation des requêtes W3C Prend en charge la journalisation des requêtes et des réponses HTTP au format W3C. UseW3CLogging
Mise en cache des réponses Prend en charge la mise en cache des réponses. UseResponseCaching
Compression de la réponse Prend en charge la compression des réponses. UseResponseCompression
Session Prend en charge la gestion des sessions utilisateur. UseSession
Fichiers statiques Prend en charge le traitement des fichiers statiques et l’exploration des répertoires. UseStaticFiles, UseFileServer
WebSockets Autorise le protocole WebSockets. UseWebSockets

Les sections suivantes couvrent la gestion des requêtes : routage, liaison de paramètres et réponses.

Routing

Un WebApplication configuré prend en charge Map{Verb} et MapMethods, où {Verb} est une méthode HTTP en casse mixte, comme Get, Post, Put ou Delete :

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

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

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

app.Run();

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

Gestionnaires d’itinéraires

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

Expression lambda

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

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

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

app.MapGet("/", handler);

app.Run();

Fonction locale

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

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

app.MapGet("/", LocalFunction);

app.Run();

Méthode d’instance

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

var handler = new HelloHandler();

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

app.Run();

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

Méthode statique

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

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

app.Run();

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

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

Les API minimales n’ont pas besoin d’être situées à l’emplacement Program.cs.

Program.cs

using MinAPISeparateFile;

var builder = WebApplication.CreateSlimBuilder(args);

var app = builder.Build();

TodoEndpoints.Map(app);

app.Run();

TodoEndpoints.cs

namespace MinAPISeparateFile;

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

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

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

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

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

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

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

app.Run();

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

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

Noms de points de terminaison :

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

Paramètres de routage

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

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

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

app.Run();

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

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

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

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

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

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

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

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

app.Run();

Contraintes d'itinéraire

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

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

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

app.Run();

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

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

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

Groupes de routage

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

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

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

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


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

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

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

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

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

    return group;
}

Dans ce scénario, vous pouvez utiliser une adresse relative pour l’en-tête Location dans le résultat 201 Created :

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

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

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

La QueryPrivateTodosfabrique de filtre de point de terminaison est une fonction locale qui modifie les paramètres TodoDb du gestionnaire d’itinéraires pour permettre l’accès et le stockage de données todo privées.

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

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

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

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

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

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

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

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

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

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

/outer group filter
/inner group filter
MapGet filter

Liaison de paramètres

La liaison de paramètres est le processus de conversion des données de requête en paramètres fortement typés qui sont exprimés par les gestionnaires de routes. Une source de liaison détermine à partir d’où les paramètres sont liés. Les sources de liaison peuvent être explicites ou déduites en fonction de la méthode HTTP et du type de paramètre.

Sources de liaison prises en charge :

  • Valeurs de routage
  • Chaîne de requête
  • Header
  • Corps (JSON)
  • Services fournis par l’injection de dépendances
  • Custom

La liaison à partir de valeurs de formulaire n’est pas prise en charge en mode natif dans .NET 6 et 7.

Le gestionnaire de routage GET suivant utilise certaines de ces sources de liaison de paramètres :

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

Le tableau suivant montre la relation entre les paramètres utilisés dans l’exemple précédent et les sources de liaison associées.

Parameter Source de liaison
id valeur de route
page chaîne de requête
customHeader header
service Fourni par l’injection de dépendances

Les méthodes HTTP GET, HEAD, OPTIONS et DELETE ne sont pas implicitement liées à partir du corps. Pour lier à partir du corps (JSON) pour ces méthodes HTTP, liez explicitement avec [FromBody] ou lisez à partir de HttpRequest.

L’exemple de gestionnaire de routage POST suivant utilise une source de liaison de corps (JSON) pour le paramètre person :

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

record Person(string Name, int Age);

Les paramètres des exemples précédents sont tous liés automatiquement à partir des données de requête. Pour illustrer la commodité de la liaison de paramètres, les gestionnaires de routage suivants montrent comment lire les données de requête directement à partir de la requête :

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

    // ...
});

Liaison de paramètre explicite

Les attributs peuvent être utilisés pour déclarer explicitement à partir d’où les paramètres sont liés.

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 Source de liaison
id valeur de routage avec le nom id
page chaîne de requête avec le nom "p"
service Fourni par l’injection de dépendances
contentType en-tête avec le nom "Content-Type"

Note

La liaison à partir de valeurs de formulaire n’est pas prise en charge en mode natif dans .NET 6 et 7.

Liaison de paramètre avec injection de dépendances

La liaison de paramètre pour les API minimales lie des paramètres via l’injection de dépendance quand le type est configuré en tant que service. Il n’est pas nécessaire d’appliquer explicitement l’attribut [FromServices] à un paramètre. Dans le code suivant, les deux actions retournent l’heure :

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

Paramètres facultatifs

Les paramètres déclarés dans les gestionnaires d’itinéraire sont traités comme requis :

  • Si une requête correspond à l’itinéraire, le gestionnaire d’itinéraire s’exécute uniquement si tous les paramètres requis sont fournis dans la requête.
  • Le fait de ne pas fournir tous les paramètres requis entraîne une erreur.
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 retourné
/products BadHttpRequestException : le paramètre obligatoire « int pageNumber » n’a pas été fourni à partir de la chaîne de requête.
/products/1 Erreur HTTP 404, aucune route correspondante

Pour rendre pageNumber facultatif, définissez le type comme facultatif ou fournissez une valeur par défaut :

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 retourné
/products 1 retourné
/products2 1 retourné

La valeur nullable et la valeur par défaut précédentes s’appliquent à toutes les sources :

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

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

app.Run();

Le code précédent appelle la méthode avec un produit null si aucun corps de requête n’est envoyé.

REMARQUE : Si des données non valides sont fournies et que le paramètre est nullable, le gestionnaire de routage n’est pas exécuté.

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 retourné
/products 1 retourné
/products?pageNumber=two BadHttpRequestException : Échec de la liaison du paramètre "Nullable<int> pageNumber" à partir de « two ».
/products/two Erreur HTTP 404, aucune route correspondante

Pour plus d’informations, consultez la section Échecs de liaison.

Types spéciaux

Les types suivants sont liés sans attributs explicites :

  • HttpContext : contexte qui contient toutes les informations sur la requête ou la réponse HTTP actuelle :

    app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
    
  • HttpRequest et HttpResponse : requête HTTP et réponse HTTP :

    app.MapGet("/", (HttpRequest request, HttpResponse response) =>
        response.WriteAsync($"Hello World {request.Query["name"]}"));
    
  • CancellationToken : jeton d’annulation associé à la requête HTTP actuelle :

    app.MapGet("/", async (CancellationToken cancellationToken) => 
        await MakeLongRunningRequestAsync(cancellationToken));
    
  • ClaimsPrincipal : utilisateur associé à la requête, lié à partir de HttpContext.User :

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

Lier le corps de la requête en tant que Stream ou PipeReader

Le corps de la requête peut être lié en tant que Stream ou PipeReader pour prendre en charge efficacement les scénarios où l’utilisateur doit traiter des données et :

  • Stockez les données dans le stockage d’objets blob ou placez les données en file d’attente dans un fournisseur de file d’attente.
  • Traitez les données stockées avec un processus Worker ou une fonction cloud.

Par exemple, les données peuvent être mises en file d’attente pour le Stockage File d’attente Azure ou stockées dans le Stockage Blob Azure.

Le code suivant implémente une file d’attente en arrière-plan :

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

Le code suivant lie le corps de la requête à un Stream :

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

Le code suivant montre l’intégralité du fichier 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();
  • Lors de la lecture de données, le Stream est le même objet que HttpRequest.Body.
  • Le corps de la requête n’est pas mis en mémoire tampon par défaut. Une fois le corps lu, il n’est pas rembobinable. Le flux ne peut pas être lu plusieurs fois.
  • Les Stream et PipeReader ne sont pas utilisables en dehors du gestionnaire d’actions minimal, car les mémoires tampons sous-jacentes seront supprimées ou réutilisées.

Chargements de fichiers à l’aide d’IFormFile et IFormFileCollection

Le code suivant utilise IFormFile et IFormFileCollection pour charger le fichier :

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

Les requêtes de chargement de fichiers authentifiés sont prises en charge à l’aide d’un en-tête d’autorisation, d’un certificat client ou d’un en-tête cookie.

Il n'y a pas de prise en charge intégrée de l’antifalsification dans ASP.NET Core dans .NET 7. Antiforgery est disponible dans ASP.NET Core dans .NET 8 ou version ultérieure. Toutefois, elle peut être implémentée à l’aide du service IAntiforgery.

Lier des tableaux et des valeurs de chaîne à partir d’en-têtes et de chaînes de requête

Le code suivant illustre la liaison de chaînes de requête à un tableau de types primitifs, de tableaux de chaînes et de 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]}");

La liaison de chaînes de requête ou de valeurs d’en-tête à un tableau de types complexes est prise en charge lorsque le type implémente TryParse. Le code suivant est lié à un tableau de chaînes et retourne tous les éléments avec les balises spécifiées :

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

Le code suivant montre le modèle et l’implémentation TryParse requise :

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

Le code suivant lie un tableau int :

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

Pour tester le code précédent, ajoutez le point de terminaison suivant pour remplir la base de données avec des éléments Todo :

// POST /todoitems/batch
app.MapPost("/todoitems/batch", async (Todo[] todos, TodoDb db) =>
{
    await db.Todos.AddRangeAsync(todos);
    await db.SaveChangesAsync();

    return Results.Ok(todos);
});

Utilisez un outil de test d’API comme HttpRepl pour passer les données suivantes au point de terminaison précédent :

[
    {
        "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"
        }
    }
]

Le code suivant est lié à la clé d’en-tête X-Todo-Id et retourne les éléments Todo avec des valeurs Id correspondantes :

// 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

Lors de la liaison d’un string[] à partir d’une chaîne de requête, l’absence d’une valeur de chaîne de requête correspondante entraîne un tableau vide au lieu d’une valeur Null.

Liaison de paramètres pour les listes d’arguments avec [AsParameters]

AsParametersAttribute active la liaison de paramètres simples aux types et non la liaison de modèle complexe ou récursive.

Examinons le code ci-dessous.

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.

Considérez le point de terminaison GET suivant :

app.MapGet("/todoitems/{id}",
                             async (int Id, TodoDb Db) =>
    await Db.Todos.FindAsync(Id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());

Le struct suivant peut être utilisé pour remplacer les paramètres mis en évidence précédents :

struct TodoItemRequest
{
    public int Id { get; set; }
    public TodoDb Db { get; set; }
}

Le point de terminaison GET refactorisé utilise le struct précédent avec l’attribut 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());

Le code suivant montre des points de terminaison supplémentaires dans l’application :

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

Les classes suivantes sont utilisées pour refactoriser les listes de paramètres :

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

Le code suivant montre les points de terminaison refactorisés avec AsParameters et le struct et les classes qui précèdent :

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

Les types record suivants peuvent être utilisés pour remplacer les paramètres précédents :

record TodoItemRequest(int Id, TodoDb Db);
record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);

L’utilisation de struct avec AsParameters peut être plus performante que l’utilisation d’un type record.

L’exemple de code complet dans le référentiel AspNetCore.Docs.Samples.

Liaison personnalisée

Il existe trois façons de personnaliser la liaison de paramètres :

  1. Pour les sources de liaison de routage, de requête et d’en-tête, liez des types personnalisés en ajoutant une méthode statique TryParse pour le type.
  2. Contrôlez le processus de liaison en implémentant une méthode BindAsync sur un type.
  3. Pour les scénarios avancés, implémentez l’interface IBindableFromHttpContext<TSelf> pour fournir une logique de liaison personnalisée directement à partir du HttpContext.

TryParse

TryParse a deux API :

public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);

Le code suivant affiche Point: 12.3, 10.1 avec 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 possède les API suivantes :

public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);

Le code suivant affiche SortBy:xyz, SortDirection:Desc, CurrentPage:99 avec 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
}

Liaison de paramètre personnalisée avec IBindableFromHttpContext

ASP.NET Core prend en charge la liaison de paramètres personnalisées dans les API minimales à l’aide de l’interface IBindableFromHttpContext<TSelf> . Cette interface, introduite avec les membres abstraits statiques de C# 11, vous permet de créer des types qui peuvent être liés à partir d’un contexte HTTP directement dans les paramètres du gestionnaire d’itinéraires.

public interface IBindableFromHttpContext<TSelf>
    where TSelf : class, IBindableFromHttpContext<TSelf>
{
    static abstract ValueTask<TSelf?> BindAsync(HttpContext context, ParameterInfo parameter);
}

En implémentant l’interface IBindableFromHttpContext<TSelf> , vous pouvez créer des types personnalisés qui gèrent leur propre logique de liaison à partir de HttpContext. Lorsqu’un gestionnaire de routage inclut un paramètre de ce type, l’infrastructure appelle automatiquement la méthode Static BindAsync pour créer l’instance :

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

Voici un exemple d’implémentation d’un paramètre personnalisé qui se lie à partir d’un en-tête 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
        });
    }
}

Vous pouvez également implémenter la validation dans votre logique de liaison personnalisée :

app.MapGet("/validated", (ValidatedParameter param) =>
{
    if (string.IsNullOrEmpty(param.Value))
    {
        return Results.BadRequest("Value cannot be empty");
    }
    
    return Results.Ok($"Validated value: {param.Value}");
});

Afficher ou télécharger l’exemple de code (comment télécharger)

Échecs de liaison

En cas d’échec de la liaison, le framework enregistre un message de débogage et retourne différents codes d’état au client en fonction du mode d’échec.

Mode d’échec Type de paramètre nullable Source de liaison Code statut
{ParameterType}.TryParse retourne « false » yes route/query/header 400
{ParameterType}.BindAsync retourne « null » yes custom 400
Exceptions {ParameterType}.BindAsync n’a pas d’importance custom 500
Échec de la désérialisation du corps JSON n’a pas d’importance body 400
Type de contenu incorrect (pas application/json) n’a pas d’importance body 415

Priorité de liaison

Règles permettant de déterminer une source de liaison à partir d’un paramètre :

  1. Attribut explicite défini sur le paramètre (attributs From*) dans l’ordre suivant :
    1. Valeurs d’itinéraire : [FromRoute]
    2. Chaîne de requête : [FromQuery]
    3. En-tête : [FromHeader]
    4. Corps : [FromBody]
    5. Service : [FromServices]
    6. Valeurs du paramètre : [AsParameters]
  2. Types spéciaux
    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. Le type de paramètre a une méthode BindAsync statique valide.
  4. Le type de paramètre est une chaîne ou a une méthode TryParse statique valide.
    1. Si le nom du paramètre existe dans le modèle de route. Dans app.Map("/todo/{id}", (int id) => {});, id est lié à partir de la route.
    2. Lié à partir de la chaîne de requête.
  5. Si le type de paramètre est un service fourni par l’injection de dépendances, il utilise ce service comme source.
  6. Le paramètre provient du corps.

Configurer les options de désérialisation JSON pour la liaison de corps

La source de liaison de corps utilise System.Text.Json pour la désérialisation. Il n’est pas possible de modifier cette valeur par défaut, mais les options de sérialisation et de désérialisation JSON peuvent être configurées.

Configurer globalement les options de désérialisation JSON

Les options qui s’appliquent globalement à une application peuvent être configurées en appelant ConfigureHttpJsonOptions. L’exemple suivant inclut des champs publics et des formats de sortie 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
// }

Étant donné que l’exemple de code configure à la fois la sérialisation et la désérialisation, il peut lire NameField et inclure NameField dans la sortie JSON.

Configurer les options de désérialisation JSON pour un point de terminaison

ReadFromJsonAsync a des surcharges qui acceptent un objet JsonSerializerOptions. L’exemple suivant inclut des champs publics et des formats de sortie 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
// }

Étant donné que le code précédent applique les options personnalisées uniquement à la désérialisation, la sortie JSON exclut NameField.

Lire le corps de la requête

Lisez le corps de la requête directement à l’aide d’un paramètre HttpContext ou 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();

Le code précédent :

  • Accède au corps de la requête à l’aide de HttpRequest.BodyReader.
  • Copie le corps de la requête dans un fichier local.

Responses

Les gestionnaires de routes prennent en charge les types de valeurs de retour suivants :

  1. Basé sur IResult : cela inclut Task<IResult> et ValueTask<IResult>
  2. string - Cela comprend Task<string> et ValueTask<string>
  3. T (Tout autre type) : cela inclut Task<T> et ValueTask<T>
Valeur retournée Behavior Content-Type
IResult Le framework appelle IResult.ExecuteAsync Décidé par l’implémentation IResult
string Le framework écrit la chaîne directement dans la réponse text/plain
T (Tout autre type) Le JSON du framework sérialise la réponse application/json

Pour obtenir un guide plus détaillé sur les valeurs de retour du gestionnaire de routes, consultez Créer des réponses dans les applications API minimales.

Exemples de valeurs de retour

Valeurs de retour string

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

Valeurs de retour JSON

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

Retour TypedResults

Le code suivant retourne TypedResults :

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

Le retour de TypedResults est préférable au retour Results. Pour plus d'informations, consultez TypedResults vs Results.

Valeurs de retour IResult

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

L’exemple suivant utilise les types de résultats intégrés pour personnaliser la réponse :

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

Code d’état personnalisé

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

Pour plus d’exemples, consultez Créer des réponses dans les applications API minimales.

Redirect

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

File

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

Résultats intégrés

Des assistants de résultats communs existent dans les classes Results et TypedResults statiques. Le retour de TypedResults est préférable au retour Results. Pour plus d'informations, consultez TypedResults vs Results.

Personnalisation des résultats

Les applications peuvent contrôler les réponses en implémentant un type IResult personnalisé. Le code suivant est un exemple de type de résultat 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);
    }
}

Nous vous recommandons d’ajouter une méthode d’extension à Microsoft.AspNetCore.Http.IResultExtensions pour rendre ces résultats personnalisés plus détectables.

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

Résultats typés

L’interface IResult peut représenter des valeurs retournées par les API minimales qui n’utilisent pas la prise en charge implicite de la sérialisation JSON de l’objet retourné dans la réponse HTTP. La classe statique Results est utilisée pour créer différents objets IResult qui représentent différents types de réponses. Par exemple, la définition du code d’état de la réponse ou la redirection vers une autre URL.

Les types implémentant IResult sont publics, ce qui permet les assertions de type lors des tests. Par exemple:

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

Vous pouvez examiner les types de retour des méthodes correspondantes sur la classe statique TypedResults pour trouver le type IResult public approprié vers lequel effectuer la conversion.

Pour plus d’exemples, consultez Créer des réponses dans les applications API minimales.

Filters

Consultez Filtres dans les applications d’API minimales

Authorization

Les itinéraires peuvent être protégés à l’aide de stratégies d’autorisation. Celles-ci peuvent être déclarées via l’attribut [Authorize] ou à l’aide de la méthode RequireAuthorization :

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

Le code précédent peut être écrit avec RequireAuthorization :

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

L’exemple suivant utilise l’autorisation basée sur la stratégie :

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

Autoriser les utilisateurs non authentifiés à accéder à un point de terminaison

[AllowAnonymous] permet aux utilisateurs non authentifiés d’accéder aux points de terminaison :

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


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

CORS

Les itinéraires peuvent avoir CORS activé à l’aide de stratégies CORS. CORS peut être déclaré via l’attribut [EnableCors] ou à l’aide de la méthode RequireCors. Les exemples suivants activent 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();

Pour plus d’informations, consultez Activation les requêtes cross-origin (CORS) dans ASP.NET Core

Voir aussi

Ce document :

Les API minimales sont les suivantes :

WebApplication

Le code suivant est généré par un modèle ASP.NET Core :

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

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

app.Run();

Le code précédent peut être créé via dotnet new web sur la ligne de commande ou en sélectionnant le modèle web Vide dans Visual Studio.

Le code suivant crée un WebApplication (app) sans créer explicitement de WebApplicationBuilder :

var app = WebApplication.Create(args);

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

app.Run();

WebApplication.Create initialise une nouvelle instance de la classe WebApplication avec les valeurs par défaut préconfigurées.

Utilisation des ports

Lorsqu’une application web est créée avec Visual Studio ou dotnet new, un fichier Properties/launchSettings.json est créé et spécifie les ports auxquels l’application répond. Dans les exemples de paramètres de port qui suivent, l’exécution de l’application à partir de Visual Studio renvoie une boîte de dialogue d’erreur Unable to connect to web server 'AppName'. Exécutez les exemples de modification de port suivants à partir de la ligne de commande.

Les sections suivantes définissent le port auquel l’application répond.

var app = WebApplication.Create(args);

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

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

Dans le code précédent, l’application répond au port 3000.

Plusieurs ports

Dans le code suivant, l’application répond aux ports 3000 et 4000.

var app = WebApplication.Create(args);

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

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

app.Run();

Définir le port à partir de la ligne de commande

La commande suivante permet à l’application de répondre au port 7777 :

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

Si le point de terminaison Kestrel est également configuré dans le fichier appsettings.json, l’URL spécifiée par le fichier appsettings.json est utilisée. Pour plus d’informations, consultez Configuration du point de terminaison Kestrel

Lire le port à partir de l’environnement

Le code suivant lit le port à partir de l’environnement :

var app = WebApplication.Create(args);

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

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

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

La méthode recommandée pour définir le port à partir de l’environnement consiste à utiliser la variable d’environnement ASPNETCORE_URLS, comme indiqué dans la section suivante.

Définir les ports via la variable d’environnement ASPNETCORE_URLS

La variable d’environnement ASPNETCORE_URLS est disponible pour définir le port :

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS prend en charge plusieurs URL :

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

Écouter sur toutes les interfaces

Les exemples suivants illustrent l’écoute sur toutes les interfaces

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

Écoutez toutes les interfaces à l’aide d’ASPNETCORE_URLS

Les exemples précédents peuvent utiliser ASPNETCORE_URLS

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

Spécifier HTTPS avec un certificat de développement

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations sur le certificat de développement, consultez Approuver le certificat de développement HTTPS ASP.NET Core sur Windows et macOS.

Spécifier HTTPS à l’aide d’un certificat personnalisé

Les sections suivantes montrent comment spécifier le certificat personnalisé à l’aide du fichier appsettings.json et via la configuration.

Spécifier le certificat personnalisé avec appsettings.json

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

Spécifier le certificat personnalisé via la configuration

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

Utiliser les API de certificat

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

Lire l’environnement

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

Pour plus d’informations sur l’utilisation de l’environnement, consultez ASP.NET environnements d’exécution Core

Configuration

Le code suivant est lu à partir du système de configuration :

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations, consultez Configuration dans ASP.NET Core

Logging

Le code suivant écrit un message dans le journal au démarrage de l’application :

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations, consultez Journalisation dans .NET et ASP.NET Core

Accéder au conteneur d’injection de dépendances (DI)

Le code suivant montre comment obtenir des services à partir du conteneur d’authentification unique au démarrage de l’application :


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

Pour plus d’informations, consultez Injection de dépendances dans ASP.NET Core.

WebApplicationBuilder

Cette section contient un exemple de code utilisant WebApplicationBuilder.

Modifier la racine du contenu, le nom de l’application et l’environnement

Le code suivant définit la racine du contenu, le nom de l’application et l’environnement :

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 initialise une nouvelle instance de la classe WebApplicationBuilder avec les valeurs par défaut préconfigurées.

Pour plus d’informations, consultez Vue d’ensemble des principes de base d’ASP.NET Core

Modifier la racine du contenu, le nom de l’application et l’environnement à l’aide de variables d’environnement ou de la ligne de commande

Le tableau suivant montre la variable d’environnement et l’argument de ligne de commande utilisés pour modifier la racine du contenu, le nom de l’application et l’environnement :

feature Variable d’environnement Argument de ligne de commande
Nom de l’application ASPNETCORE_APPLICATIONNAME --applicationName
Nom de l’environnement ASPNETCORE_ENVIRONMENT --environment
Racine du contenu ASPNETCORE_CONTENTROOT --contentRoot

Ajouter des fournisseurs de configuration

L’exemple suivant ajoute le fournisseur de configuration INI :

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

Pour plus d’informations, consultez Fournisseurs de configuration de fichiers dans Configuration dans ASP.NET Core.

Lire la configuration

Par défaut, WebApplicationBuilder lit la configuration à partir de plusieurs sources, notamment :

  • appSettings.json et appSettings.{environment}.json
  • Variables d'environnement
  • Ligne de commande

Pour obtenir la liste complète des sources de configuration lues, consultez Configuration par défaut dans Configuration dans ASP.NET Core

Le code suivant lit HelloKey à partir de la configuration et affiche la valeur au niveau du point de terminaison /. Si la valeur de configuration est null, « Hello » est affecté à message :

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Lire l’environnement

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Ajouter des fournisseurs de journalisation

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

Ajouter des services

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

Personnaliser IHostBuilder

Les méthodes d’extension existantes sur IHostBuilder sont accessibles à l’aide de la propriété 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();

Personnaliser IWebHostBuilder

Les méthodes d’extension sur IWebHostBuilder sont accessibles à l’aide de la propriété 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();

Modifier la racine web

Par défaut, la racine web est relative à la racine de contenu dans le dossier wwwroot. La racine web est l’emplacement où le middleware de fichiers statiques recherche des fichiers statiques. La racine web peut être modifiée avec WebHostOptions, la ligne de commande ou avec la méthode UseWebRoot :

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

var app = builder.Build();

app.Run();

Conteneur d’injection de dépendances (DI) personnalisé

L’exemple suivant utilise 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();

Ajouter un intergiciel

Tout intergiciel ASP.NET Core existant peut être configuré sur WebApplication :

var app = WebApplication.Create(args);

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

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

app.Run();

Pour plus d’informations, consultez Intergiciel (middleware) ASP.NET Core

Page d’exceptions du développeur

WebApplication.CreateBuilder initialise une nouvelle instance de la classe WebApplicationBuilder avec les valeurs par défaut préconfigurées. La page d’exception du développeur est activée dans les valeurs par défaut préconfigurées. Lorsque le code suivant est exécuté dans l’environnement de développement, la navigation vers / présente une page conviviale qui affiche l’exception.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Run();

Intergiciel (middleware) ASP.NET Core

Le tableau suivant répertorie certains des intergiciels fréquemment utilisés avec les API minimales.

Middleware Description API
Authentication Prend en charge l’authentification. UseAuthentication
Authorization Fournit la prise en charge des autorisations. UseAuthorization
CORS Configure le partage des ressources cross-origin (CORS). UseCors
Gestionnaire d’exceptions Gère globalement les exceptions levées par le pipeline d’intergiciels. UseExceptionHandler
En-têtes transférés Transfère les en-têtes en proxy vers la requête actuelle. UseForwardedHeaders
HTTPS Redirection Redirige toutes les requêtes HTTP vers HTTPS. UseHttpsRedirection
HSTS (HTTP Strict Transport Security) Middleware d’amélioration de la sécurité qui ajoute un en-tête de réponse spécial. UseHsts
Journalisation des demandes Prend en charge la journalisation des requêtes et des réponses HTTP. UseHttpLogging
Journalisation des requêtes W3C Prend en charge la journalisation des requêtes et des réponses HTTP au format W3C. UseW3CLogging
Mise en cache des réponses Prend en charge la mise en cache des réponses. UseResponseCaching
Compression de la réponse Prend en charge la compression des réponses. UseResponseCompression
Session Prend en charge la gestion des sessions utilisateur. UseSession
Fichiers statiques Prend en charge le traitement des fichiers statiques et l’exploration des répertoires. UseStaticFiles, UseFileServer
WebSockets Autorise le protocole WebSockets. UseWebSockets

Gestion des demandes

Les sections suivantes couvrent le routage, la liaison de paramètres et les réponses.

Routing

Une WebApplication configurée prend en charge Map{Verb} et 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();

Gestionnaires d’itinéraires

Les gestionnaires de routes sont des méthodes qui s’exécutent lorsque la route correspond. Les gestionnaires de routes peuvent être une fonction de n’importe quelle forme, y compris synchrone ou asynchrone. Les gestionnaires de routes peuvent être une expression lambda, une fonction locale, une méthode d’instance ou une méthode statique.

Expression lambda

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

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

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

app.MapGet("/", handler);

app.Run();

Fonction locale

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

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

app.MapGet("/", LocalFunction);

app.Run();

Méthode d’instance

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

var handler = new HelloHandler();

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

app.Run();

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

Méthode statique

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

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

app.Run();

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

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

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

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

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

app.Run();

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

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

Noms de points de terminaison :

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

Paramètres de routage

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

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

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

app.Run();

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

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

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

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

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

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

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

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

app.Run();

Contraintes d'itinéraire

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

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

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

app.Run();

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

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

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

Liaison de paramètre

La liaison de paramètres est le processus de conversion des données de requête en paramètres fortement typés qui sont exprimés par les gestionnaires de routes. Une source de liaison détermine à partir d’où les paramètres sont liés. Les sources de liaison peuvent être explicites ou déduites en fonction de la méthode HTTP et du type de paramètre.

Sources de liaison prises en charge :

  • Valeurs de routage
  • Chaîne de requête
  • Header
  • Corps (JSON)
  • Services fournis par l’injection de dépendances
  • Custom

Note

La liaison à partir de valeurs de formulaire n’est pas prise en charge en mode natif dans .NET.

L’exemple de gestionnaire de routes GET suivant utilise certaines de ces sources de liaison de paramètres :

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

Le tableau suivant montre la relation entre les paramètres utilisés dans l’exemple précédent et les sources de liaison associées.

Parameter Source de liaison
id valeur de route
page chaîne de requête
customHeader header
service Fourni par l’injection de dépendances

Les méthodes HTTP GET, HEAD, OPTIONS et DELETE ne sont pas implicitement liées à partir du corps. Pour lier à partir du corps (JSON) pour ces méthodes HTTP, liez explicitement avec [FromBody] ou lisez à partir de HttpRequest.

L’exemple de gestionnaire de routage POST suivant utilise une source de liaison de corps (JSON) pour le paramètre person :

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

record Person(string Name, int Age);

Les paramètres des exemples précédents sont tous liés automatiquement à partir des données de requête. Pour illustrer la commodité de la liaison de paramètres, les exemples de gestionnaires de routage suivants montrent comment lire les données de requête directement à partir de la requête :

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

    // ...
});

Liaison de paramètre explicite

Les attributs peuvent être utilisés pour déclarer explicitement à partir d’où les paramètres sont liés.

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 Source de liaison
id valeur de routage avec le nom id
page chaîne de requête avec le nom "p"
service Fourni par l’injection de dépendances
contentType en-tête avec le nom "Content-Type"

Note

La liaison à partir de valeurs de formulaire n’est pas prise en charge en mode natif dans .NET.

Liaison de paramètre avec DI

La liaison de paramètre pour les API minimales lie des paramètres via l’injection de dépendance quand le type est configuré en tant que service. Il n’est pas nécessaire d’appliquer explicitement l’attribut [FromServices] à un paramètre. Dans le code suivant, les deux actions retournent l’heure :

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

Paramètres facultatifs

Les paramètres déclarés dans les gestionnaires d’itinéraire sont traités comme requis :

  • Si une requête correspond à l’itinéraire, le gestionnaire d’itinéraire s’exécute uniquement si tous les paramètres requis sont fournis dans la requête.
  • Le fait de ne pas fournir tous les paramètres requis entraîne une erreur.
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 retourné
/products BadHttpRequestException : le paramètre obligatoire « int pageNumber » n’a pas été fourni à partir de la chaîne de requête.
/products/1 Erreur HTTP 404, aucune route correspondante

Pour rendre pageNumber facultatif, définissez le type comme facultatif ou fournissez une valeur par défaut :

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 retourné
/products 1 retourné
/products2 1 retourné

La valeur nullable et la valeur par défaut précédentes s’appliquent à toutes les sources :

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

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

app.Run();

Le code précédent appelle la méthode avec un produit null si aucun corps de requête n’est envoyé.

REMARQUE : Si des données non valides sont fournies et que le paramètre est nullable, le gestionnaire de routage n’est pas exécuté.

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 retourné
/products 1 retourné
/products?pageNumber=two BadHttpRequestException : Échec de la liaison du paramètre "Nullable<int> pageNumber" à partir de « two ».
/products/two Erreur HTTP 404, aucune route correspondante

Pour plus d’informations, consultez la section Échecs de liaison.

Types spéciaux

Les types suivants sont liés sans attributs explicites :

  • HttpContext : contexte qui contient toutes les informations sur la requête ou la réponse HTTP actuelle :

    app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
    
  • HttpRequest et HttpResponse : requête HTTP et réponse HTTP :

    app.MapGet("/", (HttpRequest request, HttpResponse response) =>
        response.WriteAsync($"Hello World {request.Query["name"]}"));
    
  • CancellationToken : jeton d’annulation associé à la requête HTTP actuelle :

    app.MapGet("/", async (CancellationToken cancellationToken) => 
        await MakeLongRunningRequestAsync(cancellationToken));
    
  • ClaimsPrincipal : utilisateur associé à la requête, lié à partir de HttpContext.User :

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

Liaison personnalisée

Il existe deux façons de personnaliser la liaison de paramètres :

  1. Pour les sources de liaison de routage, de requête et d’en-tête, liez des types personnalisés en ajoutant une méthode statique TryParse pour le type.
  2. Contrôlez le processus de liaison en implémentant une méthode BindAsync sur un type.

TryParse

TryParse a deux API :

public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);

Le code suivant affiche Point: 12.3, 10.1 avec 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 possède les API suivantes :

public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);

Le code suivant affiche SortBy:xyz, SortDirection:Desc, CurrentPage:99 avec 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
}

Échecs de liaison

En cas d’échec de la liaison, le framework enregistre un message de débogage et retourne différents codes d’état au client en fonction du mode d’échec.

Mode d’échec Type de paramètre nullable Source de liaison Code statut
{ParameterType}.TryParse retourne « false » yes route/query/header 400
{ParameterType}.BindAsync retourne « null » yes custom 400
Exceptions {ParameterType}.BindAsync n’a pas d’importance custom 500
Échec de la désérialisation du corps JSON n’a pas d’importance body 400
Type de contenu incorrect (pas application/json) n’a pas d’importance body 415

Priorité de liaison

Règles permettant de déterminer une source de liaison à partir d’un paramètre :

  1. Attribut explicite défini sur le paramètre (attributs From*) dans l’ordre suivant :
    1. Valeurs d’itinéraire : [FromRoute]
    2. Chaîne de requête : [FromQuery]
    3. En-tête : [FromHeader]
    4. Corps : [FromBody]
    5. Service : [FromServices]
  2. Types spéciaux
    1. HttpContext
    2. HttpRequest (HttpContext.Request)
    3. HttpResponse (HttpContext.Response)
    4. ClaimsPrincipal (HttpContext.User)
    5. CancellationToken (HttpContext.RequestAborted)
  3. Le type de paramètre a une méthode BindAsync valide.
  4. Le type de paramètre est une chaîne ou a une méthode TryParse valide.
    1. Si le nom du paramètre existe dans le modèle de route. Dans app.Map("/todo/{id}", (int id) => {});, id est lié à partir de la route.
    2. Lié à partir de la chaîne de requête.
  5. Si le type de paramètre est un service fourni par l’injection de dépendances, il utilise ce service comme source.
  6. Le paramètre provient du corps.

Personnaliser la liaison JSON

La source de liaison de corps utilise System.Text.Json pour la désérialisation. Il n’est pas possible de modifier cette valeur par défaut, mais la liaison peut être personnalisée à l’aide des autres techniques décrites précédemment. Pour personnaliser les options de sérialiseur JSON, utilisez un code similaire à ce qui suit :

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

Le code précédent :

  • Configure les options JSON par défaut d’entrée et de sortie.
  • Retourne le code JSON suivant :
    {
      "id": 1,
      "name": "Joe Smith"
    }
    
    Lors de la publication
    {
      "Id": 1,
      "Name": "Joe Smith"
    }
    

Lire le corps de la requête

Lisez le corps de la requête directement à l’aide d’un paramètre HttpContext ou 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();

Le code précédent :

  • Accède au corps de la requête à l’aide de HttpRequest.BodyReader.
  • Copie le corps de la requête dans un fichier local.

Responses

Les gestionnaires de routes prennent en charge les types de valeurs de retour suivants :

  1. Basé sur IResult : cela inclut Task<IResult> et ValueTask<IResult>
  2. string - Cela comprend Task<string> et ValueTask<string>
  3. T (Tout autre type) : cela inclut Task<T> et ValueTask<T>
Valeur retournée Behavior Content-Type
IResult Le framework appelle IResult.ExecuteAsync Décidé par l’implémentation IResult
string Le framework écrit la chaîne directement dans la réponse text/plain
T (Tout autre type) Le framework sérialise la réponse en JSON application/json

Exemples de valeurs de retour

Valeurs de retour string

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

Valeurs de retour JSON

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

Valeurs de retour IResult

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

L’exemple suivant utilise les types de résultats intégrés pour personnaliser la réponse :

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" }));
Code d’état personnalisé
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"));

Résultats intégrés

Des fonctions d’assistance de résultats courants existent dans la classe statique Microsoft.AspNetCore.Http.Results.

Description Type de réponse Code d’état API
Écrire une réponse JSON avec des options avancées application/json 200 Results.Json
Écrire une réponse JSON application/json 200 Results.Ok
Écrire une réponse texte text/plain (par défaut), configurable 200 Results.Text
Écrire la réponse sous forme d’octets application/octet-stream (par défaut), configurable 200 Results.Bytes
Écrire un flux d’octets dans la réponse application/octet-stream (par défaut), configurable 200 Results.Stream
Diffuser un fichier dans la réponse à télécharger avec l’en-tête content-disposition application/octet-stream (par défaut), configurable 200 Results.File
Définir le code d’état sur 404, avec une réponse JSON facultative N/A 404 Results.NotFound
Définir le code d’état sur 204 N/A 204 Results.NoContent
Définir le code d’état sur 422, avec une réponse JSON facultative N/A 422 Results.UnprocessableEntity
Définir le code d’état sur 400, avec une réponse JSON facultative N/A 400 Results.BadRequest
Définir le code d’état sur 409, avec une réponse JSON facultative N/A 409 Results.Conflict
Écrire un objet JSON des détails du problème dans la réponse N/A 500 (par défaut), configurable Results.Problem
Écrire un objet JSON des détails du problème dans la réponse avec des erreurs de validation N/A Sans objet, configurable Results.ValidationProblem

Personnalisation des résultats

Les applications peuvent contrôler les réponses en implémentant un type IResult personnalisé. Le code suivant est un exemple de type de résultat 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);
    }
}

Nous vous recommandons d’ajouter une méthode d’extension à Microsoft.AspNetCore.Http.IResultExtensions pour rendre ces résultats personnalisés plus détectables.

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

Les itinéraires peuvent être protégés à l’aide de stratégies d’autorisation. Celles-ci peuvent être déclarées via l’attribut [Authorize] ou à l’aide de la méthode RequireAuthorization :

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

Le code précédent peut être écrit avec RequireAuthorization :

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

L’exemple suivant utilise l’autorisation basée sur la stratégie :

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

Autoriser les utilisateurs non authentifiés à accéder à un point de terminaison

[AllowAnonymous] permet aux utilisateurs non authentifiés d’accéder aux points de terminaison :

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


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

CORS

Les itinéraires peuvent avoir CORS activé à l’aide de stratégies CORS. CORS peut être déclaré via l’attribut [EnableCors] ou à l’aide de la méthode RequireCors. Les exemples suivants activent 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();

Pour plus d’informations, consultez Activation les requêtes cross-origin (CORS) dans ASP.NET Core

Voir aussi

Prise en charge d’OpenAPI dans les API minimales