Minimal-APIs: Kurzreferenz

Dieses Dokument hat folgende Eigenschaften:

Die Minimal-APIs bestehen aus:

WebApplication

Der folgende Code wird von einer ASP.NET Core-Vorlage generiert:

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

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

app.Run();

Der vorstehende Code kann über dotnet new web in der Befehlszeile oder durch Auswahl der leeren Webvorlage in Visual Studio erstellt werden.

Mit dem folgenden Code wird eine WebApplication (app) erstellt, ohne explizit einen WebApplicationBuilder zu erstellen:

var app = WebApplication.Create(args);

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

app.Run();

WebApplication.Create initialisiert eine neue Instanz der WebApplication-Klasse mit vorkonfigurierten Standardwerten.

WebApplication fügt Minimal API applications abhängig von bestimmten Bedingungen automatisch die folgende Middleware hinzu:

  • UseDeveloperExceptionPage wird zuerst hinzugefügt, wenn HostingEnvironment gleich "Development" ist.
  • UseRouting wird zweitens hinzugefügt, wenn der Benutzercode UseRouting noch nicht aufgerufen hat, und wenn Endpunkte konfiguriert sind, z. B. app.MapGet.
  • UseEndpoints wird am Ende der Middlewarepipeline hinzugefügt, wenn Endpunkte konfiguriert sind.
  • UseAuthentication wird unmittelbar nach UseRouting hinzugefügt, wenn der Benutzercode UseAuthentication noch nicht aufgerufen hat und wenn IAuthenticationSchemeProvider im Dienstanbieter erkannt werden kann. IAuthenticationSchemeProvider wird standardmäßig hinzugefügt, wenn die Verwendung AddAuthentication von Diensten mit IServiceProviderIsService erkannt wird.
  • UseAuthorization wird als Nächstes hinzugefügt, wenn der Benutzercode UseAuthorization noch nicht aufgerufen hat und wenn IAuthorizationHandlerProvider im Dienstanbieter erkannt werden kann. IAuthorizationHandlerProvider wird standardmäßig hinzugefügt, wenn AddAuthorization verwendet wird, und Dienste mit IServiceProviderIsService erkannt werden.
  • Benutzerkonfigurierte Middleware und Endpunkte werden zwischen UseRouting und UseEndpoints hinzugefügt.

Nachfolgend sehen Sie den Code, der von der automatischen Middleware erzeugt wird, die zur App hinzugefügt wird:

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

app.UseRouting();

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

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

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

app.UseEndpoints(e => {});

In einigen Fällen eignet sich die standardmäßige Middleware-Konfiguration nicht für die App und muss geändert werden. Beispielsweise sollte UseCors vor UseAuthentication und UseAuthorization aufgerufen werden. Die App muss UseAuthentication und UseAuthorization aufrufen, wenn UseCors aufgerufen wird:

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

Wenn Middleware ausgeführt werden muss, bevor der Routenabgleich erfolgt, muss UseRouting aufgerufen werden, und die Middleware muss vor dem Aufruf von UseRouting platziert werden. UseEndpoints ist in diesem Fall nicht erforderlich, da es wie zuvor beschrieben automatisch hinzugefügt wird:

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

app.UseRouting();

// other middleware and endpoints

Beim Hinzufügen einer Terminal-Middleware:

  • Die Middleware muss nach UseEndpoints hinzugefügt werden.
  • Die App muss UseRouting und UseEndpoints aufrufen, damit die Terminal-Middleware an der richtigen Position platziert werden kann.
app.UseRouting();

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

app.UseEndpoints(e => {});

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

Terminal-Middleware ist Middleware, die ausgeführt wird, wenn kein Endpunkt die Anforderung verarbeitet.

Arbeiten mit Ports

Beim Erstellen einer Web-App mit Visual Studio oder dotnet new wird eine Datei Properties/launchSettings.json erstellt, die die Ports angibt, an denen die Anwendung antwortet. In den folgenden Beispielen für Porteinstellungen wird beim Ausführen der App in Visual Studio ein Fehlerdialogfeld Unable to connect to web server 'AppName' angezeigt. Visual Studio gibt einen Fehler zurück, da der in Properties/launchSettings.jsonangegebene Port erwartet wird, die App jedoch den von app.Run("http://localhost:3000") angegebenen Port verwendet. Führen Sie die folgenden Beispiele für Portänderungen über die Befehlszeile aus.

In den folgenden Abschnitten wird der Port festgelegt, auf den die App reagiert.

var app = WebApplication.Create(args);

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

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

Im vorangehenden Code antwortet die App auf Port 3000.

Mehrere Ports

Im folgenden Code antwortet die App auf Port 3000 und 4000.

var app = WebApplication.Create(args);

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

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

app.Run();

Festlegen des Ports über die Befehlszeile

Mit dem folgenden Befehl antwortet die App auf Port 7777:

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

Wenn der Endpunkt Kestrel ebenfalls in der Datei appsettings.json konfiguriert ist, wird die in der Datei appsettings.json angegebene URL verwendet. Weitere Informationen finden Sie unter Kestrel-Endpunktkonfiguration.

Lesen des Ports aus der Umgebung

Der folgende Code liest den Port aus der Umgebung:

var app = WebApplication.Create(args);

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

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

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

Die bevorzugte Methode zur Festlegung des Ports über die Umgebung ist die Verwendung der Umgebungsvariablen ASPNETCORE_URLS, die im folgenden Abschnitt beschrieben wird.

Festlegen der Ports über die ASPNETCORE_URLS-Umgebungsvariable

Für die Festlegung des Ports steht die Umgebungsvariable ASPNETCORE_URLS zur Verfügung:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS unterstützt mehrere URLs:

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

Lauschen an allen Schnittstellen

Die folgenden Beispiele veranschaulichen das Lauschen an allen Schnittstellen.

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

Lauschen an allen Schnittstellen mit ASPNETCORE_URLS

In den vorherigen Beispielen kann ASPNETCORE_URLS verwendet werden.

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

Überwachen aller Schnittstellen mithilfe von ASPNETCORE_HTTPS_PORTS

In den vorherigen Beispielen können ASPNETCORE_HTTPS_PORTS und ASPNETCORE_HTTP_PORTS verwendet werden.

ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000

Weitere Informationen finden Sie unter Konfigurieren von Endpunkten für den Kestrel-Webserver von ASP.NET Core.

Angeben von HTTPS mit Entwicklungszertifikat

var app = WebApplication.Create(args);

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

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

app.Run();

Weitere Informationen über das Entwicklungszertifikat finden Sie unter Vertrauen Sie dem ASP.NET Core-HTTPS-Entwicklungszertifikat unter Windows und macOS.

Angeben von HTTPS mithilfe eines benutzerdefinierten Zertifikats

Die folgenden Abschnitte zeigen, wie das benutzerdefinierte Zertifikat mithilfe der Datei appsettings.json und über die Konfiguration angegeben wird.

Angeben des benutzerdefinierten Zertifikats mit appsettings.json

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

Angeben des benutzerdefinierten Zertifikats über die Konfiguration

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

Verwenden der Zertifikat-APIs

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

Lesen der Umgebung

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

Weitere Informationen zur Verwendung der Umgebung finden Sie unter Verwenden mehrerer Umgebungen in ASP.NET Core.

Konfiguration

Der folgende Code liest Informationen aus dem Konfigurationssystem:

var app = WebApplication.Create(args);

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

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

app.Run();

Weitere Informationen finden Sie unter Konfiguration in ASP.NET Core.

Protokollierung

Der folgende Code schreibt eine Meldung in das Anwendungsstartprotokoll:

var app = WebApplication.Create(args);

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

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

app.Run();

Weitere Informationen finden Sie unter Protokollieren in .NET Core und ASP.NET Core.

Zugreifen auf den Container für Abhängigkeitsinjektion

Der folgende Code zeigt, wie Dienste während des Anwendungsstarts aus dem Abhängigkeitsinjektionscontainer abzurufen sind:


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

Der folgende Code zeigt, wie Sie mit dem [FromKeyedServices]-Attribut auf Schlüssel aus dem DI-Container (Dependency Injection, Abhängigkeitsinjektion) zugreifen können:

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

Weitere Informationen zu DI finden Sie unter Abhängigkeitsinjektion in ASP.NET Core.

WebApplicationBuilder

Dieser Abschnitt enthält Beispielcode unter Verwendung von WebApplicationBuilder.

Ändern von Inhaltsstamm, Anwendungsname und Umgebung

Der folgende Code legt den Inhaltsstamm, den Anwendungsnamen und die Umgebung fest:

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 Initialisiert eine neue Instanz der WebApplicationBuilder-Klasse mit vorkonfigurierten Standardwerten.

Weitere Informationen finden Sie unter ASP.NET Core – Grundlagenübersicht.

Ändern von Inhaltsstamm, App-Name und Umgebung über Umgebungsvariablen oder Befehlszeile

Die folgende Tabelle zeigt die Umgebungsvariablen und Befehlszeilenargumente, die zum Ändern von Inhaltsstamm, Anwendungsname und Umgebung verwendet werden:

Feature Umgebungsvariable Befehlszeilenargument
Anwendungsname ASPNETCORE_APPLICATIONNAME --applicationName
Umgebungsname ASPNETCORE_ENVIRONMENT --environment
Inhaltsstammverzeichnis ASPNETCORE_CONTENTROOT --contentRoot

Hinzufügen von Konfigurationsanbietern

Im folgenden Beispiel wird der INI-Konfigurationsanbieter hinzugefügt:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

Ausführliche Informationen finden Sie unter Dateikonfigurationsanbieter in Konfiguration in ASP.NET Core.

Lesen der Konfiguration

Standardmäßig liest die WebApplicationBuilder die Konfiguration aus mehreren Quellen, darunter:

  • appSettings.json und appSettings.{environment}.json
  • Umgebungsvariablen
  • Die Befehlszeile

Eine vollständige Liste der gelesenen Konfigurationsquellen finden Sie unter Standardkonfiguration in Konfiguration in ASP.NET Core.

Der folgende Code liest HelloKey aus der Konfiguration und zeigt den Wert am Endpunkt / an. Wenn der Konfigurationswert NULL ist, wird „Hello“ message zugewiesen:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Lesen der Umgebung

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Hinzufügen von Protokollierungsanbietern

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

Hinzufügen von Diensten

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

Anpassen von IHostBuilder

Vorhandene Erweiterungsmethoden für IHostBuilder können über die Host-Eigenschaft aufgerufen werden:

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

Anpassen von IWebHostBuilder

Erweiterungsmethoden für IWebHostBuilder können über die Eigenschaft WebApplicationBuilder.WebHost aufgerufen werden.

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

Ändern des Webstamms

Standardmäßig ist der Webstamm relativ zum Inhaltsstamm im Ordner wwwroot angegeben. Im Webstamm sucht die Middleware für statische Dateien nach statischen Dateien. Der Webstamm kann mit WebHostOptions, der Befehlszeile oder mit der Methode UseWebRoot geändert werden:

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

var app = builder.Build();

app.Run();

Container für benutzerdefinierte Abhängigkeitsinjektion

Im folgenden Beispiel wird Autofac verwendet:

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

Hinzufügen von Middleware

Für die WebApplication kann eine beliebige vorhandene ASP.NET Core-Middleware konfiguriert werden:

var app = WebApplication.Create(args);

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

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

app.Run();

Weitere Informationen finden Sie unter ASP.NET Core-Middleware.

Seite mit Ausnahmen für Entwickler

WebApplication.CreateBuilder initialisiert eine neue Instanz der WebApplicationBuilder-Klasse mit vorkonfigurierten Standardwerten. Die Seite mit Ausnahmen für Entwickler ist in den vorkonfigurierten Standardwerten aktiviert. Durch Ausführung des folgende Codes in der Entwicklungsumgebung wird beim Navigieren zu / eine benutzerfreundliche Seite geöffnet, auf der die Ausnahme angezeigt wird.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Run();

ASP.NET Core-Middleware

In der folgenden Tabelle werden einige der Middlewarekomponenten aufgeführt, die häufig mit Minimal-APIs verwendet wird.

Middleware Beschreibung API
Authentifizierung Bietet Unterstützung für Authentifizierungen. UseAuthentication
Autorisierung Bietet Unterstützung für Authentifizierungen UseAuthorization
CORS Konfiguriert die Ressourcenfreigabe zwischen verschiedenen Ursprüngen (Cross-Origin Resource Sharing, CORS). UseCors
Ausnahmehandler Behandelt global Ausnahmen, die von der Middlewarepipeline ausgelöst werden. UseExceptionHandler
Weitergeleitete Header Leitet Proxyheader an die aktuelle Anforderung weiter. UseForwardedHeaders
HTTPS-Umleitung Leitet alle HTTP-Anforderungen an HTTPS um. UseHttpsRedirection
HTTP Strict Transport Security (HSTS) Middleware für erweiterte Sicherheit, die einen besonderen Antwortheader hinzufügt. UseHsts
Anforderungsprotokollierung Bietet Unterstützung für die Protokollierung von HTTP-Anforderungen und -Antworten. UseHttpLogging
Anforderungstimeouts Bietet Unterstützung beim Konfigurieren von Anforderungstimeouts und globaler Standardwerte sowie bei der Konfiguration pro Endpunkt. UseRequestTimeouts
W3C-Anforderungsprotokollierung Bietet Unterstützung für die Protokollierung von HTTP-Anforderungen und -Antworten im W3C-Format. UseW3CLogging
Zwischenspeichern von Antworten Bietet Unterstützung für das Zwischenspeichern von Antworten. UseResponseCaching
Antwortkomprimierung Bietet Unterstützung für das Komprimieren von Antworten. UseResponseCompression
Sitzung Bietet Unterstützung für das Verwalten von Benutzersitzungen. UseSession
Statische Dateien Bietet Unterstützung für das Verarbeiten statischer Dateien und das Durchsuchen des Verzeichnisses. UseStaticFiles, UseFileServer
WebSockets Aktiviert das WebSockets-Protokoll. UseWebSockets

In den folgenden Abschnitten werden Routing, Parameterbindung und Antworten behandelt.

Routing

Eine konfigurierte WebApplication unterstützt Map{Verb} und MapMethods, wobei {Verb} eine HTTP-Methode mit Camel-Case-Schreibweise wie Get, Post, Put oder Delete ist:

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

Die an diese Methoden übergebenen Delegate-Argumente werden als „Routenhandler“ bezeichnet.

Routenhandler

Routenhandler sind Methoden, die ausgeführt werden, wenn die Route übereinstimmt. Als Routenhandler kann ein Lambdaausdruck, eine lokale Funktion, eine Instanzmethode oder eine statische Methode verwendet werden. Routenhandler können synchron oder asynchron sein.

Lambdaausdruck

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

Lokale Funktion

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

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

app.MapGet("/", LocalFunction);

app.Run();

für eine Instanzmethode

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

für eine statische Methode

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

Endpunkt außerhalb von Program.cs definiert

Minimale APIs müssen sich nicht in Program.cs befinden.

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

Weitere Informationen dazu finden Sie auch unter Routengruppen weiter unten in diesem Artikel.

Endpunkte können Namen erhalten, um URLs für den jeweiligen Endpunkt zu generieren. Durch die Verwendung eines benannten Endpunkts entfällt das Hartcodieren von Pfaden in einer App:

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

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

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

app.Run();

Der vorangehende Code zeigt The link to the hello endpoint is /hello vom Endpunkt / an.

HINWEIS: Für Endpunktnamen muss Groß-/Kleinschreibung beachtet werden.

Endpunktnamen:

  • Dieser muss global eindeutig sein.
  • Werden als OpenAPI-Vorgangs-ID verwendet, wenn die OpenAPI-Unterstützung aktiviert ist. Weitere Informationen finden Sie unter OpenAPI.

Routenparameter

Routenparameter können als Teil der Routenmusterdefinition erfasst werden:

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

Der vorstehende Code gibt The user id is 3 and book id is 7 aus dem URI /users/3/books/7 zurück.

Der Routenhandler kann die zu erfassende Parameter deklarieren. Bei einer Anforderung an eine Route mit Parametern, deren Erfassung deklariert wurde, werden die Parameter geparst und an den Handler übergeben. Dadurch können die Werte problemlos typsicher erfasst werden. Im vorangegangenen Code sind userId und bookId beide vom Typ int.

Wenn im vorstehenden Code einer der beiden Routenwerte nicht in den Typ int umgewandelt werden kann, wird eine Ausnahme ausgelöst. Die GET-Anforderung /users/hello/books/3 löst die folgende Ausnahme aus:

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

Platzhalterzeichen und Abfangen aller Routen

Der folgende Code zum Abfangen aller Routen gibt Routing to hello vom Endpunkt „/posts/hello“ zurück:

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

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

app.Run();

Routeneinschränkungen

Routeneinschränkungen schränken das Abgleichsverhalten einer Route ein.

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

Die folgende Tabelle zeigt die vorangegangenen Routenvorlagen und ihr Verhalten:

Routenvorlage Beispiel-URI für Übereinstimmung
/todos/{id:int} /todos/1
/todos/{text} /todos/something
/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

Weitere Informationen finden Sie unter Referenz für Routeneinschränkungen in Routing in ASP.NET Core.

Routengruppen

Die MapGroup-Erweiterungsmethode hilft, Gruppen von Endpunkten mit einem gemeinsamen Präfix zu organisieren. Sie reduziert sich wiederholenden Code und ermöglicht die benutzerdefinierte Anpassung ganzer Gruppen von Endpunkten mit einem einzigen Aufruf von Methoden wie RequireAuthorization und WithMetadata,die Endpunktmetadaten hinzufügen.

Der folgende Code erstellt beispielsweise zwei ähnliche Endpunktgruppen:

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

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


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

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

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

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

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

    return group;
}

In diesem Szenario können Sie eine relative Adresse für den Location-Header im 201 Created-Ergebnis verwenden:

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

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

Die erste Gruppe von Endpunkten entspricht nur Anforderungen mit dem Präfix /public/todos und ist ohne Authentifizierung zugänglich. Die zweite Gruppe von Endpunkten entspricht nur Anforderungen mit dem Präfix /private/todos und erfordert Authentifizierung.

Die QueryPrivateTodosEndpunktfilterfactory ist eine lokale Funktion, die die TodoDb-Parameter des Routenhandlers so ändert, dass Zugriff auf private Aufgabendaten zulässig ist und diese gespeichert werden können.

Routengruppen unterstützen auch geschachtelte Gruppen und komplexe Präfixmuster mit Routenparametern und -einschränkungen. Im folgenden Beispiel kann der der user-Gruppe zugeordnete Routenhandler die Routenparameter {org} und {group} erfassen, die in den Präfixen der äußeren Gruppe definiert sind.

Das Präfix kann auch leer sein. Dies kann hilfreich sein, um Endpunktmetadaten oder Filter einer Gruppe von Endpunkten hinzuzufügen, ohne das Routenmuster zu ändern.

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

Das Hinzufügen von Filtern oder Metadaten zu einer Gruppe verhält sich genauso wie das individuelle Hinzufügen zu jedem Endpunkt, bevor zusätzliche Filter oder Metadaten hinzugefügt werden, die einer inneren Gruppe oder einem bestimmten Endpunkt hinzugefügt wurden.

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

Im Beispiel oben protokolliert der äußere Filter die eingehende Anforderung vor dem inneren Filter, obwohl er als zweiter hinzugefügt wurde. Da die Filter auf verschiedene Gruppen angewendet wurden, spielt die Reihenfolge, in der sie relativ zueinander hinzugefügt wurden, keine Rolle. Die Reihenfolge, in der Filter hinzugefügt werden, spielt eine Rolle, wenn sie auf dieselbe Gruppe oder einen bestimmten Endpunkt angewendet werden.

Eine Anforderung an /outer/inner/ protokolliert Folgendes:

/outer group filter
/inner group filter
MapGet filter

Parameterbindung

Die Parameterbindung ist der Prozess der Umwandlung von Anforderungsdaten in stark typisierte Parameter, die durch Routenhandler ausgedrückt werden. Eine Bindungsquelle bestimmt, von wo aus Parameter gebunden werden. Bindungsquellen können basierend auf der HTTP-Methode und dem Parametertyp explizit sein oder abgeleitet werden.

Unterstützte Bindungsquellen:

  • Routenwerte
  • Abfragezeichenfolge
  • Header
  • Textkörper (als JSON)
  • Formularwerte
  • Von der Abhängigkeitsinjektion bereitgestellte Dienste
  • Benutzerdefiniert

Im folgenden Beispiel verwendet der GET-Routenhandler einige dieser Parameterbindungsquellen:

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

Die folgende Tabelle zeigt die Beziehung zwischen den im vorherigen Beispiel verwendeten Parametern und den zugeordneten Bindungsquellen.

Parameter Bindungsquelle
id Routenwert
page Abfragezeichenfolge
customHeader header
service Von der Abhängigkeitsinjektion bereitgestellt

Bei den HTTP-Methoden GET, HEAD, OPTIONS und DELETE erfolgt keine implizite Bindung aus dem Text. Um eine Bindung vom Textkörper (als JSON) für diese HTTP-Methoden zu verwenden, führen Sie explizit eine Bindung mit [FromBody] oder einen Lesevorgang aus HttpRequest durch.

Im folgenden Beispiel verwendet der POST-Routenhandler eine Bindungsquelle des Textkörpers (als JSON) für den Parameter person:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

record Person(string Name, int Age);

Die Parameter in den vorherigen Beispielen werden alle automatisch über Anforderungsdaten gebunden. Um die Benutzerfreundlichkeit der Parameterbindung zu veranschaulichen, zeigen die folgenden Routenhandler, wie Anforderungsdaten direkt aus der Anforderung gelesen werden:

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

    // ...
});

Explizite Parameterbindung

Attribute können verwendet werden, um explizit zu deklarieren, von wo Parameter gebunden werden.

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 Bindungsquelle
id Routenwert mit dem Namen id
page Abfragezeichenfolge mit dem Namen "p"
service Von der Abhängigkeitsinjektion bereitgestellt
contentType Header mit dem Namen "Content-Type"

Explizite Bindung aus Formularwerten

Das [FromForm]-Attribut bindet Formularwerte:

app.MapPost("/todos", async ([FromForm] string name,
    [FromForm] Visibility visibility, IFormFile? attachment, TodoDb db) =>
{
    var todo = new Todo
    {
        Name = name,
        Visibility = visibility
    };

    if (attachment is not null)
    {
        var attachmentName = Path.GetRandomFileName();

        using var stream = File.Create(Path.Combine("wwwroot", attachmentName));
        await attachment.CopyToAsync(stream);
    }

    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Ok();
});

// Remaining code removed for brevity.

Eine Alternative besteht darin, das [AsParameters]-Attribut mit einem benutzerdefinierten Typ zu verwenden, der über Eigenschaften verfügt, die mit [FromForm] versehen sind. Der folgende Code bindet z. B. von Formularwerten an Eigenschaften der NewTodoRequest-Datensatzstruktur:

app.MapPost("/ap/todos", async ([AsParameters] NewTodoRequest request, TodoDb db) =>
{
    var todo = new Todo
    {
        Name = request.Name,
        Visibility = request.Visibility
    };

    if (request.Attachment is not null)
    {
        var attachmentName = Path.GetRandomFileName();

        using var stream = File.Create(Path.Combine("wwwroot", attachmentName));
        await request.Attachment.CopyToAsync(stream);

        todo.Attachment = attachmentName;
    }

    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Ok();
});

// Remaining code removed for brevity.
public record struct NewTodoRequest([FromForm] string Name,
    [FromForm] Visibility Visibility, IFormFile? Attachment);

Weitere Informationen finden Sie im Abschnitt zu AsParameters weiter unten in diesem Artikel.

Den vollständigen Beispielcode finden Sie im AspNetCore.Docs.Samples-Repository.

Sichere Bindung von IFormFile und IFormFileCollection

Komplexe Formularbindung wird unter Verwendung von IFormFile und IFormFileCollection mithilfe von [FromForm] unterstützt:

using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAntiforgery();

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

// Generate a form with an anti-forgery token and an /upload endpoint.
app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
    var token = antiforgery.GetAndStoreTokens(context);
    var html = MyUtils.GenerateHtmlForm(token.FormFieldName, token.RequestToken!);
    return Results.Content(html, "text/html");
});

app.MapPost("/upload", async Task<Results<Ok<string>, BadRequest<string>>>
    ([FromForm] FileUploadForm fileUploadForm, HttpContext context,
                                                IAntiforgery antiforgery) =>
{
    await MyUtils.SaveFileWithName(fileUploadForm.FileDocument!,
              fileUploadForm.Name!, app.Environment.ContentRootPath);
    return TypedResults.Ok($"Your file with the description:" +
        $" {fileUploadForm.Description} has been uploaded successfully");
});

app.Run();

Parameter, die an die Anforderung mit [FromForm] gebunden sind, enthalten ein Anti-Fälschungstoken. Das Anti-Fälschungstoken wird überprüft, wenn die Anforderung verarbeitet wird. Weitere Informationen finden Sie unter Schutz vor Fälschung mit Minimal APIs.

Weitere Informationen finden Sie unter Formularbindung in Minimal-APIs.

Den vollständigen Beispielcode finden Sie im AspNetCore.Docs.Samples-Repository.

Parameterbindung mit Abhängigkeitsinjektion

Parameterbindung für minimale APIs bindet Parameter durch Abhängigkeitsinjektion (Dependency Injection, DI), wenn der Typ als Dienst konfiguriert wird. Es ist nicht erforderlich, das [FromServices]-Attribut explizit auf einen Parameter anzuwenden. Im folgenden Code geben beide Aktionen die Uhrzeit zurück:

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

Optionale Parameter

In Routenhandlern deklarierte Parameter werden als erforderlich behandelt:

  • Wenn eine Anforderung der Route entspricht, wird der Routenhandler nur ausgeführt, wenn alle erforderlichen Parameter in der Anforderung angegeben sind.
  • Sind nicht alle erforderlichen Parameter enthalten, kommt es zu einem Fehler.
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 zurückgegeben
/products BadHttpRequestException: Der erforderliche Parameter „int pageNumber“ wurde nicht in der Abfragezeichenfolge bereitgestellt.
/products/1 HTTP-Fehler vom Typ 404, keine übereinstimmende Route

Um pageNumber als optional festzulegen, definieren Sie den Typ als optional, oder geben Sie einen Standardwert an:

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 zurückgegeben
/products 1 zurückgegeben
/products2 1 zurückgegeben

Der vorangehende Nullwerte zulassende und Standardwert gilt für alle Quellen:

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

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

app.Run();

Der stehende Code ruft die Methode mit einem NULL-Produkt auf, wenn kein Anforderungstext gesendet wird.

HINWEIS: Wenn ungültige Daten angegeben werden und der Parameter Nullwerte zulässt, wird der Routenhandler nicht ausgeführt.

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 zurückgegeben
/products 1 zurückgegeben
/products?pageNumber=two BadHttpRequestException: Fehler beim Binden von Parameter "Nullable<int> pageNumber" aus „two“.
/products/two HTTP-Fehler vom Typ 404, keine übereinstimmende Route

Weitere Informationen finden Sie im Abschnitt Bindungsfehler.

Sondertypen

Die folgenden Typen werden ohne explizite Attribute gebunden:

  • HttpContext: Der Kontext, der alle Informationen zur aktuellen HTTP-Anforderung oder -Antwort enthält:

    app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
    
  • HttpRequest und HttpResponse: die HTTP-Anforderung und HTTP-Antwort:

    app.MapGet("/", (HttpRequest request, HttpResponse response) =>
        response.WriteAsync($"Hello World {request.Query["name"]}"));
    
  • CancellationToken: das mit der aktuellen HTTP-Anforderung verknüpfte Abbruchtoken:

    app.MapGet("/", async (CancellationToken cancellationToken) => 
        await MakeLongRunningRequestAsync(cancellationToken));
    
  • ClaimsPrincipal: der mit der Anforderung verknüpfte Benutzer, gebunden aus HttpContext.User:

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

Binden des Anforderungstexts als Stream oder PipeReader

Der Anforderungstext kann als Stream oder PipeReader gebunden werden, um effizient Szenarien zu unterstützen, in denen der Benutzer Daten verarbeiten und Folgendes tun muss:

  • Daten im Blobspeicher speichern oder Daten in die Warteschlange eines Warteschlangenanbieters einreihen.
  • Die gespeicherten Daten mit einem Workerprozess oder einer Cloudfunktion verarbeiten.

Beispielsweise werden die Daten möglicherweise in Azure Queue Storage eingereiht oder in Azure Blob Storage gespeichert.

Der folgende Code implementiert eine Hintergrundwarteschlange:

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

Der folgende Code bindet den Anforderungstext an einen 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);
});

Der folgende Code veranschaulicht die vollständige Program.cs-Datei:

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();
  • Beim Lesen von Daten ist der Stream dasselbe Objekt wie HttpRequest.Body.
  • Der Anforderungstext wird standardmäßig nicht gepuffert. Nachdem der Textkörper gelesen wurde, ist eine Rückkehr zum Anfang nicht möglich. Der Datenstrom kann nicht mehrmals gelesen werden.
  • Stream und PipeReader sind außerhalb des minimalen Aktionshandlers nicht verwendbar, da die zugrunde liegenden Puffer verworfen oder wiederverwendet werden.

Dateiuploads mit IFormFile und IFormFileCollection

Der folgende Code verwendet IFormFile und IFormFileCollection zum Hochladen der Datei:

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

Authentifizierte Dateiuploadanforderungen werden mithilfe eines Autorisierungsheaders, eines Clientzertifikats oder eines cookie-Headers unterstützt.

Bindung an Formulare mit IFormCollection, IFormFile und IFormFileCollection

Die Bindung von formularbasierten Parametern mit IFormCollection, IFormFileund IFormFileCollection wird unterstützt. OpenAPI-Metadaten werden für Formularparameter abgeleitet, um die Integration mit der Swagger-Benutzeroberfläche zu unterstützen.

Der folgende Code lädt Dateien mithilfe der abgeleiteten Bindung vom IFormFile-Typ hoch:

using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAntiforgery();

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

string GetOrCreateFilePath(string fileName, string filesDirectory = "uploadFiles")
{
    var directoryPath = Path.Combine(app.Environment.ContentRootPath, filesDirectory);
    Directory.CreateDirectory(directoryPath);
    return Path.Combine(directoryPath, fileName);
}

async Task UploadFileWithName(IFormFile file, string fileSaveName)
{
    var filePath = GetOrCreateFilePath(fileSaveName);
    await using var fileStream = new FileStream(filePath, FileMode.Create);
    await file.CopyToAsync(fileStream);
}

app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
    var token = antiforgery.GetAndStoreTokens(context);
    var html = $"""
      <html>
        <body>
          <form action="/upload" method="POST" enctype="multipart/form-data">
            <input name="{token.FormFieldName}" type="hidden" value="{token.RequestToken}"/>
            <input type="file" name="file" placeholder="Upload an image..." accept=".jpg, 
                                                                            .jpeg, .png" />
            <input type="submit" />
          </form> 
        </body>
      </html>
    """;

    return Results.Content(html, "text/html");
});

app.MapPost("/upload", async Task<Results<Ok<string>,
   BadRequest<string>>> (IFormFile file, HttpContext context, IAntiforgery antiforgery) =>
{
    var fileSaveName = Guid.NewGuid().ToString("N") + Path.GetExtension(file.FileName);
    await UploadFileWithName(file, fileSaveName);
    return TypedResults.Ok("File uploaded successfully!");
});

app.Run();

Warnung: Bei der Implementierung von Formularen muss die App Angriffe des Typs Cross-Site Request Forgery (XSRF/CSRF) verhindern. Im vorherigen Code wird der IAntiforgery-Dienst verwendet, um XSRF-Angriffe zu verhindern, indem ein Fälschungssicherheitstoken generiert und validiert wird:

using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAntiforgery();

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

string GetOrCreateFilePath(string fileName, string filesDirectory = "uploadFiles")
{
    var directoryPath = Path.Combine(app.Environment.ContentRootPath, filesDirectory);
    Directory.CreateDirectory(directoryPath);
    return Path.Combine(directoryPath, fileName);
}

async Task UploadFileWithName(IFormFile file, string fileSaveName)
{
    var filePath = GetOrCreateFilePath(fileSaveName);
    await using var fileStream = new FileStream(filePath, FileMode.Create);
    await file.CopyToAsync(fileStream);
}

app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
    var token = antiforgery.GetAndStoreTokens(context);
    var html = $"""
      <html>
        <body>
          <form action="/upload" method="POST" enctype="multipart/form-data">
            <input name="{token.FormFieldName}" type="hidden" value="{token.RequestToken}"/>
            <input type="file" name="file" placeholder="Upload an image..." accept=".jpg, 
                                                                            .jpeg, .png" />
            <input type="submit" />
          </form> 
        </body>
      </html>
    """;

    return Results.Content(html, "text/html");
});

app.MapPost("/upload", async Task<Results<Ok<string>,
   BadRequest<string>>> (IFormFile file, HttpContext context, IAntiforgery antiforgery) =>
{
    var fileSaveName = Guid.NewGuid().ToString("N") + Path.GetExtension(file.FileName);
    await UploadFileWithName(file, fileSaveName);
    return TypedResults.Ok("File uploaded successfully!");
});

app.Run();

Weitere Informationen zu XSRF-Angriffen finden Sie unter Fälschungsschutz mit minimalen APIs

Weitere Informationen finden Sie unter Formularbindung in Minimal-APIs.

Binden an Sammlungen und komplexe Typen aus Formularen

Die Bindung wird unterstützt für:

  • Sammlungen, z. B . Liste und Wörterbuch
  • Komplexe Typen, z. B. Todo oder Project

Dies wird im folgenden Code veranschaulicht:

  • Ein minimaler Endpunkt, der eine mehrteilige Formulareingabe an ein komplexes Objekt bindet.
  • Verwenden der Antifälschungsdienste zur Unterstützung der Generierung und Validierung von Antifälschungstoken
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAntiforgery();

var app = builder.Build();

app.UseAntiforgery();

app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
    var token = antiforgery.GetAndStoreTokens(context);
    var html = $"""
        <html><body>
           <form action="/todo" method="POST" enctype="multipart/form-data">
               <input name="{token.FormFieldName}" 
                                type="hidden" value="{token.RequestToken}" />
               <input type="text" name="name" />
               <input type="date" name="dueDate" />
               <input type="checkbox" name="isCompleted" value="true" />
               <input type="submit" />
               <input name="isCompleted" type="hidden" value="false" /> 
           </form>
        </body></html>
    """;
    return Results.Content(html, "text/html");
});

app.MapPost("/todo", async Task<Results<Ok<Todo>, BadRequest<string>>> 
               ([FromForm] Todo todo, HttpContext context, IAntiforgery antiforgery) =>
{
    try
    {
        await antiforgery.ValidateRequestAsync(context);
        return TypedResults.Ok(todo);
    }
    catch (AntiforgeryValidationException e)
    {
        return TypedResults.BadRequest("Invalid anti-forgery token");
    }
});

app.Run();

class Todo
{
    public string Name { get; set; } = string.Empty;
    public bool IsCompleted { get; set; } = false;
    public DateTime DueDate { get; set; } = DateTime.Now.Add(TimeSpan.FromDays(1));
}

Für den Code oben gilt:

  • Der Zielparameter muss mit dem [FromForm]-Attribut versehen werden, um ihn von den Parametern zu unterscheiden, die aus dem JSON-Text gelesen werden sollen.
  • Die Bindung von komplexen Typen oder Collectiontypen wird für minimale APIs, die mit dem Anforderungsdelegat-Generator kompiliert werden, nicht unterstützt.
  • Das Markup enthält eine zusätzliche ausgeblendete Eingabe mit dem Namen isCompleted und dem Wert false. Wenn das Kontrollkästchen isCompleted beim Senden des Formulars aktiviert ist, werden die Werte true und false beide als Werte übermittelt. Ist das Kontrollkästchen nicht aktiviert, wird nur der Wert false der ausgeblendeten Eingabe übermittelt. Der ASP.NET Core-Modellbindungsprozess liest bei der Bindung an einen bool-Wert nur den ersten Wert. Dies führt bei aktivierten Kontrollkästchen zum Ergebnis true und bei nicht aktivierten Kontrollkästchen zum Ergebnis false.

Ein Beispiel für die an den vorherigen Endpunkt übermittelten Formulardaten sieht wie folgt aus:

__RequestVerificationToken: CfDJ8Bveip67DklJm5vI2PF2VOUZ594RC8kcGWpTnVV17zCLZi1yrs-CSz426ZRRrQnEJ0gybB0AD7hTU-0EGJXDU-OaJaktgAtWLIaaEWMOWCkoxYYm-9U9eLV7INSUrQ6yBHqdMEE_aJpD4AI72gYiCqc
name: Walk the dog
dueDate: 2024-04-06
isCompleted: true
isCompleted: false

Binden von Arrays und Zeichenfolgenwerten aus Headern und Abfragezeichenfolgen

Der folgende Code veranschaulicht die Bindung von Abfragezeichenfolgen an ein Array von primitiven Typen, Zeichenfolgenarrays und 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]}");

Die Bindung von Abfragezeichenfolgen oder Headerwerten an ein Array komplexer Typen wird unterstützt, wenn im Typ TryParse implementiert ist. Der folgende Code bindet an ein Zeichenfolgenarray und gibt alle Elemente mit den angegebenen Tags zurück:

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

Der folgende Code zeigt das Modell und die erforderliche TryParse-Implementierung:

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

Der folgende Code richtet eine Bindung mit einem int-Array ein:

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

Fügen Sie zum Testen des vorherigen Codes den folgenden Endpunkt hinzu, um die Datenbank mit Todo-Elementen aufzufüllen:

// POST /todoitems/batch
app.MapPost("/todoitems/batch", async (Todo[] todos, TodoDb db) =>
{
    await db.Todos.AddRangeAsync(todos);
    await db.SaveChangesAsync();

    return Results.Ok(todos);
});

Verwenden Sie ein Tool wie HttpRepl, um die folgenden Daten an den vorherigen Endpunkt zu übergeben:

[
    {
        "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"
        }
    }
]

Der folgende Code bindet an den Headerschlüssel X-Todo-Id und gibt die Todo-Elemente mit übereinstimmenden Id-Werten zurück:

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

Hinweis

Beim Binden eines string[]-Werts aus einer Abfragezeichenfolge führt das Fehlen einer übereinstimmenden Abfragezeichenfolge zu einem leeren Array anstelle eines NULL-Werts.

Parameterbindung für Argumentlisten mit [AsParameters]

AsParametersAttribute ermöglicht einfache Parameterbindung an Typen und nicht komplexe oder rekursive Modellbindung.

Betrachten Sie folgenden Code:

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.

Betrachten Sie den folgenden GET-Endpunkt:

app.MapGet("/todoitems/{id}",
                             async (int Id, TodoDb Db) =>
    await Db.Todos.FindAsync(Id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());

Mit der folgenden struct können die vorherigen hervorgehobenen Parameter ersetzt werden:

struct TodoItemRequest
{
    public int Id { get; set; }
    public TodoDb Db { get; set; }
}

Der umgestaltete GET-Endpunkt verwendet die vorherige struct mit dem AsParameters-Attribut:

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

Der folgende Code zeigt zusätzliche Endpunkte in der App:

app.MapPost("/todoitems", async (TodoItemDTO Dto, TodoDb Db) =>
{
    var todoItem = new Todo
    {
        IsComplete = Dto.IsComplete,
        Name = Dto.Name
    };

    Db.Todos.Add(todoItem);
    await Db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});

app.MapPut("/todoitems/{id}", async (int Id, TodoItemDTO Dto, TodoDb Db) =>
{
    var todo = await Db.Todos.FindAsync(Id);

    if (todo is null) return Results.NotFound();

    todo.Name = Dto.Name;
    todo.IsComplete = Dto.IsComplete;

    await Db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/todoitems/{id}", async (int Id, TodoDb Db) =>
{
    if (await Db.Todos.FindAsync(Id) is Todo todo)
    {
        Db.Todos.Remove(todo);
        await Db.SaveChangesAsync();
        return Results.Ok(new TodoItemDTO(todo));
    }

    return Results.NotFound();
});

Die folgenden Klassen werden verwendet, um die Parameterlisten umzugestalten:

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

Der folgende Code zeigt die umgestalteten Endpunkte mit AsParameters und der vorherigen struct und Klassen:

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

Die folgenden record-Typen können verwendet werden, um die vorherigen Parameter zu ersetzen:

record TodoItemRequest(int Id, TodoDb Db);
record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);

Die Verwendung einer struct mit AsParameters kann effizienter sein als die Verwendung eines record-Typs.

Den vollständigen Beispielcode finden Sie im AspNetCore.Docs.Samples-Repository.

Benutzerdefinierte Bindung

Es gibt zwei Möglichkeiten zum Anpassen der Parameterbindung:

  1. Binden Sie für Routen-, Abfrage- und Headerbindungsquellen benutzerdefinierte Typen, indem Sie eine statische TryParse-Methode für den Typ hinzufügen.
  2. Steuern Sie den Bindungsprozess, indem Sie eine BindAsync-Methode für einen Typ implementieren.

TryParse

TryParse umfasst zwei APIs:

public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);

Der folgende Code zeigt Point: 12.3, 10.1 mit dem URI /map?Point=12.3,10.1 an:

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 umfasst die folgenden APIs:

public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);

Der nachstehende Code zeigt SortBy:xyz, SortDirection:Desc, CurrentPage:99 mit dem URI /products?SortBy=xyz&SortDir=Desc&Page=99 an:

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
}

Bindungsfehler

Wenn die Bindung fehlschlägt, protokolliert das Framework eine Debugmeldung und gibt abhängig vom Fehlermodus verschiedene Statuscodes an den Client zurück.

Fehlermodus Parametertypen, die Nullwerte zulassen Bindungsquelle Statuscode
{ParameterType}.TryParse gibt false zurück. ja Route/Abfrage/Header 400
{ParameterType}.BindAsync gibt null zurück. ja custom 400
{ParameterType}.BindAsync wird ausgelöst ist nicht wichtig. custom 500
Fehler beim Deserialisieren des JSON-Textkörpers ist nicht wichtig. Text 400
Falscher Inhaltstyp (nicht application/json) ist nicht wichtig. Text 415

Bindungsrangfolge

Die Regeln zum Bestimmen einer Bindungsquelle anhand eines Parameters:

  1. Explizites Attribut, das für den Parameter (From*-Attribute) in der folgenden Reihenfolge definiert ist:
    1. Routenwerte: [FromRoute]
    2. Abfragezeichenfolge: [FromQuery]
    3. Header: [FromHeader]
    4. Hauptteil: [FromBody]
    5. Formular: [FromForm]
    6. Service: [FromServices]
    7. Parameterwerte: [AsParameters]
  2. Sondertypen
    1. HttpContext
    2. HttpRequest (HttpContext.Request)
    3. HttpResponse (HttpContext.Response)
    4. ClaimsPrincipal (HttpContext.User)
    5. CancellationToken (HttpContext.RequestAborted)
    6. IFormCollection (HttpContext.Request.Form)
    7. IFormFileCollection (HttpContext.Request.Form.Files)
    8. IFormFile (HttpContext.Request.Form.Files[paramName])
    9. Stream (HttpContext.Request.Body)
    10. PipeReader (HttpContext.Request.BodyReader)
  3. Der Parametertyp verfügt über eine gültige statische BindAsync-Methode.
  4. Der Parametertyp lautet „string“ oder verfügt über eine gültige statische TryParse-Methode.
    1. Wenn der Parametername in der Routenvorlage vorhanden ist (z. B. app.Map("/todo/{id}", (int id) => {});), wird er über die Route gebunden.
    2. Bindung über die Abfragezeichenfolge.
  5. Wenn der Parametertyp ein durch die Abhängigkeitsinjektion bereitgestellter Dienst ist, wird dieser Dienst als Quelle verwendet.
  6. Der Parameter stammt aus dem Text.

Konfigurieren von JSON-Deserialisierungsoptionen für die Textbindung

Die Textbindungsquelle verwendet System.Text.Json für die Deserialisierung. Es ist nicht möglich, diese Standardeinstellung zu ändern, aber die JSON-Serialisierungs- und Deserialisierungsoptionen können konfiguriert werden.

Globales Konfigurieren von JSON-Deserialisierungsoptionen

Optionen, die global für eine App gelten, können durch Aufrufen von ConfigureHttpJsonOptions konfiguriert werden. Das folgende Beispiel enthält öffentliche Felder und Formate der JSON-Ausgabe.

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

Da der Beispielcode sowohl die Serialisierung als auch die Deserialisierung konfiguriert, kann er NameField lesen und NameField in die JSON-Ausgabe einschließen.

Konfigurieren von JSON-Deserialisierungsoptionen für einen Endpunkt

ReadFromJsonAsync verfügt über Überladungen, die ein JsonSerializerOptions-Objekt akzeptieren. Das folgende Beispiel enthält öffentliche Felder und Formate der JSON-Ausgabe.

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

Da der vorangehende Code die angepassten Optionen nur auf die Deserialisierung anwendet, schließt die JSON-Ausgabe NameField aus.

Lesen des Anforderungstexts

Lesen Sie den Anforderungstext direkt mithilfe des Parameters HttpContext oder 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();

Der obige Code:

  • Greift mit HttpRequest.BodyReader auf den Anforderungstext zu.
  • Kopiert den Anforderungstext in eine lokale Datei.

Antworten

Routenhandler unterstützen die folgenden Typen von Rückgabewerten:

  1. IResult-basiert: Dies schließt Task<IResult> und ValueTask<IResult> ein.
  2. string: Dies schließt Task<string> und ValueTask<string> ein.
  3. T (ein beliebiger weiterer Typ): Dies schließt Task<T> und ValueTask<T> ein.
Rückgabewert Verhalten Content-Type
IResult Das Framework ruft IResult.ExecuteAsync auf. Richtet sich nach der IResult-Implementierung
string Das Framework schreibt die Zeichenfolge direkt in die Antwort. text/plain
T (beliebiger anderer Typ) Das Framework JSON serialisiert die Antwort. application/json

Ausführlichere Anleitungen zu Rückgabewerten des Routenhandlers finden Sie unter Erstellen von Antworten in Minimal-API-Anwendungen.

Beispielrückgabewerte

Rückgabewerte vom Typ „string“

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

JSON-Rückgabewerte

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

Zurückgeben von TypedResults

Der folgende Code gibt TypedResults zurück:

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

Die Rückgabe von TypedResults wird der Rückgabe von Results vorgezogen. Weitere Informationen finden Sie unter Vergleich von TypedResults und Results.

IResult-Rückgabewerte

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

Im folgenden Beispiel werden die integrierten Ergebnistypen verwendet, um die Antwort anzupassen:

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

JSEIN

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

Benutzerdefinierter Statuscode

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

Weitere Beispiele finden Sie unter Erstellen von Antworten in Minimal-API-Anwendungen.

Umleiten

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

Datei

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

Integrierte Ergebnisse

Die statischen Klassen Results und TypedResults enthalten allgemeine Ergebnishilfen. Die Rückgabe von TypedResults wird der Rückgabe von Results vorgezogen. Weitere Informationen finden Sie unter Vergleich von TypedResults und Results.

Anpassen von Ergebnissen

Anwendungen können Antworten steuern, indem sie einen benutzerdefinierten IResult-Typ implementieren. Der folgende Code ist ein Beispiel für einen HTML-Ergebnistyp:

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

Es wird empfohlen, Microsoft.AspNetCore.Http.IResultExtensions eine Erweiterungsmethode hinzuzufügen, um diese benutzerdefinierten Ergebnisse leichter auffindbar zu machen.

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

Eingegebene Ergebnisse

Die IResult-Schnittstelle kann von minimalen APIs zurückgegebene Werte darstellen, welche nicht die implizite Unterstützung für die JSON-Serialisierung des zurückgegebenen Objekts in die HTTP-Antwort verwenden. Die statische Results-Klasse wird verwendet, um unterschiedliche IResult-Objekte zu erstellen, die verschiedene Arten von Antworten darstellen. Beispiel: Festlegen des Antwortstatuscodes oder Umleitung an eine andere URL.

Die IResult implementierenden Typen sind öffentlich, sodass Typassertionen beim Testen zugelassen werden. Beispiel:

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

Sie können die Rückgabetypen der entsprechenden Methoden in der statischen TypedResults-Klasse anzeigen, um den richtigen öffentlichen IResult-Typ zu finden, in den Werte umgewandelt erden sollen.

Weitere Beispiele finden Sie unter Erstellen von Antworten in Minimal-API-Anwendungen.

Filter

Thema

Autorisierung

Routen können mithilfe von Autorisierungsrichtlinien geschützt werden. Diese können mit dem Attribut [Authorize] oder der Methode RequireAuthorization angegeben werden.

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

Der vorangehende Code kann mit RequireAuthorization geschrieben werden:

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

Im folgenden Beispiel wird die richtlinienbasierte Autorisierung verwendet:

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

Nicht authentifizierten Benutzern den Zugriff auf einen Endpunkt gestatten

Durch [AllowAnonymous] können nicht authentifizierte Benutzer auf Endpunkte zugreifen:

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


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

CORS

Routen können mithilfe von CORS-Richtlinien für CORS aktiviert werden. CORS kann über das Attribut [EnableCors] oder mit der Methode RequireCors deklariert werden. In den folgenden Beispielen wird CORS aktiviert:

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

Weitere Informationen finden Sie unter Aktivieren ursprungsübergreifender Anforderungen (Cross-Origin Requests, CORS) in ASP.NET Core.

ValidateScopes und ValidateOnBuild

ValidateScopes und ValidateOnBuild sind standardmäßig in der Entwicklungsumgebung aktiviert, aber in anderen Umgebungen deaktiviert.

Wenn ValidateOnBuildtrue ist, überprüft der DI-Container die Dienstkonfiguration zur Buildzeit. Wenn die Dienstkonfiguration ungültig ist, schlägt der Build beim Starten der App und nicht zur Laufzeit fehl, wenn der Dienst angefordert wird.

Wenn ValidateScopestrue ist, überprüft der DI-Container, dass ein bereichsbezogener Dienst nicht aus dem Stammbereich aufgelöst wird. Das Auflösen eines bereichsbezogenen Diensts aus dem Stammbereich kann zu einem Arbeitsspeicherverlust führen, da der Dienst länger als der Anforderungsbereich im Arbeitsspeicher aufbewahrt wird.

ValidateScopes und ValidateOnBuild sind aus Leistungsgründen standardmäßig in Nichtentwicklungsmodi falsch.

Der folgende Code zeigt, dass ValidateScopes im Entwicklungsmodus standardmäßig aktiviert, aber im Releasemodus deaktiviert ist:

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

Der folgende Code zeigt, dass ValidateOnBuild im Entwicklungsmodus standardmäßig aktiviert, aber im Releasemodus deaktiviert ist:

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

Der folgende Code deaktiviert ValidateScopes und ValidateOnBuild in Development:

var builder = WebApplication.CreateBuilder(args);

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

Siehe auch

Dieses Dokument hat folgende Eigenschaften:

Die Minimal-APIs bestehen aus:

WebApplication

Der folgende Code wird von einer ASP.NET Core-Vorlage generiert:

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

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

app.Run();

Der vorstehende Code kann über dotnet new web in der Befehlszeile oder durch Auswahl der leeren Webvorlage in Visual Studio erstellt werden.

Mit dem folgenden Code wird eine WebApplication (app) erstellt, ohne explizit einen WebApplicationBuilder zu erstellen:

var app = WebApplication.Create(args);

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

app.Run();

WebApplication.Create initialisiert eine neue Instanz der WebApplication-Klasse mit vorkonfigurierten Standardwerten.

WebApplication fügt Minimal API applications abhängig von bestimmten Bedingungen automatisch die folgende Middleware hinzu:

  • UseDeveloperExceptionPage wird zuerst hinzugefügt, wenn HostingEnvironment gleich "Development" ist.
  • UseRouting wird zweitens hinzugefügt, wenn der Benutzercode UseRouting noch nicht aufgerufen hat, und wenn Endpunkte konfiguriert sind, z. B. app.MapGet.
  • UseEndpoints wird am Ende der Middlewarepipeline hinzugefügt, wenn Endpunkte konfiguriert sind.
  • UseAuthentication wird unmittelbar nach UseRouting hinzugefügt, wenn der Benutzercode UseAuthentication noch nicht aufgerufen hat und wenn IAuthenticationSchemeProvider im Dienstanbieter erkannt werden kann. IAuthenticationSchemeProvider wird standardmäßig hinzugefügt, wenn die Verwendung AddAuthentication von Diensten mit IServiceProviderIsService erkannt wird.
  • UseAuthorization wird als Nächstes hinzugefügt, wenn der Benutzercode UseAuthorization noch nicht aufgerufen hat und wenn IAuthorizationHandlerProvider im Dienstanbieter erkannt werden kann. IAuthorizationHandlerProvider wird standardmäßig hinzugefügt, wenn AddAuthorization verwendet wird, und Dienste mit IServiceProviderIsService erkannt werden.
  • Benutzerkonfigurierte Middleware und Endpunkte werden zwischen UseRouting und UseEndpoints hinzugefügt.

Nachfolgend sehen Sie den Code, der von der automatischen Middleware erzeugt wird, die zur App hinzugefügt wird:

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

app.UseRouting();

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

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

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

app.UseEndpoints(e => {});

In einigen Fällen eignet sich die standardmäßige Middleware-Konfiguration nicht für die App und muss geändert werden. Beispielsweise sollte UseCors vor UseAuthentication und UseAuthorization aufgerufen werden. Die App muss UseAuthentication und UseAuthorization aufrufen, wenn UseCors aufgerufen wird:

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

Wenn Middleware ausgeführt werden muss, bevor der Routenabgleich erfolgt, muss UseRouting aufgerufen werden, und die Middleware muss vor dem Aufruf von UseRouting platziert werden. UseEndpoints ist in diesem Fall nicht erforderlich, da es wie zuvor beschrieben automatisch hinzugefügt wird:

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

app.UseRouting();

// other middleware and endpoints

Beim Hinzufügen einer Terminal-Middleware:

  • Die Middleware muss nach UseEndpoints hinzugefügt werden.
  • Die App muss UseRouting und UseEndpoints aufrufen, damit die Terminal-Middleware an der richtigen Position platziert werden kann.
app.UseRouting();

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

app.UseEndpoints(e => {});

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

Terminal-Middleware ist Middleware, die ausgeführt wird, wenn kein Endpunkt die Anforderung verarbeitet.

Arbeiten mit Ports

Beim Erstellen einer Web-App mit Visual Studio oder dotnet new wird eine Datei Properties/launchSettings.json erstellt, die die Ports angibt, an denen die Anwendung antwortet. In den folgenden Beispielen für Porteinstellungen wird beim Ausführen der App in Visual Studio ein Fehlerdialogfeld Unable to connect to web server 'AppName' angezeigt. Visual Studio gibt einen Fehler zurück, da der in Properties/launchSettings.jsonangegebene Port erwartet wird, die App jedoch den von app.Run("http://localhost:3000") angegebenen Port verwendet. Führen Sie die folgenden Beispiele für Portänderungen über die Befehlszeile aus.

In den folgenden Abschnitten wird der Port festgelegt, auf den die App reagiert.

var app = WebApplication.Create(args);

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

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

Im vorangehenden Code antwortet die App auf Port 3000.

Mehrere Ports

Im folgenden Code antwortet die App auf Port 3000 und 4000.

var app = WebApplication.Create(args);

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

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

app.Run();

Festlegen des Ports über die Befehlszeile

Mit dem folgenden Befehl antwortet die App auf Port 7777:

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

Wenn der Endpunkt Kestrel ebenfalls in der Datei appsettings.json konfiguriert ist, wird die in der Datei appsettings.json angegebene URL verwendet. Weitere Informationen finden Sie unter Kestrel-Endpunktkonfiguration.

Lesen des Ports aus der Umgebung

Der folgende Code liest den Port aus der Umgebung:

var app = WebApplication.Create(args);

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

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

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

Die bevorzugte Methode zur Festlegung des Ports über die Umgebung ist die Verwendung der Umgebungsvariablen ASPNETCORE_URLS, die im folgenden Abschnitt beschrieben wird.

Festlegen der Ports über die ASPNETCORE_URLS-Umgebungsvariable

Für die Festlegung des Ports steht die Umgebungsvariable ASPNETCORE_URLS zur Verfügung:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS unterstützt mehrere URLs:

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

Weitere Informationen zur Verwendung der Umgebung finden Sie unter Verwenden mehrerer Umgebungen in ASP.NET Core.

Lauschen an allen Schnittstellen

Die folgenden Beispiele veranschaulichen das Lauschen an allen Schnittstellen.

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

Lauschen an allen Schnittstellen mit ASPNETCORE_URLS

In den vorherigen Beispielen kann ASPNETCORE_URLS verwendet werden.

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

Angeben von HTTPS mit Entwicklungszertifikat

var app = WebApplication.Create(args);

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

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

app.Run();

Weitere Informationen über das Entwicklungszertifikat finden Sie unter Vertrauen Sie dem ASP.NET Core-HTTPS-Entwicklungszertifikat unter Windows und macOS.

Angeben von HTTPS mithilfe eines benutzerdefinierten Zertifikats

Die folgenden Abschnitte zeigen, wie das benutzerdefinierte Zertifikat mithilfe der Datei appsettings.json und über die Konfiguration angegeben wird.

Angeben des benutzerdefinierten Zertifikats mit appsettings.json

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

Angeben des benutzerdefinierten Zertifikats über die Konfiguration

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

Verwenden der Zertifikat-APIs

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

Konfiguration

Der folgende Code liest Informationen aus dem Konfigurationssystem:

var app = WebApplication.Create(args);

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

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

app.Run();

Weitere Informationen finden Sie unter Konfiguration in ASP.NET Core.

Protokollierung

Der folgende Code schreibt eine Meldung in das Anwendungsstartprotokoll:

var app = WebApplication.Create(args);

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

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

app.Run();

Weitere Informationen finden Sie unter Protokollieren in .NET Core und ASP.NET Core.

Zugreifen auf den Container für Abhängigkeitsinjektion

Der folgende Code zeigt, wie Dienste während des Anwendungsstarts aus dem Abhängigkeitsinjektionscontainer abzurufen sind:


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

Weitere Informationen finden Sie unter Abhängigkeitsinjektion in ASP.NET Core.

WebApplicationBuilder

Dieser Abschnitt enthält Beispielcode unter Verwendung von WebApplicationBuilder.

Ändern von Inhaltsstamm, Anwendungsname und Umgebung

Der folgende Code legt den Inhaltsstamm, den Anwendungsnamen und die Umgebung fest:

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 Initialisiert eine neue Instanz der WebApplicationBuilder-Klasse mit vorkonfigurierten Standardwerten.

Weitere Informationen finden Sie unter ASP.NET Core – Grundlagenübersicht.

Ändern von Inhaltsstamm, App-Name und Umgebung über Umgebungsvariablen oder Befehlszeile

Die folgende Tabelle zeigt die Umgebungsvariablen und Befehlszeilenargumente, die zum Ändern von Inhaltsstamm, Anwendungsname und Umgebung verwendet werden:

Feature Umgebungsvariable Befehlszeilenargument
Anwendungsname ASPNETCORE_APPLICATIONNAME --applicationName
Umgebungsname ASPNETCORE_ENVIRONMENT --environment
Inhaltsstammverzeichnis ASPNETCORE_CONTENTROOT --contentRoot

Hinzufügen von Konfigurationsanbietern

Im folgenden Beispiel wird der INI-Konfigurationsanbieter hinzugefügt:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

Ausführliche Informationen finden Sie unter Dateikonfigurationsanbieter in Konfiguration in ASP.NET Core.

Lesen der Konfiguration

Standardmäßig liest die WebApplicationBuilder die Konfiguration aus mehreren Quellen, darunter:

  • appSettings.json und appSettings.{environment}.json
  • Umgebungsvariablen
  • Die Befehlszeile

Der folgende Code liest HelloKey aus der Konfiguration und zeigt den Wert am Endpunkt / an. Wenn der Konfigurationswert NULL ist, wird „Hello“ message zugewiesen:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Eine vollständige Liste der gelesenen Konfigurationsquellen finden Sie unter Standardkonfiguration in Konfiguration in ASP.NET Core.

Hinzufügen von Protokollierungsanbietern

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

Hinzufügen von Diensten

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

Anpassen von IHostBuilder

Vorhandene Erweiterungsmethoden für IHostBuilder können über die Host-Eigenschaft aufgerufen werden:

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

Anpassen von IWebHostBuilder

Erweiterungsmethoden für IWebHostBuilder können über die Eigenschaft WebApplicationBuilder.WebHost aufgerufen werden.

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

Ändern des Webstamms

Standardmäßig ist der Webstamm relativ zum Inhaltsstamm im Ordner wwwroot angegeben. Im Webstamm sucht die Middleware für statische Dateien nach statischen Dateien. Der Webstamm kann mit WebHostOptions, der Befehlszeile oder mit der Methode UseWebRoot geändert werden:

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

var app = builder.Build();

app.Run();

Container für benutzerdefinierte Abhängigkeitsinjektion

Im folgenden Beispiel wird Autofac verwendet:

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

Hinzufügen von Middleware

Für die WebApplication kann eine beliebige vorhandene ASP.NET Core-Middleware konfiguriert werden:

var app = WebApplication.Create(args);

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

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

app.Run();

Weitere Informationen finden Sie unter ASP.NET Core-Middleware.

Seite mit Ausnahmen für Entwickler

WebApplication.CreateBuilder initialisiert eine neue Instanz der WebApplicationBuilder-Klasse mit vorkonfigurierten Standardwerten. Die Seite mit Ausnahmen für Entwickler ist in den vorkonfigurierten Standardwerten aktiviert. Durch Ausführung des folgende Codes in der Entwicklungsumgebung wird beim Navigieren zu / eine benutzerfreundliche Seite geöffnet, auf der die Ausnahme angezeigt wird.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Run();

ASP.NET Core-Middleware

In der folgenden Tabelle werden einige der Middlewarekomponenten aufgeführt, die häufig mit Minimal-APIs verwendet wird.

Middleware Beschreibung API
Authentifizierung Bietet Unterstützung für Authentifizierungen. UseAuthentication
Autorisierung Bietet Unterstützung für Authentifizierungen UseAuthorization
CORS Konfiguriert die Ressourcenfreigabe zwischen verschiedenen Ursprüngen (Cross-Origin Resource Sharing, CORS). UseCors
Ausnahmehandler Behandelt global Ausnahmen, die von der Middlewarepipeline ausgelöst werden. UseExceptionHandler
Weitergeleitete Header Leitet Proxyheader an die aktuelle Anforderung weiter. UseForwardedHeaders
HTTPS-Umleitung Leitet alle HTTP-Anforderungen an HTTPS um. UseHttpsRedirection
HTTP Strict Transport Security (HSTS) Middleware für erweiterte Sicherheit, die einen besonderen Antwortheader hinzufügt. UseHsts
Anforderungsprotokollierung Bietet Unterstützung für die Protokollierung von HTTP-Anforderungen und -Antworten. UseHttpLogging
Anforderungstimeouts Bietet Unterstützung beim Konfigurieren von Anforderungstimeouts und globaler Standardwerte sowie bei der Konfiguration pro Endpunkt. UseRequestTimeouts
W3C-Anforderungsprotokollierung Bietet Unterstützung für die Protokollierung von HTTP-Anforderungen und -Antworten im W3C-Format. UseW3CLogging
Zwischenspeichern von Antworten Bietet Unterstützung für das Zwischenspeichern von Antworten. UseResponseCaching
Antwortkomprimierung Bietet Unterstützung für das Komprimieren von Antworten. UseResponseCompression
Sitzung Bietet Unterstützung für das Verwalten von Benutzersitzungen. UseSession
Statische Dateien Bietet Unterstützung für das Verarbeiten statischer Dateien und das Durchsuchen des Verzeichnisses. UseStaticFiles, UseFileServer
WebSockets Aktiviert das WebSockets-Protokoll. UseWebSockets

In den folgenden Abschnitten werden Routing, Parameterbindung und Antworten behandelt.

Routing

Eine konfigurierte WebApplication unterstützt Map{Verb} und MapMethods, wobei {Verb} eine HTTP-Methode mit Camel-Case-Schreibweise wie Get, Post, Put oder Delete ist:

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

Die an diese Methoden übergebenen Delegate-Argumente werden als „Routenhandler“ bezeichnet.

Routenhandler

Routenhandler sind Methoden, die ausgeführt werden, wenn die Route übereinstimmt. Als Routenhandler kann ein Lambdaausdruck, eine lokale Funktion, eine Instanzmethode oder eine statische Methode verwendet werden. Routenhandler können synchron oder asynchron sein.

Lambdaausdruck

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

Lokale Funktion

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

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

app.MapGet("/", LocalFunction);

app.Run();

für eine Instanzmethode

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

für eine statische Methode

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

Endpunkt außerhalb von Program.cs definiert

Minimale APIs müssen sich nicht in Program.cs befinden.

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

Weitere Informationen dazu finden Sie auch unter Routengruppen weiter unten in diesem Artikel.

Endpunkte können Namen erhalten, um URLs für den jeweiligen Endpunkt zu generieren. Durch die Verwendung eines benannten Endpunkts entfällt das Hartcodieren von Pfaden in einer App:

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

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

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

app.Run();

Der vorangehende Code zeigt The link to the hello endpoint is /hello vom Endpunkt / an.

HINWEIS: Für Endpunktnamen muss Groß-/Kleinschreibung beachtet werden.

Endpunktnamen:

  • Dieser muss global eindeutig sein.
  • Werden als OpenAPI-Vorgangs-ID verwendet, wenn die OpenAPI-Unterstützung aktiviert ist. Weitere Informationen finden Sie unter OpenAPI.

Routenparameter

Routenparameter können als Teil der Routenmusterdefinition erfasst werden:

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

Der vorstehende Code gibt The user id is 3 and book id is 7 aus dem URI /users/3/books/7 zurück.

Der Routenhandler kann die zu erfassende Parameter deklarieren. Bei einer Anforderung an eine Route mit Parametern, deren Erfassung deklariert wurde, werden die Parameter geparst und an den Handler übergeben. Dadurch können die Werte problemlos typsicher erfasst werden. Im vorangegangenen Code sind userId und bookId beide vom Typ int.

Wenn im vorstehenden Code einer der beiden Routenwerte nicht in den Typ int umgewandelt werden kann, wird eine Ausnahme ausgelöst. Die GET-Anforderung /users/hello/books/3 löst die folgende Ausnahme aus:

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

Platzhalterzeichen und Abfangen aller Routen

Der folgende Code zum Abfangen aller Routen gibt Routing to hello vom Endpunkt „/posts/hello“ zurück:

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

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

app.Run();

Routeneinschränkungen

Routeneinschränkungen schränken das Abgleichsverhalten einer Route ein.

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

Die folgende Tabelle zeigt die vorangegangenen Routenvorlagen und ihr Verhalten:

Routenvorlage Beispiel-URI für Übereinstimmung
/todos/{id:int} /todos/1
/todos/{text} /todos/something
/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

Weitere Informationen finden Sie unter Referenz für Routeneinschränkungen in Routing in ASP.NET Core.

Routengruppen

Die MapGroup-Erweiterungsmethode hilft, Gruppen von Endpunkten mit einem gemeinsamen Präfix zu organisieren. Sie reduziert sich wiederholenden Code und ermöglicht die benutzerdefinierte Anpassung ganzer Gruppen von Endpunkten mit einem einzigen Aufruf von Methoden wie RequireAuthorization und WithMetadata,die Endpunktmetadaten hinzufügen.

Der folgende Code erstellt beispielsweise zwei ähnliche Endpunktgruppen:

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

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


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

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

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

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

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

    return group;
}

In diesem Szenario können Sie eine relative Adresse für den Location-Header im 201 Created-Ergebnis verwenden:

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

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

Die erste Gruppe von Endpunkten entspricht nur Anforderungen mit dem Präfix /public/todos und ist ohne Authentifizierung zugänglich. Die zweite Gruppe von Endpunkten entspricht nur Anforderungen mit dem Präfix /private/todos und erfordert Authentifizierung.

Die QueryPrivateTodosEndpunktfilterfactory ist eine lokale Funktion, die die TodoDb-Parameter des Routenhandlers so ändert, dass Zugriff auf private Aufgabendaten zulässig ist und diese gespeichert werden können.

Routengruppen unterstützen auch geschachtelte Gruppen und komplexe Präfixmuster mit Routenparametern und -einschränkungen. Im folgenden Beispiel kann der der user-Gruppe zugeordnete Routenhandler die Routenparameter {org} und {group} erfassen, die in den Präfixen der äußeren Gruppe definiert sind.

Das Präfix kann auch leer sein. Dies kann hilfreich sein, um Endpunktmetadaten oder Filter einer Gruppe von Endpunkten hinzuzufügen, ohne das Routenmuster zu ändern.

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

Das Hinzufügen von Filtern oder Metadaten zu einer Gruppe verhält sich genauso wie das individuelle Hinzufügen zu jedem Endpunkt, bevor zusätzliche Filter oder Metadaten hinzugefügt werden, die einer inneren Gruppe oder einem bestimmten Endpunkt hinzugefügt wurden.

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

Im Beispiel oben protokolliert der äußere Filter die eingehende Anforderung vor dem inneren Filter, obwohl er als zweiter hinzugefügt wurde. Da die Filter auf verschiedene Gruppen angewendet wurden, spielt die Reihenfolge, in der sie relativ zueinander hinzugefügt wurden, keine Rolle. Die Reihenfolge, in der Filter hinzugefügt werden, spielt eine Rolle, wenn sie auf dieselbe Gruppe oder einen bestimmten Endpunkt angewendet werden.

Eine Anforderung an /outer/inner/ protokolliert Folgendes:

/outer group filter
/inner group filter
MapGet filter

Parameterbindung

Die Parameterbindung ist der Prozess der Umwandlung von Anforderungsdaten in stark typisierte Parameter, die durch Routenhandler ausgedrückt werden. Eine Bindungsquelle bestimmt, von wo aus Parameter gebunden werden. Bindungsquellen können basierend auf der HTTP-Methode und dem Parametertyp explizit sein oder abgeleitet werden.

Unterstützte Bindungsquellen:

  • Routenwerte
  • Abfragezeichenfolge
  • Header
  • Textkörper (als JSON)
  • Von der Abhängigkeitsinjektion bereitgestellte Dienste
  • Benutzerdefiniert

Die Bindung aus Formularen wird in .NET 6 und 7 nicht nativ unterstützt.

Im folgenden Beispiel verwendet der GET-Routenhandler einige dieser Parameterbindungsquellen:

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

Die folgende Tabelle zeigt die Beziehung zwischen den im vorherigen Beispiel verwendeten Parametern und den zugeordneten Bindungsquellen.

Parameter Bindungsquelle
id Routenwert
page Abfragezeichenfolge
customHeader header
service Von der Abhängigkeitsinjektion bereitgestellt

Bei den HTTP-Methoden GET, HEAD, OPTIONS und DELETE erfolgt keine implizite Bindung aus dem Text. Um eine Bindung vom Textkörper (als JSON) für diese HTTP-Methoden zu verwenden, führen Sie explizit eine Bindung mit [FromBody] oder einen Lesevorgang aus HttpRequest durch.

Im folgenden Beispiel verwendet der POST-Routenhandler eine Bindungsquelle des Textkörpers (als JSON) für den Parameter person:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

record Person(string Name, int Age);

Die Parameter in den vorherigen Beispielen werden alle automatisch über Anforderungsdaten gebunden. Um die Benutzerfreundlichkeit der Parameterbindung zu veranschaulichen, zeigen die folgenden Routenhandler, wie Anforderungsdaten direkt aus der Anforderung gelesen werden:

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

    // ...
});

Explizite Parameterbindung

Attribute können verwendet werden, um explizit zu deklarieren, von wo Parameter gebunden werden.

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 Bindungsquelle
id Routenwert mit dem Namen id
page Abfragezeichenfolge mit dem Namen "p"
service Von der Abhängigkeitsinjektion bereitgestellt
contentType Header mit dem Namen "Content-Type"

Hinweis

Die Bindung aus Formularen wird in .NET 6 und 7 nicht nativ unterstützt.

Parameterbindung mit Abhängigkeitsinjektion

Parameterbindung für minimale APIs bindet Parameter durch Abhängigkeitsinjektion (Dependency Injection, DI), wenn der Typ als Dienst konfiguriert wird. Es ist nicht erforderlich, das [FromServices]-Attribut explizit auf einen Parameter anzuwenden. Im folgenden Code geben beide Aktionen die Uhrzeit zurück:

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

Optionale Parameter

In Routenhandlern deklarierte Parameter werden als erforderlich behandelt:

  • Wenn eine Anforderung der Route entspricht, wird der Routenhandler nur ausgeführt, wenn alle erforderlichen Parameter in der Anforderung angegeben sind.
  • Sind nicht alle erforderlichen Parameter enthalten, kommt es zu einem Fehler.
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 zurückgegeben
/products BadHttpRequestException: Der erforderliche Parameter „int pageNumber“ wurde nicht von der Abfragezeichenfolge bereitgestellt.
/products/1 HTTP-Fehler vom Typ 404, keine übereinstimmende Route

Um pageNumber als optional festzulegen, definieren Sie den Typ als optional, oder geben Sie einen Standardwert an:

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 zurückgegeben
/products 1 zurückgegeben
/products2 1 zurückgegeben

Der vorangehende Nullwerte zulassende und Standardwert gilt für alle Quellen:

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

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

app.Run();

Der stehende Code ruft die Methode mit einem NULL-Produkt auf, wenn kein Anforderungstext gesendet wird.

HINWEIS: Wenn ungültige Daten angegeben werden und der Parameter Nullwerte zulässt, wird der Routenhandler nicht ausgeführt.

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 zurückgegeben
/products 1 zurückgegeben
/products?pageNumber=two BadHttpRequestException: Fehler beim Binden von Parameter "Nullable<int> pageNumber" aus „two“.
/products/two HTTP-Fehler vom Typ 404, keine übereinstimmende Route

Weitere Informationen finden Sie im Abschnitt Bindungsfehler.

Sondertypen

Die folgenden Typen werden ohne explizite Attribute gebunden:

  • HttpContext: Der Kontext, der alle Informationen zur aktuellen HTTP-Anforderung oder -Antwort enthält:

    app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
    
  • HttpRequest und HttpResponse: die HTTP-Anforderung und HTTP-Antwort:

    app.MapGet("/", (HttpRequest request, HttpResponse response) =>
        response.WriteAsync($"Hello World {request.Query["name"]}"));
    
  • CancellationToken: das mit der aktuellen HTTP-Anforderung verknüpfte Abbruchtoken:

    app.MapGet("/", async (CancellationToken cancellationToken) => 
        await MakeLongRunningRequestAsync(cancellationToken));
    
  • ClaimsPrincipal: der mit der Anforderung verknüpfte Benutzer, gebunden aus HttpContext.User:

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

Binden des Anforderungstexts als Stream oder PipeReader

Der Anforderungstext kann als Stream oder PipeReader gebunden werden, um effizient Szenarien zu unterstützen, in denen der Benutzer Daten verarbeiten und Folgendes tun muss:

  • Daten im Blobspeicher speichern oder Daten in die Warteschlange eines Warteschlangenanbieters einreihen.
  • Die gespeicherten Daten mit einem Workerprozess oder einer Cloudfunktion verarbeiten.

Beispielsweise werden die Daten möglicherweise in Azure Queue Storage eingereiht oder in Azure Blob Storage gespeichert.

Der folgende Code implementiert eine Hintergrundwarteschlange:

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

Der folgende Code bindet den Anforderungstext an einen 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);
});

Der folgende Code veranschaulicht die vollständige Program.cs-Datei:

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();
  • Beim Lesen von Daten ist der Stream dasselbe Objekt wie HttpRequest.Body.
  • Der Anforderungstext wird standardmäßig nicht gepuffert. Nachdem der Textkörper gelesen wurde, ist eine Rückkehr zum Anfang nicht möglich. Der Datenstrom kann nicht mehrmals gelesen werden.
  • Stream und PipeReader sind außerhalb des minimalen Aktionshandlers nicht verwendbar, da die zugrunde liegenden Puffer verworfen oder wiederverwendet werden.

Dateiuploads mit IFormFile und IFormFileCollection

Der folgende Code verwendet IFormFile und IFormFileCollection zum Hochladen der Datei:

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

Authentifizierte Dateiuploadanforderungen werden mithilfe eines Autorisierungsheaders, eines Clientzertifikats oder eines cookie-Headers unterstützt.

Es gibt keine integrierte Unterstützung für Anti-Forgery-Systeme in ASP.NET Core 7.0. Antiforgery ist in ASP.NET Core 8.0 und höher verfügbar. Die Implementierung ist jedoch mit dem IAntiforgery-Dienst möglich.

Binden von Arrays und Zeichenfolgenwerten aus Headern und Abfragezeichenfolgen

Der folgende Code veranschaulicht die Bindung von Abfragezeichenfolgen an ein Array von primitiven Typen, Zeichenfolgenarrays und 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]}");

Die Bindung von Abfragezeichenfolgen oder Headerwerten an ein Array komplexer Typen wird unterstützt, wenn im Typ TryParse implementiert ist. Der folgende Code bindet an ein Zeichenfolgenarray und gibt alle Elemente mit den angegebenen Tags zurück:

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

Der folgende Code zeigt das Modell und die erforderliche TryParse-Implementierung:

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

Der folgende Code richtet eine Bindung mit einem int-Array ein:

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

Fügen Sie zum Testen des vorherigen Codes den folgenden Endpunkt hinzu, um die Datenbank mit Todo-Elementen aufzufüllen:

// POST /todoitems/batch
app.MapPost("/todoitems/batch", async (Todo[] todos, TodoDb db) =>
{
    await db.Todos.AddRangeAsync(todos);
    await db.SaveChangesAsync();

    return Results.Ok(todos);
});

Verwenden Sie ein API-Testtool wie HttpRepl, um die folgenden Daten an den vorherigen Endpunkt zu übergeben:

[
    {
        "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"
        }
    }
]

Der folgende Code bindet an den Headerschlüssel X-Todo-Id und gibt die Todo-Elemente mit übereinstimmenden Id-Werten zurück:

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

Hinweis

Beim Binden eines string[]-Werts aus einer Abfragezeichenfolge führt das Fehlen einer übereinstimmenden Abfragezeichenfolge zu einem leeren Array anstelle eines NULL-Werts.

Parameterbindung für Argumentlisten mit [AsParameters]

AsParametersAttribute ermöglicht einfache Parameterbindung an Typen und nicht komplexe oder rekursive Modellbindung.

Betrachten Sie folgenden Code:

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.

Betrachten Sie den folgenden GET-Endpunkt:

app.MapGet("/todoitems/{id}",
                             async (int Id, TodoDb Db) =>
    await Db.Todos.FindAsync(Id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());

Mit der folgenden struct können die vorherigen hervorgehobenen Parameter ersetzt werden:

struct TodoItemRequest
{
    public int Id { get; set; }
    public TodoDb Db { get; set; }
}

Der umgestaltete GET-Endpunkt verwendet die vorherige struct mit dem AsParameters-Attribut:

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

Der folgende Code zeigt zusätzliche Endpunkte in der App:

app.MapPost("/todoitems", async (TodoItemDTO Dto, TodoDb Db) =>
{
    var todoItem = new Todo
    {
        IsComplete = Dto.IsComplete,
        Name = Dto.Name
    };

    Db.Todos.Add(todoItem);
    await Db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});

app.MapPut("/todoitems/{id}", async (int Id, TodoItemDTO Dto, TodoDb Db) =>
{
    var todo = await Db.Todos.FindAsync(Id);

    if (todo is null) return Results.NotFound();

    todo.Name = Dto.Name;
    todo.IsComplete = Dto.IsComplete;

    await Db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/todoitems/{id}", async (int Id, TodoDb Db) =>
{
    if (await Db.Todos.FindAsync(Id) is Todo todo)
    {
        Db.Todos.Remove(todo);
        await Db.SaveChangesAsync();
        return Results.Ok(new TodoItemDTO(todo));
    }

    return Results.NotFound();
});

Die folgenden Klassen werden verwendet, um die Parameterlisten umzugestalten:

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

Der folgende Code zeigt die umgestalteten Endpunkte mit AsParameters und der vorherigen struct und Klassen:

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

Die folgenden record-Typen können verwendet werden, um die vorherigen Parameter zu ersetzen:

record TodoItemRequest(int Id, TodoDb Db);
record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);

Die Verwendung einer struct mit AsParameters kann effizienter sein als die Verwendung eines record-Typs.

Den vollständigen Beispielcode finden Sie im AspNetCore.Docs.Samples-Repository.

Benutzerdefinierte Bindung

Es gibt zwei Möglichkeiten zum Anpassen der Parameterbindung:

  1. Binden Sie für Routen-, Abfrage- und Headerbindungsquellen benutzerdefinierte Typen, indem Sie eine statische TryParse-Methode für den Typ hinzufügen.
  2. Steuern Sie den Bindungsprozess, indem Sie eine BindAsync-Methode für einen Typ implementieren.

TryParse

TryParse umfasst zwei APIs:

public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);

Der folgende Code zeigt Point: 12.3, 10.1 mit dem URI /map?Point=12.3,10.1 an:

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 umfasst die folgenden APIs:

public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);

Der nachstehende Code zeigt SortBy:xyz, SortDirection:Desc, CurrentPage:99 mit dem URI /products?SortBy=xyz&SortDir=Desc&Page=99 an:

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
}

Bindungsfehler

Wenn die Bindung fehlschlägt, protokolliert das Framework eine Debugmeldung und gibt abhängig vom Fehlermodus verschiedene Statuscodes an den Client zurück.

Fehlermodus Parametertypen, die Nullwerte zulassen Bindungsquelle Statuscode
{ParameterType}.TryParse gibt false zurück. ja Route/Abfrage/Header 400
{ParameterType}.BindAsync gibt null zurück. ja custom 400
{ParameterType}.BindAsync wird ausgelöst Nicht relevant custom 500
Fehler beim Deserialisieren des JSON-Textkörpers Nicht relevant body 400
Falscher Inhaltstyp (nicht application/json) Nicht relevant body 415

Bindungsrangfolge

Die Regeln zum Bestimmen einer Bindungsquelle anhand eines Parameters:

  1. Explizites Attribut, das für den Parameter (From*-Attribute) in der folgenden Reihenfolge definiert ist:
    1. Routenwerte: [FromRoute]
    2. Abfragezeichenfolge: [FromQuery]
    3. Header: [FromHeader]
    4. Hauptteil: [FromBody]
    5. Service: [FromServices]
    6. Parameterwerte: [AsParameters]
  2. Sondertypen
    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. Der Parametertyp verfügt über eine gültige statische BindAsync-Methode.
  4. Der Parametertyp lautet „string“ oder verfügt über eine gültige statische TryParse-Methode.
    1. Wenn der Parametername in der Routenvorlage vorhanden ist, z. B. app.Map("/todo/{id}", (int id) => {});, wird er über die Route gebunden.
    2. Bindung über die Abfragezeichenfolge.
  5. Wenn der Parametertyp ein durch die Abhängigkeitsinjektion bereitgestellter Dienst ist, wird dieser Dienst als Quelle verwendet.
  6. Der Parameter stammt aus dem Text.

Konfigurieren von JSON-Deserialisierungsoptionen für die Textbindung

Die Textbindungsquelle verwendet System.Text.Json für die Deserialisierung. Es ist nicht möglich, diese Standardeinstellung zu ändern, aber die JSON-Serialisierungs- und Deserialisierungsoptionen können konfiguriert werden.

Globales Konfigurieren von JSON-Deserialisierungsoptionen

Optionen, die global für eine App gelten, können durch Aufrufen von ConfigureHttpJsonOptions konfiguriert werden. Das folgende Beispiel enthält öffentliche Felder und Formate der JSON-Ausgabe.

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

Da der Beispielcode sowohl die Serialisierung als auch die Deserialisierung konfiguriert, kann er NameField lesen und NameField in die JSON-Ausgabe einschließen.

Konfigurieren von JSON-Deserialisierungsoptionen für einen Endpunkt

ReadFromJsonAsync verfügt über Überladungen, die ein JsonSerializerOptions-Objekt akzeptieren. Das folgende Beispiel enthält öffentliche Felder und Formate der JSON-Ausgabe.

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

Da der vorangehende Code die angepassten Optionen nur auf die Deserialisierung anwendet, schließt die JSON-Ausgabe NameField aus.

Lesen des Anforderungstexts

Lesen Sie den Anforderungstext direkt mithilfe des Parameters HttpContext oder 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();

Der obige Code:

  • Greift mit HttpRequest.BodyReader auf den Anforderungstext zu.
  • Kopiert den Anforderungstext in eine lokale Datei.

Antworten

Routenhandler unterstützen die folgenden Typen von Rückgabewerten:

  1. IResult-basiert: Dies schließt Task<IResult> und ValueTask<IResult> ein.
  2. string: Dies schließt Task<string> und ValueTask<string> ein.
  3. T (ein beliebiger weiterer Typ): Dies schließt Task<T> und ValueTask<T> ein.
Rückgabewert Verhalten Content-Type
IResult Das Framework ruft IResult.ExecuteAsync auf. Richtet sich nach der IResult-Implementierung
string Das Framework schreibt die Zeichenfolge direkt in die Antwort. text/plain
T (beliebiger anderer Typ) Das Framework JSON serialisiert die Antwort. application/json

Ausführlichere Anleitungen zu Rückgabewerten des Routenhandlers finden Sie unter Erstellen von Antworten in Minimal-API-Anwendungen.

Beispielrückgabewerte

Rückgabewerte vom Typ „string“

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

JSON-Rückgabewerte

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

Zurückgeben von TypedResults

Der folgende Code gibt TypedResults zurück:

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

Die Rückgabe von TypedResults wird der Rückgabe von Results vorgezogen. Weitere Informationen finden Sie unter Vergleich von TypedResults und Results.

IResult-Rückgabewerte

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

Im folgenden Beispiel werden die integrierten Ergebnistypen verwendet, um die Antwort anzupassen:

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

JSEIN

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

Benutzerdefinierter Statuscode

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

Weitere Beispiele finden Sie unter Erstellen von Antworten in Minimal-API-Anwendungen.

Umleiten

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

Datei

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

Integrierte Ergebnisse

Die statischen Klassen Results und TypedResults enthalten allgemeine Ergebnishilfen. Die Rückgabe von TypedResults wird der Rückgabe von Results vorgezogen. Weitere Informationen finden Sie unter Vergleich von TypedResults und Results.

Anpassen von Ergebnissen

Anwendungen können Antworten steuern, indem sie einen benutzerdefinierten IResult-Typ implementieren. Der folgende Code ist ein Beispiel für einen HTML-Ergebnistyp:

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

Es wird empfohlen, Microsoft.AspNetCore.Http.IResultExtensions eine Erweiterungsmethode hinzuzufügen, um diese benutzerdefinierten Ergebnisse leichter auffindbar zu machen.

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

Eingegebene Ergebnisse

Die IResult-Schnittstelle kann von minimalen APIs zurückgegebene Werte darstellen, welche nicht die implizite Unterstützung für die JSON-Serialisierung des zurückgegebenen Objekts in die HTTP-Antwort verwenden. Die statische Results-Klasse wird verwendet, um unterschiedliche IResult-Objekte zu erstellen, die verschiedene Arten von Antworten darstellen. Beispiel: Festlegen des Antwortstatuscodes oder Umleitung an eine andere URL.

Die IResult implementierenden Typen sind öffentlich, sodass Typassertionen beim Testen zugelassen werden. Beispiel:

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

Sie können die Rückgabetypen der entsprechenden Methoden in der statischen TypedResults-Klasse anzeigen, um den richtigen öffentlichen IResult-Typ zu finden, in den Werte umgewandelt erden sollen.

Weitere Beispiele finden Sie unter Erstellen von Antworten in Minimal-API-Anwendungen.

Filter

Weitere Informationen finden Sie unter Filter in Minimal-API-Apps.

Autorisierung

Routen können mithilfe von Autorisierungsrichtlinien geschützt werden. Diese können mit dem Attribut [Authorize] oder der Methode RequireAuthorization angegeben werden.

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

Der vorangehende Code kann mit RequireAuthorization geschrieben werden:

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

Im folgenden Beispiel wird die richtlinienbasierte Autorisierung verwendet:

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

Nicht authentifizierten Benutzern den Zugriff auf einen Endpunkt gestatten

Durch [AllowAnonymous] können nicht authentifizierte Benutzer auf Endpunkte zugreifen:

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


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

CORS

Routen können mithilfe von CORS-Richtlinien für CORS aktiviert werden. CORS kann über das Attribut [EnableCors] oder mit der Methode RequireCors deklariert werden. In den folgenden Beispielen wird CORS aktiviert:

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

Weitere Informationen finden Sie unter Aktivieren ursprungsübergreifender Anforderungen (Cross-Origin Requests, CORS) in ASP.NET Core.

Siehe auch

Dieses Dokument hat folgende Eigenschaften:

Die Minimal-APIs bestehen aus:

WebApplication

Der folgende Code wird von einer ASP.NET Core-Vorlage generiert:

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

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

app.Run();

Der vorstehende Code kann über dotnet new web in der Befehlszeile oder durch Auswahl der leeren Webvorlage in Visual Studio erstellt werden.

Mit dem folgenden Code wird eine WebApplication (app) erstellt, ohne explizit einen WebApplicationBuilder zu erstellen:

var app = WebApplication.Create(args);

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

app.Run();

WebApplication.Create initialisiert eine neue Instanz der WebApplication-Klasse mit vorkonfigurierten Standardwerten.

Arbeiten mit Ports

Beim Erstellen einer Web-App mit Visual Studio oder dotnet new wird eine Datei Properties/launchSettings.json erstellt, die die Ports angibt, an denen die Anwendung antwortet. In den folgenden Beispielen für Porteinstellungen wird beim Ausführen der App in Visual Studio ein Fehlerdialogfeld Unable to connect to web server 'AppName' angezeigt. Führen Sie die folgenden Beispiele für Portänderungen über die Befehlszeile aus.

In den folgenden Abschnitten wird der Port festgelegt, auf den die App reagiert.

var app = WebApplication.Create(args);

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

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

Im vorangehenden Code antwortet die App auf Port 3000.

Mehrere Ports

Im folgenden Code antwortet die App auf Port 3000 und 4000.

var app = WebApplication.Create(args);

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

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

app.Run();

Festlegen des Ports über die Befehlszeile

Mit dem folgenden Befehl antwortet die App auf Port 7777:

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

Wenn der Endpunkt Kestrel ebenfalls in der Datei appsettings.json konfiguriert ist, wird die in der Datei appsettings.json angegebene URL verwendet. Weitere Informationen finden Sie unter Kestrel-Endpunktkonfiguration.

Lesen des Ports aus der Umgebung

Der folgende Code liest den Port aus der Umgebung:

var app = WebApplication.Create(args);

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

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

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

Die bevorzugte Methode zur Festlegung des Ports über die Umgebung ist die Verwendung der Umgebungsvariablen ASPNETCORE_URLS, die im folgenden Abschnitt beschrieben wird.

Festlegen der Ports über die ASPNETCORE_URLS-Umgebungsvariable

Für die Festlegung des Ports steht die Umgebungsvariable ASPNETCORE_URLS zur Verfügung:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS unterstützt mehrere URLs:

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

Lauschen an allen Schnittstellen

Die folgenden Beispiele veranschaulichen das Lauschen an allen Schnittstellen.

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

Lauschen an allen Schnittstellen mit ASPNETCORE_URLS

In den vorherigen Beispielen kann ASPNETCORE_URLS verwendet werden.

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

Angeben von HTTPS mit Entwicklungszertifikat

var app = WebApplication.Create(args);

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

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

app.Run();

Weitere Informationen über das Entwicklungszertifikat finden Sie unter Vertrauen Sie dem ASP.NET Core-HTTPS-Entwicklungszertifikat unter Windows und macOS.

Angeben von HTTPS mithilfe eines benutzerdefinierten Zertifikats

Die folgenden Abschnitte zeigen, wie das benutzerdefinierte Zertifikat mithilfe der Datei appsettings.json und über die Konfiguration angegeben wird.

Angeben des benutzerdefinierten Zertifikats mit appsettings.json

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

Angeben des benutzerdefinierten Zertifikats über die Konfiguration

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

Verwenden der Zertifikat-APIs

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

Lesen der Umgebung

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

Weitere Informationen zur Verwendung der Umgebung finden Sie unter Verwenden mehrerer Umgebungen in ASP.NET Core.

Konfiguration

Der folgende Code liest Informationen aus dem Konfigurationssystem:

var app = WebApplication.Create(args);

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

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

app.Run();

Weitere Informationen finden Sie unter Konfiguration in ASP.NET Core.

Protokollierung

Der folgende Code schreibt eine Meldung in das Anwendungsstartprotokoll:

var app = WebApplication.Create(args);

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

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

app.Run();

Weitere Informationen finden Sie unter Protokollieren in .NET Core und ASP.NET Core.

Zugreifen auf den Container für Abhängigkeitsinjektion

Der folgende Code zeigt, wie Dienste während des Anwendungsstarts aus dem Abhängigkeitsinjektionscontainer abzurufen sind:


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

Weitere Informationen finden Sie unter Abhängigkeitsinjektion in ASP.NET Core.

WebApplicationBuilder

Dieser Abschnitt enthält Beispielcode unter Verwendung von WebApplicationBuilder.

Ändern von Inhaltsstamm, Anwendungsname und Umgebung

Der folgende Code legt den Inhaltsstamm, den Anwendungsnamen und die Umgebung fest:

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 Initialisiert eine neue Instanz der WebApplicationBuilder-Klasse mit vorkonfigurierten Standardwerten.

Weitere Informationen finden Sie unter ASP.NET Core – Grundlagenübersicht.

Ändern von Inhaltsstamm, App-Name und Umgebung über Umgebungsvariablen oder Befehlszeile

Die folgende Tabelle zeigt die Umgebungsvariablen und Befehlszeilenargumente, die zum Ändern von Inhaltsstamm, Anwendungsname und Umgebung verwendet werden:

Feature Umgebungsvariable Befehlszeilenargument
Anwendungsname ASPNETCORE_APPLICATIONNAME --applicationName
Umgebungsname ASPNETCORE_ENVIRONMENT --environment
Inhaltsstammverzeichnis ASPNETCORE_CONTENTROOT --contentRoot

Hinzufügen von Konfigurationsanbietern

Im folgenden Beispiel wird der INI-Konfigurationsanbieter hinzugefügt:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

Ausführliche Informationen finden Sie unter Dateikonfigurationsanbieter in Konfiguration in ASP.NET Core.

Lesen der Konfiguration

Standardmäßig liest die WebApplicationBuilder die Konfiguration aus mehreren Quellen, darunter:

  • appSettings.json und appSettings.{environment}.json
  • Umgebungsvariablen
  • Die Befehlszeile

Eine vollständige Liste der gelesenen Konfigurationsquellen finden Sie unter Standardkonfiguration in Konfiguration in ASP.NET Core.

Der folgende Code liest HelloKey aus der Konfiguration und zeigt den Wert am Endpunkt / an. Wenn der Konfigurationswert NULL ist, wird „Hello“ message zugewiesen:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Lesen der Umgebung

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Hinzufügen von Protokollierungsanbietern

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

Hinzufügen von Diensten

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

Anpassen von IHostBuilder

Vorhandene Erweiterungsmethoden für IHostBuilder können über die Host-Eigenschaft aufgerufen werden:

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

Anpassen von IWebHostBuilder

Erweiterungsmethoden für IWebHostBuilder können über die Eigenschaft WebApplicationBuilder.WebHost aufgerufen werden.

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

Ändern des Webstamms

Standardmäßig ist der Webstamm relativ zum Inhaltsstamm im Ordner wwwroot angegeben. Im Webstamm sucht die Middleware für statische Dateien nach statischen Dateien. Der Webstamm kann mit WebHostOptions, der Befehlszeile oder mit der Methode UseWebRoot geändert werden:

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

var app = builder.Build();

app.Run();

Container für benutzerdefinierte Abhängigkeitsinjektion

Im folgenden Beispiel wird Autofac verwendet:

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

Hinzufügen von Middleware

Für die WebApplication kann eine beliebige vorhandene ASP.NET Core-Middleware konfiguriert werden:

var app = WebApplication.Create(args);

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

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

app.Run();

Weitere Informationen finden Sie unter ASP.NET Core-Middleware.

Seite mit Ausnahmen für Entwickler

WebApplication.CreateBuilder initialisiert eine neue Instanz der WebApplicationBuilder-Klasse mit vorkonfigurierten Standardwerten. Die Seite mit Ausnahmen für Entwickler ist in den vorkonfigurierten Standardwerten aktiviert. Durch Ausführung des folgende Codes in der Entwicklungsumgebung wird beim Navigieren zu / eine benutzerfreundliche Seite geöffnet, auf der die Ausnahme angezeigt wird.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Run();

ASP.NET Core-Middleware

In der folgenden Tabelle werden einige der Middlewarekomponenten aufgeführt, die häufig mit Minimal-APIs verwendet wird.

Middleware Beschreibung API
Authentifizierung Bietet Unterstützung für Authentifizierungen. UseAuthentication
Autorisierung Bietet Unterstützung für Authentifizierungen UseAuthorization
CORS Konfiguriert die Ressourcenfreigabe zwischen verschiedenen Ursprüngen (Cross-Origin Resource Sharing, CORS). UseCors
Ausnahmehandler Behandelt global Ausnahmen, die von der Middlewarepipeline ausgelöst werden. UseExceptionHandler
Weitergeleitete Header Leitet Proxyheader an die aktuelle Anforderung weiter. UseForwardedHeaders
HTTPS-Umleitung Leitet alle HTTP-Anforderungen an HTTPS um. UseHttpsRedirection
HTTP Strict Transport Security (HSTS) Middleware für erweiterte Sicherheit, die einen besonderen Antwortheader hinzufügt. UseHsts
Anforderungsprotokollierung Bietet Unterstützung für die Protokollierung von HTTP-Anforderungen und -Antworten. UseHttpLogging
W3C-Anforderungsprotokollierung Bietet Unterstützung für die Protokollierung von HTTP-Anforderungen und -Antworten im W3C-Format. UseW3CLogging
Zwischenspeichern von Antworten Bietet Unterstützung für das Zwischenspeichern von Antworten. UseResponseCaching
Antwortkomprimierung Bietet Unterstützung für das Komprimieren von Antworten. UseResponseCompression
Sitzung Bietet Unterstützung für das Verwalten von Benutzersitzungen. UseSession
Statische Dateien Bietet Unterstützung für das Verarbeiten statischer Dateien und das Durchsuchen des Verzeichnisses. UseStaticFiles, UseFileServer
WebSockets Aktiviert das WebSockets-Protokoll. UseWebSockets

Anforderungsverarbeitung

In den folgenden Abschnitten werden Routing, Parameterbindung und Antworten behandelt.

Routing

Eine konfigurierte WebApplication unterstützt Map{Verb} und 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();

Routenhandler

Routenhandler sind Methoden, die ausgeführt werden, wenn die Route übereinstimmt. Routenhandler können eine Funktion in beliebiger Form sein, sowohl synchron als auch asynchron. Als Routenhandler kann ein Lambdaausdruck, eine lokale Funktion, eine Instanzmethode oder eine statische Methode verwendet werden.

Lambdaausdruck

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

Lokale Funktion

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

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

app.MapGet("/", LocalFunction);

app.Run();

für eine Instanzmethode

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

für eine statische Methode

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

Endpunkte können Namen erhalten, um URLs für den jeweiligen Endpunkt zu generieren. Durch die Verwendung eines benannten Endpunkts entfällt das Hartcodieren von Pfaden in einer App:

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

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

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

app.Run();

Der vorangehende Code zeigt The link to the hello endpoint is /hello vom Endpunkt / an.

HINWEIS: Für Endpunktnamen muss Groß-/Kleinschreibung beachtet werden.

Endpunktnamen:

  • Dieser muss global eindeutig sein.
  • Werden als OpenAPI-Vorgangs-ID verwendet, wenn die OpenAPI-Unterstützung aktiviert ist. Weitere Informationen finden Sie unter OpenAPI.

Routenparameter

Routenparameter können als Teil der Routenmusterdefinition erfasst werden:

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

Der vorstehende Code gibt The user id is 3 and book id is 7 aus dem URI /users/3/books/7 zurück.

Der Routenhandler kann die zu erfassende Parameter deklarieren. Wenn eine Anforderung über eine Route mit Parametern gestellt wird, die zur Erfassung deklariert sind, werden die Parameter analysiert und an den Handler übergeben. Dadurch können die Werte problemlos typsicher erfasst werden. Im vorangegangenen Code sind userId und bookId beide vom Typ int.

Wenn im vorstehenden Code einer der beiden Routenwerte nicht in den Typ int umgewandelt werden kann, wird eine Ausnahme ausgelöst. Die GET-Anforderung /users/hello/books/3 löst die folgende Ausnahme aus:

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

Platzhalterzeichen und Abfangen aller Routen

Der folgende Code zum Abfangen aller Routen gibt Routing to hello vom Endpunkt „/posts/hello“ zurück:

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

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

app.Run();

Routeneinschränkungen

Routeneinschränkungen schränken das Abgleichsverhalten einer Route ein.

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

Die folgende Tabelle zeigt die vorangegangenen Routenvorlagen und ihr Verhalten:

Routenvorlage Beispiel-URI für Übereinstimmung
/todos/{id:int} /todos/1
/todos/{text} /todos/something
/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

Weitere Informationen finden Sie unter Referenz für Routeneinschränkungen in Routing in ASP.NET Core.

Parameterbindung

Die Parameterbindung ist der Prozess der Umwandlung von Anforderungsdaten in stark typisierte Parameter, die durch Routenhandler ausgedrückt werden. Eine Bindungsquelle bestimmt, von wo aus Parameter gebunden werden. Bindungsquellen können basierend auf der HTTP-Methode und dem Parametertyp explizit sein oder abgeleitet werden.

Unterstützte Bindungsquellen:

  • Routenwerte
  • Abfragezeichenfolge
  • Header
  • Textkörper (als JSON)
  • Von der Abhängigkeitsinjektion bereitgestellte Dienste
  • Benutzerdefiniert

Hinweis

Die Bindung aus Formularen wird in .NET nicht nativ unterstützt.

Im folgenden Beispiel verwendet der GET-Routenhandler einige dieser Parameterbindungsquellen:

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

Die folgende Tabelle zeigt die Beziehung zwischen den im vorherigen Beispiel verwendeten Parametern und den zugeordneten Bindungsquellen.

Parameter Bindungsquelle
id Routenwert
page Abfragezeichenfolge
customHeader header
service Von der Abhängigkeitsinjektion bereitgestellt

Bei den HTTP-Methoden GET, HEAD, OPTIONS und DELETE erfolgt keine implizite Bindung aus dem Text. Um eine Bindung vom Textkörper (als JSON) für diese HTTP-Methoden zu verwenden, führen Sie explizit eine Bindung mit [FromBody] oder einen Lesevorgang aus HttpRequest durch.

Im folgenden Beispiel verwendet der POST-Routenhandler eine Bindungsquelle des Textkörpers (als JSON) für den Parameter person:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

record Person(string Name, int Age);

Die Parameter in den vorherigen Beispielen werden alle automatisch über Anforderungsdaten gebunden. Um die Benutzerfreundlichkeit der Parameterbindung zu veranschaulichen, zeigen die folgenden Beispielroutenhandler, wie Anforderungsdaten direkt aus der Anforderung gelesen werden:

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

    // ...
});

Explizite Parameterbindung

Attribute können verwendet werden, um explizit zu deklarieren, von wo Parameter gebunden werden.

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 Bindungsquelle
id Routenwert mit dem Namen id
page Abfragezeichenfolge mit dem Namen "p"
service Von der Abhängigkeitsinjektion bereitgestellt
contentType Header mit dem Namen "Content-Type"

Hinweis

Die Bindung aus Formularen wird in .NET nicht nativ unterstützt.

Parameterbindung mit DI

Parameterbindung für minimale APIs bindet Parameter durch Abhängigkeitsinjektion (Dependency Injection, DI), wenn der Typ als Dienst konfiguriert wird. Es ist nicht erforderlich, das [FromServices]-Attribut explizit auf einen Parameter anzuwenden. Im folgenden Code geben beide Aktionen die Uhrzeit zurück:

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

Optionale Parameter

In Routenhandlern deklarierte Parameter werden als erforderlich behandelt:

  • Wenn eine Anforderung der Route entspricht, wird der Routenhandler nur ausgeführt, wenn alle erforderlichen Parameter in der Anforderung angegeben sind.
  • Sind nicht alle erforderlichen Parameter enthalten, kommt es zu einem Fehler.
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 zurückgegeben
/products BadHttpRequestException: Der erforderliche Parameter „int pageNumber“ wurde nicht von der Abfragezeichenfolge bereitgestellt.
/products/1 HTTP-Fehler vom Typ 404, keine übereinstimmende Route

Um pageNumber als optional festzulegen, definieren Sie den Typ als optional, oder geben Sie einen Standardwert an:

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 zurückgegeben
/products 1 zurückgegeben
/products2 1 zurückgegeben

Der vorangehende Nullwerte zulassende und Standardwert gilt für alle Quellen:

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

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

app.Run();

Der stehende Code ruft die Methode mit einem NULL-Produkt auf, wenn kein Anforderungstext gesendet wird.

HINWEIS: Wenn ungültige Daten angegeben werden und der Parameter Nullwerte zulässt, wird der Routenhandler nicht ausgeführt.

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 zurückgegeben
/products 1 zurückgegeben
/products?pageNumber=two BadHttpRequestException: Fehler beim Binden von Parameter "Nullable<int> pageNumber" aus „two“.
/products/two HTTP-Fehler vom Typ 404, keine übereinstimmende Route

Weitere Informationen finden Sie im Abschnitt Bindungsfehler.

Sondertypen

Die folgenden Typen werden ohne explizite Attribute gebunden:

  • HttpContext: Der Kontext, der alle Informationen zur aktuellen HTTP-Anforderung oder -Antwort enthält:

    app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
    
  • HttpRequest und HttpResponse: die HTTP-Anforderung und HTTP-Antwort:

    app.MapGet("/", (HttpRequest request, HttpResponse response) =>
        response.WriteAsync($"Hello World {request.Query["name"]}"));
    
  • CancellationToken: das mit der aktuellen HTTP-Anforderung verknüpfte Abbruchtoken:

    app.MapGet("/", async (CancellationToken cancellationToken) => 
        await MakeLongRunningRequestAsync(cancellationToken));
    
  • ClaimsPrincipal: der mit der Anforderung verknüpfte Benutzer, gebunden aus HttpContext.User:

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

Benutzerdefinierte Bindung

Es gibt zwei Möglichkeiten zum Anpassen der Parameterbindung:

  1. Binden Sie für Routen-, Abfrage- und Headerbindungsquellen benutzerdefinierte Typen, indem Sie eine statische TryParse-Methode für den Typ hinzufügen.
  2. Steuern Sie den Bindungsprozess, indem Sie eine BindAsync-Methode für einen Typ implementieren.

TryParse

TryParse umfasst zwei APIs:

public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);

Der folgende Code zeigt Point: 12.3, 10.1 mit dem URI /map?Point=12.3,10.1 an:

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 umfasst die folgenden APIs:

public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);

Der nachstehende Code zeigt SortBy:xyz, SortDirection:Desc, CurrentPage:99 mit dem URI /products?SortBy=xyz&SortDir=Desc&Page=99 an:

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
}

Bindungsfehler

Wenn die Bindung fehlschlägt, protokolliert das Framework eine Debugmeldung und gibt abhängig vom Fehlermodus verschiedene Statuscodes an den Client zurück.

Fehlermodus Parametertypen, die Nullwerte zulassen Bindungsquelle Statuscode
{ParameterType}.TryParse gibt false zurück. ja Route/Abfrage/Header 400
{ParameterType}.BindAsync gibt null zurück. ja custom 400
{ParameterType}.BindAsync wird ausgelöst Nicht relevant custom 500
Fehler beim Deserialisieren des JSON-Textkörpers Nicht relevant body 400
Falscher Inhaltstyp (nicht application/json) Nicht relevant body 415

Bindungsrangfolge

Die Regeln zum Bestimmen einer Bindungsquelle anhand eines Parameters:

  1. Explizites Attribut, das für den Parameter (From*-Attribute) in der folgenden Reihenfolge definiert ist:
    1. Routenwerte: [FromRoute]
    2. Abfragezeichenfolge: [FromQuery]
    3. Header: [FromHeader]
    4. Hauptteil: [FromBody]
    5. Service: [FromServices]
  2. Sondertypen
    1. HttpContext
    2. HttpRequest (HttpContext.Request)
    3. HttpResponse (HttpContext.Response)
    4. ClaimsPrincipal (HttpContext.User)
    5. CancellationToken (HttpContext.RequestAborted)
  3. Der Parametertyp umfasst eine gültige BindAsync-Methode.
  4. Der Parametertyp lautet „string“ oder umfasst eine gültige TryParse-Methode.
    1. Wenn der Parametername in der Routenvorlage vorhanden ist, z. B. app.Map("/todo/{id}", (int id) => {});, wird er über die Route gebunden.
    2. Bindung über die Abfragezeichenfolge.
  5. Wenn der Parametertyp ein durch die Abhängigkeitsinjektion bereitgestellter Dienst ist, wird dieser Dienst als Quelle verwendet.
  6. Der Parameter stammt aus dem Text.

Anpassen der JSON-Bindung

Die Textbindungsquelle verwendet System.Text.Json für die Deserialisierung. Es ist nicht möglich, diese Standardeinstellung zu ändern, aber die Bindung kann mithilfe von anderen zuvor beschriebenen Techniken angepasst werden. Verwenden Sie zum Anpassen von JSON-Serialisierungsoptionen ähnlichen Code wie im folgenden Beispiel:

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

Der vorangehende Code:

  • Konfiguriert die JSON-Standardoptionen sowohl für die Eingabe als auch für die Ausgabe.
  • Gibt folgende JSON zurück
    {
      "id": 1,
      "name": "Joe Smith"
    }
    
    Wenn Folgendes gepostet wird:
    {
      "Id": 1,
      "Name": "Joe Smith"
    }
    

Lesen des Anforderungstexts

Lesen Sie den Anforderungstext direkt mithilfe des Parameters HttpContext oder 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();

Der obige Code:

  • Greift mit HttpRequest.BodyReader auf den Anforderungstext zu.
  • Kopiert den Anforderungstext in eine lokale Datei.

Antworten

Routenhandler unterstützen die folgenden Typen von Rückgabewerten:

  1. IResult-basiert: Dies schließt Task<IResult> und ValueTask<IResult> ein.
  2. string: Dies schließt Task<string> und ValueTask<string> ein.
  3. T (ein beliebiger weiterer Typ): Dies schließt Task<T> und ValueTask<T> ein.
Rückgabewert Verhalten Content-Type
IResult Das Framework ruft IResult.ExecuteAsync auf. Richtet sich nach der IResult-Implementierung
string Das Framework schreibt die Zeichenfolge direkt in die Antwort. text/plain
T (beliebiger anderer Typ) Das Framework wird die Antwort JSON serialisieren application/json

Beispielrückgabewerte

Rückgabewerte vom Typ „string“

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

JSON-Rückgabewerte

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

IResult-Rückgabewerte

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

Im folgenden Beispiel werden die integrierten Ergebnistypen verwendet, um die Antwort anzupassen:

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);
JSEIN
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
Benutzerdefinierter Statuscode
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();
Umleiten
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
Datei
app.MapGet("/download", () => Results.File("myfile.text"));

Integrierte Ergebnisse

Die statische Klasse Microsoft.AspNetCore.Http.Results enthält allgemeine Ergebnishilfen.

Beschreibung Antworttyp Statuscode API
Schreiben einer JSON-Antwort mit erweiterten Optionen Anwendung/json 200 Results.Json
Schreiben einer JSON-Antwort Anwendung/json 200 Results.Ok
Schreiben einer Textantwort text/plain (Standard), konfigurierbar 200 Results.Text
Schreiben der Antwort in Byte application/octet-stream (Standard), konfigurierbar 200 Results.Bytes
Schreiben eines Bytestreams in die Antwort application/octet-stream (Standard), konfigurierbar 200 Results.Stream
Streamen einer Datei in die Antwort zum Herunterladen mit dem content-disposition-Header application/octet-stream (Standard), konfigurierbar 200 Results.File
Festlegen des Statuscodes auf 404 mit optionaler JSON-Antwort 404 Results.NotFound
Festlegen des Statuscodes auf 204 204 Results.NoContent
Festlegen des Statuscodes auf 422 mit optionaler JSON-Antwort 422 Results.UnprocessableEntity
Festlegen des Statuscodes auf 400 mit optionaler JSON-Antwort 400 Results.BadRequest
Festlegen des Statuscodes auf 409 mit optionaler JSON-Antwort 409 Results.Conflict
Schreiben eines JSON-Objekts mit Problemdetails in die Antwort 500 (Standard), konfigurierbar Results.Problem
Schreiben eines JSON-Objekts mit Problemdetails in die Antwort, mit Validierungsfehlern –, konfigurierbar Results.ValidationProblem

Anpassen von Ergebnissen

Anwendungen können Antworten steuern, indem sie einen benutzerdefinierten IResult-Typ implementieren. Der folgende Code ist ein Beispiel für einen HTML-Ergebnistyp:

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

Es wird empfohlen, Microsoft.AspNetCore.Http.IResultExtensions eine Erweiterungsmethode hinzuzufügen, um diese benutzerdefinierten Ergebnisse leichter auffindbar zu machen.

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

Autorisierung

Routen können mithilfe von Autorisierungsrichtlinien geschützt werden. Diese können mit dem Attribut [Authorize] oder der Methode RequireAuthorization angegeben werden.

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

Der vorangehende Code kann mit RequireAuthorization geschrieben werden:

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

Im folgenden Beispiel wird die richtlinienbasierte Autorisierung verwendet:

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

Nicht authentifizierten Benutzern den Zugriff auf einen Endpunkt gestatten

Durch [AllowAnonymous] können nicht authentifizierte Benutzer auf Endpunkte zugreifen:

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


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

CORS

Routen können mithilfe von CORS-Richtlinien für CORS aktiviert werden. CORS kann über das Attribut [EnableCors] oder mit der Methode RequireCors deklariert werden. In den folgenden Beispielen wird CORS aktiviert:

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

Weitere Informationen finden Sie unter Aktivieren ursprungsübergreifender Anforderungen (Cross-Origin Requests, CORS) in ASP.NET Core.

Siehe auch

OpenAPI-Unterstützung in minimalen APIs