Minimal-APIs: Kurzreferenz
Hinweis
Dies ist nicht die neueste Version dieses Artikels. Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Warnung
Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der .NET- und .NET Core-Supportrichtlinie. Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Wichtig
Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.
Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Dieses Dokument hat folgende Eigenschaften:
- Stellt eine Kurzreferenz für Minimal-APIs bereit.
- Ist für erfahrene Entwickler vorgesehen. Eine Einführung finden Sie im Tutorial: Erstellen einer Minimal-API mit ASP.NET Core.
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, wennHostingEnvironment
gleich"Development"
ist.UseRouting
wird zweitens hinzugefügt, wenn der BenutzercodeUseRouting
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 nachUseRouting
hinzugefügt, wenn der BenutzercodeUseAuthentication
noch nicht aufgerufen hat und wennIAuthenticationSchemeProvider
im Dienstanbieter erkannt werden kann.IAuthenticationSchemeProvider
wird standardmäßig hinzugefügt, wenn die VerwendungAddAuthentication
von Diensten mitIServiceProviderIsService
erkannt wird.UseAuthorization
wird als Nächstes hinzugefügt, wenn der BenutzercodeUseAuthorization
noch nicht aufgerufen hat und wennIAuthorizationHandlerProvider
im Dienstanbieter erkannt werden kann.IAuthorizationHandlerProvider
wird standardmäßig hinzugefügt, wennAddAuthorization
verwendet wird, und Dienste mitIServiceProviderIsService
erkannt werden.- Benutzerkonfigurierte Middleware und Endpunkte werden zwischen
UseRouting
undUseEndpoints
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
undUseEndpoints
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.json
angegebene 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
undappSettings.{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.
Benannte Endpunkte und Linkgenerierung
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 route 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 QueryPrivateTodos
-Endpunktfilterfactory 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
- Text (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 Antifälschungstoken. Das Antifä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 wieHttpRequest.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
undPipeReader
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 Cross-Site Request Forgery-Angriffe (XSRF/CSRF) verhindern. Im vorherigen Code wird der IAntiforgery-Dienst verwendet, um XSRF-Angriffe zu verhindern, indem ein Antifälschungstoken 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
oderProject
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 antiforgery 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));
}
Im obigen Code:
- Der Zielparameter muss mit dem Attribut
[FromForm]
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 Wertfalse
. Wenn das KontrollkästchenisCompleted
beim Senden des Formulars aktiviert ist, werden die Wertetrue
undfalse
beide als Werte übermittelt. Ist das Kontrollkästchen nicht aktiviert, wird nur der Wertfalse
der ausgeblendeten Eingabe übermittelt. Der ASP.NET Core-Modellbindungsprozess liest bei der Bindung an einenbool
-Wert nur den ersten Wert. Dies führt bei aktivierten Kontrollkästchen zum Ergebnistrue
und bei nicht aktivierten Kontrollkästchen zum Ergebnisfalse
.
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:
- Binden Sie für Routen-, Abfrage- und Headerbindungsquellen benutzerdefinierte Typen, indem Sie eine statische
TryParse
-Methode für den Typ hinzufügen. - 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-Texts | 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:
- Explizites Attribut, das für den Parameter (From*-Attribute) in der folgenden Reihenfolge definiert ist:
- Routenwerte:
[FromRoute]
- Abfragezeichenfolge:
[FromQuery]
- Header:
[FromHeader]
- Hauptteil:
[FromBody]
- Formular:
[FromForm]
- Service:
[FromServices]
- Parameterwerte:
[AsParameters]
- Routenwerte:
- Sondertypen
HttpContext
HttpRequest
(HttpContext.Request
)HttpResponse
(HttpContext.Response
)ClaimsPrincipal
(HttpContext.User
)CancellationToken
(HttpContext.RequestAborted
)IFormCollection
(HttpContext.Request.Form
)IFormFileCollection
(HttpContext.Request.Form.Files
)IFormFile
(HttpContext.Request.Form.Files[paramName]
)Stream
(HttpContext.Request.Body
)PipeReader
(HttpContext.Request.BodyReader
)
- Der Parametertyp verfügt über eine gültige statische
BindAsync
-Methode. - Der Parametertyp lautet „string“ oder verfügt über eine gültige statische
TryParse
-Methode.- Wenn der Parametername in der Routenvorlage vorhanden ist (z. B.
app.Map("/todo/{id}", (int id) => {});
), wird er über die Route gebunden. - Bindung über die Abfragezeichenfolge.
- Wenn der Parametername in der Routenvorlage vorhanden ist (z. B.
- Wenn der Parametertyp ein durch die Abhängigkeitsinjektion bereitgestellter Dienst ist, wird dieser Dienst als Quelle verwendet.
- 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:
IResult
-basiert: Dies schließtTask<IResult>
undValueTask<IResult>
ein.string
: Dies schließtTask<string>
undValueTask<string>
ein.T
(ein beliebiger weiterer Typ): Dies schließtTask<T>
undValueTask<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);
JSON
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.
ValidateScopes und ValidateOnBuild
ValidateScopes und ValidateOnBuild sind standardmäßig in der Entwicklungsumgebung aktiviert, aber in anderen Umgebungen deaktiviert.
Wenn ValidateOnBuild
true
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 ValidateScopes
true
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
- Minimal-APIs: Kurzreferenz
- OpenAPI-Dokumente generieren
- Erstellen von Antworten in Minimal-API-Anwendungen
- Filter in Minimal-API-Apps
- Behandeln von Fehlern in minimalen APIs
- Authentifizierung und Autorisierung in Minimal-API-Apps
- Testen von Minimal-API-Apps
- Kurzschlussrouting
- Identity-API-Endpunkte
- Unterstützung von Containern für abhängigkeitsbasierte Dienste
- Ein Blick hinter die Kulissen von minimalen API-Endpunkten
- Organisieren von Minimal-APIs für ASP.NET Core
- Fluent-Validierungsdiskussion auf GitHub
Dieses Dokument hat folgende Eigenschaften:
- Stellt eine Kurzreferenz für Minimal-APIs bereit.
- Ist für erfahrene Entwickler vorgesehen. Eine Einführung finden Sie im Tutorial: Erstellen einer minimalen API mit ASP.NET Core.
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, wennHostingEnvironment
gleich"Development"
ist.UseRouting
wird zweitens hinzugefügt, wenn der BenutzercodeUseRouting
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 nachUseRouting
hinzugefügt, wenn der BenutzercodeUseAuthentication
noch nicht aufgerufen hat und wennIAuthenticationSchemeProvider
im Dienstanbieter erkannt werden kann.IAuthenticationSchemeProvider
wird standardmäßig hinzugefügt, wenn die VerwendungAddAuthentication
von Diensten mitIServiceProviderIsService
erkannt wird.UseAuthorization
wird als Nächstes hinzugefügt, wenn der BenutzercodeUseAuthorization
noch nicht aufgerufen hat und wennIAuthorizationHandlerProvider
im Dienstanbieter erkannt werden kann.IAuthorizationHandlerProvider
wird standardmäßig hinzugefügt, wennAddAuthorization
verwendet wird, und Dienste mitIServiceProviderIsService
erkannt werden.- Benutzerkonfigurierte Middleware und Endpunkte werden zwischen
UseRouting
undUseEndpoints
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
undUseEndpoints
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.json
angegebene 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
undappSettings.{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.
Benannte Endpunkte und Linkgenerierung
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 route 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 QueryPrivateTodos
-Endpunktfilterfactory 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
- Text (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 wieHttpRequest.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
undPipeReader
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:
- Binden Sie für Routen-, Abfrage- und Headerbindungsquellen benutzerdefinierte Typen, indem Sie eine statische
TryParse
-Methode für den Typ hinzufügen. - 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-Texts | Nicht relevant | body | 400 |
Falscher Inhaltstyp (nicht application/json ) |
Nicht relevant | body | 415 |
Bindungsrangfolge
Die Regeln zum Bestimmen einer Bindungsquelle anhand eines Parameters:
- Explizites Attribut, das für den Parameter (From*-Attribute) in der folgenden Reihenfolge definiert ist:
- Routenwerte:
[FromRoute]
- Abfragezeichenfolge:
[FromQuery]
- Header:
[FromHeader]
- Hauptteil:
[FromBody]
- Service:
[FromServices]
- Parameterwerte:
[AsParameters]
- Routenwerte:
- Sondertypen
HttpContext
HttpRequest
(HttpContext.Request
)HttpResponse
(HttpContext.Response
)ClaimsPrincipal
(HttpContext.User
)CancellationToken
(HttpContext.RequestAborted
)IFormFileCollection
(HttpContext.Request.Form.Files
)IFormFile
(HttpContext.Request.Form.Files[paramName]
)Stream
(HttpContext.Request.Body
)PipeReader
(HttpContext.Request.BodyReader
)
- Der Parametertyp verfügt über eine gültige statische
BindAsync
-Methode. - Der Parametertyp lautet „string“ oder verfügt über eine gültige statische
TryParse
-Methode.- Wenn der Parametername in der Routenvorlage vorhanden ist. In
app.Map("/todo/{id}", (int id) => {});
gehtid
von der Route aus. - Bindung über die Abfragezeichenfolge.
- Wenn der Parametername in der Routenvorlage vorhanden ist. In
- Wenn der Parametertyp ein durch die Abhängigkeitsinjektion bereitgestellter Dienst ist, wird dieser Dienst als Quelle verwendet.
- 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:
IResult
-basiert: Dies schließtTask<IResult>
undValueTask<IResult>
ein.string
: Dies schließtTask<string>
undValueTask<string>
ein.T
(ein beliebiger weiterer Typ): Dies schließtTask<T>
undValueTask<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);
JSON
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:
- Stellt eine Kurzreferenz für Minimal-APIs bereit.
- Ist für erfahrene Entwickler vorgesehen. Eine Einführung finden Sie im Tutorial: Erstellen einer minimalen API mit ASP.NET Core.
Die Minimal-APIs bestehen aus:
- WebApplication und WebApplicationBuilder
- Routenhandler
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
undappSettings.{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";
}
}
Benannte Endpunkte und Linkgenerierung
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
- Text (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:
- Binden Sie für Routen-, Abfrage- und Headerbindungsquellen benutzerdefinierte Typen, indem Sie eine statische
TryParse
-Methode für den Typ hinzufügen. - 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-Texts | Nicht relevant | body | 400 |
Falscher Inhaltstyp (nicht application/json ) |
Nicht relevant | body | 415 |
Bindungsrangfolge
Die Regeln zum Bestimmen einer Bindungsquelle anhand eines Parameters:
- Explizites Attribut, das für den Parameter (From*-Attribute) in der folgenden Reihenfolge definiert ist:
- Routenwerte:
[FromRoute]
- Abfragezeichenfolge:
[FromQuery]
- Header:
[FromHeader]
- Hauptteil:
[FromBody]
- Service:
[FromServices]
- Routenwerte:
- Sondertypen
- Der Parametertyp umfasst eine gültige
BindAsync
-Methode. - Der Parametertyp lautet „string“ oder umfasst eine gültige
TryParse
-Methode.- Wenn der Parametername in der Routenvorlage vorhanden ist. In
app.Map("/todo/{id}", (int id) => {});
gehtid
von der Route aus. - Bindung über die Abfragezeichenfolge.
- Wenn der Parametername in der Routenvorlage vorhanden ist. In
- Wenn der Parametertyp ein durch die Abhängigkeitsinjektion bereitgestellter Dienst ist, wird dieser Dienst als Quelle verwendet.
- 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 Code ähnlich dem folgenden:
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 folgenden JSON-Code zurück:
Wenn Folgendes gepostet wird:{ "id": 1, "name": "Joe Smith" }
{ "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:
IResult
-basiert: Dies schließtTask<IResult>
undValueTask<IResult>
ein.string
: Dies schließtTask<string>
undValueTask<string>
ein.T
(ein beliebiger weiterer Typ): Dies schließtTask<T>
undValueTask<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 serialisiert die Antwort im JSON-Format. | 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);
JSON
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 | N/V | 404 | Results.NotFound |
Festlegen des Statuscodes auf 204 | – | 204 | Results.NoContent |
Festlegen des Statuscodes auf 422 mit optionaler JSON-Antwort | N/V | 422 | Results.UnprocessableEntity |
Festlegen des Statuscodes auf 400 mit optionaler JSON-Antwort | N/V | 400 | Results.BadRequest |
Festlegen des Statuscodes auf 409 mit optionaler JSON-Antwort | N/V | 409 | Results.Conflict |
Schreiben eines JSON-Objekts mit Problemdetails in die Antwort | N/V | 500 (Standard), konfigurierbar | Results.Problem |
Schreiben eines JSON-Objekts mit Problemdetails in die Antwort, mit Validierungsfehlern | N/V | –, 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.