Ereignisse
Power BI DataViz Weltmeisterschaften
14. Feb., 16 Uhr - 31. März, 16 Uhr
Mit 4 Chancen, ein Konferenzpaket zu gewinnen und es zum LIVE Grand Finale in Las Vegas zu machen
Weitere InformationenDieser Browser wird nicht mehr unterstützt.
Führen Sie ein Upgrade auf Microsoft Edge durch, um die neuesten Features, Sicherheitsupdates und den technischen Support zu nutzen.
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:
Die Minimal-APIs bestehen aus:
Der folgende Code wird von einer ASP.NET Core-Vorlage generiert:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Der vorstehende Code kann über dotnet new web
in der Befehlszeile oder durch Auswahl der leeren Webvorlage in Visual Studio erstellt werden.
Mit dem folgenden Code wird eine WebApplication (app
) erstellt, ohne explizit einen WebApplicationBuilder zu erstellen:
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run();
WebApplication.Create
initialisiert eine neue Instanz der WebApplication-Klasse mit vorkonfigurierten Standardwerten.
WebApplication
fügt Minimal API applications
abhängig von bestimmten Bedingungen automatisch die folgende Middleware hinzu:
UseDeveloperExceptionPage
wird zuerst hinzugefügt, wenn HostingEnvironment
gleich "Development"
ist.UseRouting
wird zweitens hinzugefügt, wenn der Benutzercode UseRouting
noch nicht aufgerufen hat, und wenn Endpunkte konfiguriert sind, z. B. app.MapGet
.UseEndpoints
wird am Ende der Middlewarepipeline hinzugefügt, wenn Endpunkte konfiguriert sind.UseAuthentication
wird unmittelbar nach UseRouting
hinzugefügt, wenn der Benutzercode UseAuthentication
noch nicht aufgerufen hat und wenn IAuthenticationSchemeProvider
im Dienstanbieter erkannt werden kann. IAuthenticationSchemeProvider
wird standardmäßig hinzugefügt, wenn die Verwendung AddAuthentication
von Diensten mit IServiceProviderIsService
erkannt wird.UseAuthorization
wird als Nächstes hinzugefügt, wenn der Benutzercode UseAuthorization
noch nicht aufgerufen hat und wenn IAuthorizationHandlerProvider
im Dienstanbieter erkannt werden kann. IAuthorizationHandlerProvider
wird standardmäßig hinzugefügt, wenn die Verwendung AddAuthorization
von Diensten mit IServiceProviderIsService
erkannt wird.UseRouting
und UseEndpoints
hinzugefügt.Nachfolgend sehen Sie den Code, der von der automatischen Middleware erzeugt wird, die zur App hinzugefügt wird:
if (isDevelopment)
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
if (isAuthenticationConfigured)
{
app.UseAuthentication();
}
if (isAuthorizationConfigured)
{
app.UseAuthorization();
}
// user middleware/endpoints
app.CustomMiddleware(...);
app.MapGet("/", () => "hello world");
// end user middleware/endpoints
app.UseEndpoints(e => {});
In einigen Fällen eignet sich die standardmäßige Middleware-Konfiguration nicht für die App und muss geändert werden. Beispielsweise sollte UseCors vor UseAuthentication und UseAuthorization aufgerufen werden. Die App muss UseAuthentication
und UseAuthorization
aufrufen, wenn UseCors
aufgerufen wird:
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
Wenn Middleware ausgeführt werden muss, bevor der Routenabgleich erfolgt, muss UseRouting aufgerufen werden, und die Middleware muss vor dem Aufruf von UseRouting
platziert werden. UseEndpoints ist in diesem Fall nicht erforderlich, da es wie zuvor beschrieben automatisch hinzugefügt wird:
app.Use((context, next) =>
{
return next(context);
});
app.UseRouting();
// other middleware and endpoints
Beim Hinzufügen einer Terminal-Middleware:
UseEndpoints
hinzugefügt werden.UseRouting
und UseEndpoints
aufrufen, damit die Terminal-Middleware an der richtigen Position platziert werden kann.app.UseRouting();
app.MapGet("/", () => "hello world");
app.UseEndpoints(e => {});
app.Run(context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
});
Terminal-Middleware ist Middleware, die ausgeführt wird, wenn kein Endpunkt die Anforderung verarbeitet.
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
.
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();
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.
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.
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
Die folgenden Beispiele veranschaulichen das Lauschen an allen Schnittstellen.
var app = WebApplication.Create(args);
app.Urls.Add("http://*:3000");
app.MapGet("/", () => "Hello World");
app.Run();
var app = WebApplication.Create(args);
app.Urls.Add("http://+:3000");
app.MapGet("/", () => "Hello World");
app.Run();
var app = WebApplication.Create(args);
app.Urls.Add("http://0.0.0.0:3000");
app.MapGet("/", () => "Hello World");
app.Run();
In den vorherigen Beispielen kann ASPNETCORE_URLS
verwendet werden.
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
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.
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.
Die folgenden Abschnitte zeigen, wie das benutzerdefinierte Zertifikat mithilfe der Datei appsettings.json
und über die Konfiguration angegeben wird.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Certificates": {
"Default": {
"Path": "cert.pem",
"KeyPath": "key.pem"
}
}
}
}
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();
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();
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.
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.
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.
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.
Dieser Abschnitt enthält Beispielcode unter Verwendung von WebApplicationBuilder.
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.
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 |
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.
Standardmäßig liest die WebApplicationBuilder die Konfiguration aus mehreren Quellen, darunter:
appSettings.json
und appSettings.{environment}.json
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();
var builder = WebApplication.CreateBuilder(args);
if (builder.Environment.IsDevelopment())
{
Console.WriteLine($"Running in development.");
}
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
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();
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();
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();
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();
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();
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();
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.
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();
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.
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 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.
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();
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
string LocalFunction() => "This is local function";
app.MapGet("/", LocalFunction);
app.Run();
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";
}
}
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";
}
}
Minimale APIs müssen sich nicht in Program.cs
befinden.
Program.cs
using MinAPISeparateFile;
var builder = WebApplication.CreateSlimBuilder(args);
var app = builder.Build();
TodoEndpoints.Map(app);
app.Run();
TodoEndpoints.cs
namespace MinAPISeparateFile;
public static class TodoEndpoints
{
public static void Map(WebApplication app)
{
app.MapGet("/", async context =>
{
// Get all todo items
await context.Response.WriteAsJsonAsync(new { Message = "All todo items" });
});
app.MapGet("/{id}", async context =>
{
// Get one todo item
await context.Response.WriteAsJsonAsync(new { Message = "One todo item" });
});
}
}
Weitere Informationen dazu finden Sie auch unter Routengruppen weiter unten in diesem Artikel.
Endpunkte können Namen erhalten, um URLs für den jeweiligen Endpunkt zu generieren. Durch die Verwendung eines benannten Endpunkts entfällt das Hartcodieren von Pfaden in einer App:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/hello", () => "Hello named route")
.WithName("hi");
app.MapGet("/", (LinkGenerator linker) =>
$"The link to the hello route is {linker.GetPathByName("hi", values: null)}");
app.Run();
Der vorangehende Code zeigt The link to the hello route is /hello
vom Endpunkt /
an.
HINWEIS: Für Endpunktnamen muss Groß-/Kleinschreibung beachtet werden.
Endpunktnamen:
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".
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 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.
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
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:
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>();
// ...
});
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" |
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.
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 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();
In Routenhandlern deklarierte Parameter werden als erforderlich behandelt:
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.
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);
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:
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();
Stream
dasselbe Objekt wie HttpRequest.Body
.Stream
und PipeReader
sind außerhalb des minimalen Aktionshandlers nicht verwendbar, da die zugrunde liegenden Puffer verworfen oder wiederverwendet werden.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.
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.
Die Bindung wird unterstützt für:
Todo
oder Project
Dies wird im folgenden Code veranschaulicht:
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:
[FromForm]
versehen werden, um ihn von den Parametern zu unterscheiden, die aus dem JSON-Text gelesen werden sollen.isCompleted
und dem Wert false
. Wenn das Kontrollkästchen isCompleted
beim Senden des Formulars aktiviert ist, werden die Werte true
und false
beide als Werte übermittelt. Ist das Kontrollkästchen nicht aktiviert, wird nur der Wert false
der ausgeblendeten Eingabe übermittelt. Der ASP.NET Core-Modellbindungsprozess liest bei der Bindung an einen bool
-Wert nur den ersten Wert. Dies führt bei aktivierten Kontrollkästchen zum Ergebnis true
und bei nicht aktivierten Kontrollkästchen zum Ergebnis false
.Ein Beispiel für die an den vorherigen Endpunkt übermittelten Formulardaten sieht wie folgt aus:
__RequestVerificationToken: CfDJ8Bveip67DklJm5vI2PF2VOUZ594RC8kcGWpTnVV17zCLZi1yrs-CSz426ZRRrQnEJ0gybB0AD7hTU-0EGJXDU-OaJaktgAtWLIaaEWMOWCkoxYYm-9U9eLV7INSUrQ6yBHqdMEE_aJpD4AI72gYiCqc
name: Walk the dog
dueDate: 2024-04-06
isCompleted: true
isCompleted: false
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.
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.
Es gibt zwei Möglichkeiten zum Anpassen der Parameterbindung:
TryParse
-Methode für den Typ hinzufügen.BindAsync
-Methode für einen Typ implementieren.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
umfasst die folgenden APIs:
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
Der folgende 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
}
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 |
Die Regeln zum Bestimmen einer Bindungsquelle anhand eines Parameters:
[FromRoute]
[FromQuery]
[FromHeader]
[FromBody]
[FromForm]
[FromServices]
[AsParameters]
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
)BindAsync
-Methode.TryParse
-Methode.
app.Map("/todo/{id}", (int id) => {});
), wird er über die Route gebunden.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.
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.
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 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:
Routenhandler unterstützen die folgenden Typen von Rückgabewerten:
IResult
-basiert: Dies schließt Task<IResult>
und ValueTask<IResult>
ein.string
: Dies schließt Task<string>
und ValueTask<string>
ein.T
(ein beliebiger weiterer Typ): Dies schließt Task<T>
und ValueTask<T>
ein.Rückgabewert | Verhalten | Content-Type |
---|---|---|
IResult |
Das Framework ruft IResult.ExecuteAsync auf. | Richtet sich nach der IResult -Implementierung |
string |
Das Framework schreibt die Zeichenfolge direkt in die Antwort. | text/plain |
T (beliebiger anderer Typ) |
Das Framework JSON-serialisiert die Antwort. | application/json |
Ausführlichere Anleitungen zu Rückgabewerten des Routenhandlers finden Sie unter Erstellen von Antworten in Minimal-API-Anwendungen.
app.MapGet("/hello", () => "Hello World");
app.MapGet("/hello", () => new { Message = "Hello World" });
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.
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);
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
app.MapGet("/405", () => Results.StatusCode(405));
app.MapGet("/text", () => Results.Text("This is some text"));
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.
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
app.MapGet("/download", () => Results.File("myfile.text"));
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.
Verwenden Sie das HttpResponse
-Objekt, um Antwortheader zu ändern:
app.MapGet("/", (HttpContext context) => {
// Set a custom header
context.Response.Headers["X-Custom-Header"] = "CustomValue";
// Set a known header
context.Response.Headers.CacheControl = $"public,max-age=3600";
return "Hello World";
});
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();
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.
Weitere Informationen finden Sie unter Filter in Minimal-API-Apps.
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();
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();
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 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;
});
}
Dieses Dokument hat folgende Eigenschaften:
Die Minimal-APIs bestehen aus:
Der folgende Code wird von einer ASP.NET Core-Vorlage generiert:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Der vorstehende Code kann über dotnet new web
in der Befehlszeile oder durch Auswahl der leeren Webvorlage in Visual Studio erstellt werden.
Mit dem folgenden Code wird eine WebApplication (app
) erstellt, ohne explizit einen WebApplicationBuilder zu erstellen:
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run();
WebApplication.Create
initialisiert eine neue Instanz der WebApplication-Klasse mit vorkonfigurierten Standardwerten.
WebApplication
fügt Minimal API applications
abhängig von bestimmten Bedingungen automatisch die folgende Middleware hinzu:
UseDeveloperExceptionPage
wird zuerst hinzugefügt, wenn HostingEnvironment
gleich "Development"
ist.UseRouting
wird zweitens hinzugefügt, wenn der Benutzercode UseRouting
noch nicht aufgerufen hat, und wenn Endpunkte konfiguriert sind, z. B. app.MapGet
.UseEndpoints
wird am Ende der Middlewarepipeline hinzugefügt, wenn Endpunkte konfiguriert sind.UseAuthentication
wird unmittelbar nach UseRouting
hinzugefügt, wenn der Benutzercode UseAuthentication
noch nicht aufgerufen hat und wenn IAuthenticationSchemeProvider
im Dienstanbieter erkannt werden kann. IAuthenticationSchemeProvider
wird standardmäßig hinzugefügt, wenn die Verwendung AddAuthentication
von Diensten mit IServiceProviderIsService
erkannt wird.UseAuthorization
wird als Nächstes hinzugefügt, wenn der Benutzercode UseAuthorization
noch nicht aufgerufen hat und wenn IAuthorizationHandlerProvider
im Dienstanbieter erkannt werden kann. IAuthorizationHandlerProvider
wird standardmäßig hinzugefügt, wenn die Verwendung AddAuthorization
von Diensten mit IServiceProviderIsService
erkannt wird.UseRouting
und UseEndpoints
hinzugefügt.Nachfolgend sehen Sie den Code, der von der automatischen Middleware erzeugt wird, die zur App hinzugefügt wird:
if (isDevelopment)
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
if (isAuthenticationConfigured)
{
app.UseAuthentication();
}
if (isAuthorizationConfigured)
{
app.UseAuthorization();
}
// user middleware/endpoints
app.CustomMiddleware(...);
app.MapGet("/", () => "hello world");
// end user middleware/endpoints
app.UseEndpoints(e => {});
In einigen Fällen eignet sich die standardmäßige Middleware-Konfiguration nicht für die App und muss geändert werden. Beispielsweise sollte UseCors vor UseAuthentication und UseAuthorization aufgerufen werden. Die App muss UseAuthentication
und UseAuthorization
aufrufen, wenn UseCors
aufgerufen wird:
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
Wenn Middleware ausgeführt werden muss, bevor der Routenabgleich erfolgt, muss UseRouting aufgerufen werden, und die Middleware muss vor dem Aufruf von UseRouting
platziert werden. UseEndpoints ist in diesem Fall nicht erforderlich, da es wie zuvor beschrieben automatisch hinzugefügt wird:
app.Use((context, next) =>
{
return next(context);
});
app.UseRouting();
// other middleware and endpoints
Beim Hinzufügen einer Terminal-Middleware:
UseEndpoints
hinzugefügt werden.UseRouting
und UseEndpoints
aufrufen, damit die Terminal-Middleware an der richtigen Position platziert werden kann.app.UseRouting();
app.MapGet("/", () => "hello world");
app.UseEndpoints(e => {});
app.Run(context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
});
Terminal-Middleware ist Middleware, die ausgeführt wird, wenn kein Endpunkt die Anforderung verarbeitet.
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
.
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();
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.
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.
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.
Die folgenden Beispiele veranschaulichen das Lauschen an allen Schnittstellen.
var app = WebApplication.Create(args);
app.Urls.Add("http://*:3000");
app.MapGet("/", () => "Hello World");
app.Run();
var app = WebApplication.Create(args);
app.Urls.Add("http://+:3000");
app.MapGet("/", () => "Hello World");
app.Run();
var app = WebApplication.Create(args);
app.Urls.Add("http://0.0.0.0:3000");
app.MapGet("/", () => "Hello World");
app.Run();
In den vorherigen Beispielen kann ASPNETCORE_URLS
verwendet werden.
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
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.
Die folgenden Abschnitte zeigen, wie das benutzerdefinierte Zertifikat mithilfe der Datei appsettings.json
und über die Konfiguration angegeben wird.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Certificates": {
"Default": {
"Path": "cert.pem",
"KeyPath": "key.pem"
}
}
}
}
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();
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();
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.
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.
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.
Dieser Abschnitt enthält Beispielcode unter Verwendung von WebApplicationBuilder.
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.
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 |
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.
Standardmäßig liest die WebApplicationBuilder die Konfiguration aus mehreren Quellen, darunter:
appSettings.json
und appSettings.{environment}.json
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.
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();
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();
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();
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();
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();
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();
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.
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();
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.
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 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.
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();
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
string LocalFunction() => "This is local function";
app.MapGet("/", LocalFunction);
app.Run();
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";
}
}
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";
}
}
Minimale APIs müssen sich nicht in Program.cs
befinden.
Program.cs
using MinAPISeparateFile;
var builder = WebApplication.CreateSlimBuilder(args);
var app = builder.Build();
TodoEndpoints.Map(app);
app.Run();
TodoEndpoints.cs
namespace MinAPISeparateFile;
public static class TodoEndpoints
{
public static void Map(WebApplication app)
{
app.MapGet("/", async context =>
{
// Get all todo items
await context.Response.WriteAsJsonAsync(new { Message = "All todo items" });
});
app.MapGet("/{id}", async context =>
{
// Get one todo item
await context.Response.WriteAsJsonAsync(new { Message = "One todo item" });
});
}
}
Weitere Informationen dazu finden Sie auch unter Routengruppen weiter unten in diesem Artikel.
Endpunkte können Namen erhalten, um URLs für den jeweiligen Endpunkt zu generieren. Durch die Verwendung eines benannten Endpunkts entfällt das Hartcodieren von Pfaden in einer App:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/hello", () => "Hello named route")
.WithName("hi");
app.MapGet("/", (LinkGenerator linker) =>
$"The link to the hello route is {linker.GetPathByName("hi", values: null)}");
app.Run();
Der vorangehende Code zeigt The link to the hello route is /hello
vom Endpunkt /
an.
HINWEIS: Für Endpunktnamen muss Groß-/Kleinschreibung beachtet werden.
Endpunktnamen:
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".
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 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.
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
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:
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>();
// ...
});
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 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();
In Routenhandlern deklarierte Parameter werden als erforderlich behandelt:
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.
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);
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:
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();
Stream
dasselbe Objekt wie HttpRequest.Body
.Stream
und PipeReader
sind außerhalb des minimalen Aktionshandlers nicht verwendbar, da die zugrunde liegenden Puffer verworfen oder wiederverwendet werden.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.
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.
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.
Es gibt zwei Möglichkeiten zum Anpassen der Parameterbindung:
TryParse
-Methode für den Typ hinzufügen.BindAsync
-Methode für einen Typ implementieren.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
umfasst die folgenden APIs:
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
Der folgende 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
}
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 | Text | 400 |
Falscher Inhaltstyp (nicht application/json ) |
Nicht relevant | Text | 415 |
Die Regeln zum Bestimmen einer Bindungsquelle anhand eines Parameters:
[FromRoute]
[FromQuery]
[FromHeader]
[FromBody]
[FromServices]
[AsParameters]
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
)BindAsync
-Methode.TryParse
-Methode.
app.Map("/todo/{id}", (int id) => {});
geht id
von der Route aus.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.
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.
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 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:
Routenhandler unterstützen die folgenden Typen von Rückgabewerten:
IResult
-basiert: Dies schließt Task<IResult>
und ValueTask<IResult>
ein.string
: Dies schließt Task<string>
und ValueTask<string>
ein.T
(ein beliebiger weiterer Typ): Dies schließt Task<T>
und ValueTask<T>
ein.Rückgabewert | Verhalten | Content-Type |
---|---|---|
IResult |
Das Framework ruft IResult.ExecuteAsync auf. | Richtet sich nach der IResult -Implementierung |
string |
Das Framework schreibt die Zeichenfolge direkt in die Antwort. | text/plain |
T (beliebiger anderer Typ) |
Das Framework JSON-serialisiert die Antwort. | application/json |
Ausführlichere Anleitungen zu Rückgabewerten des Routenhandlers finden Sie unter Erstellen von Antworten in Minimal-API-Anwendungen.
app.MapGet("/hello", () => "Hello World");
app.MapGet("/hello", () => new { Message = "Hello World" });
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.
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);
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
app.MapGet("/405", () => Results.StatusCode(405));
app.MapGet("/text", () => Results.Text("This is some text"));
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.
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
app.MapGet("/download", () => Results.File("myfile.text"));
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.
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();
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.
Weitere Informationen finden Sie unter Filter in Minimal-API-Apps.
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();
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();
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.
Dieses Dokument hat folgende Eigenschaften:
Die Minimal-APIs bestehen aus:
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.
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
.
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();
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.
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.
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
Die folgenden Beispiele veranschaulichen das Lauschen an allen Schnittstellen.
var app = WebApplication.Create(args);
app.Urls.Add("http://*:3000");
app.MapGet("/", () => "Hello World");
app.Run();
var app = WebApplication.Create(args);
app.Urls.Add("http://+:3000");
app.MapGet("/", () => "Hello World");
app.Run();
var app = WebApplication.Create(args);
app.Urls.Add("http://0.0.0.0:3000");
app.MapGet("/", () => "Hello World");
app.Run();
In den vorherigen Beispielen kann ASPNETCORE_URLS
verwendet werden.
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
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.
Die folgenden Abschnitte zeigen, wie das benutzerdefinierte Zertifikat mithilfe der Datei appsettings.json
und über die Konfiguration angegeben wird.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Certificates": {
"Default": {
"Path": "cert.pem",
"KeyPath": "key.pem"
}
}
}
}
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();
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();
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.
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.
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.
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.
Dieser Abschnitt enthält Beispielcode unter Verwendung von WebApplicationBuilder.
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.
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 |
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.
Standardmäßig liest die WebApplicationBuilder die Konfiguration aus mehreren Quellen, darunter:
appSettings.json
und appSettings.{environment}.json
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();
var builder = WebApplication.CreateBuilder(args);
var message = builder.Configuration["HelloKey"] ?? "Hello";
var app = builder.Build();
app.MapGet("/", () => message);
app.Run();
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();
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();
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();
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();
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();
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();
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.
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();
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 |
In den folgenden Abschnitten werden Routing, Parameterbindung und Antworten behandelt.
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 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.
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();
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
string LocalFunction() => "This is local function";
app.MapGet("/", LocalFunction);
app.Run();
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";
}
}
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", HelloHandler.Hello);
app.Run();
class HelloHandler
{
public static string Hello()
{
return "Hello static method";
}
}
Endpunkte können Namen erhalten, um URLs für den jeweiligen Endpunkt zu generieren. Durch die Verwendung eines benannten Endpunkts entfällt das Hartcodieren von Pfaden in einer App:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/hello", () => "Hello named route")
.WithName("hi");
app.MapGet("/", (LinkGenerator linker) =>
$"The link to the hello route is {linker.GetPathByName("hi", values: null)}");
app.Run();
Der vorangehende Code zeigt The link to the hello endpoint is /hello
vom Endpunkt /
an.
HINWEIS: Für Endpunktnamen muss Groß-/Kleinschreibung beachtet werden.
Endpunktnamen:
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".
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 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.
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:
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>();
// ...
});
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 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();
In Routenhandlern deklarierte Parameter werden als erforderlich behandelt:
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.
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);
Es gibt zwei Möglichkeiten zum Anpassen der Parameterbindung:
TryParse
-Methode für den Typ hinzufügen.BindAsync
-Methode für einen Typ implementieren.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
umfasst die folgenden APIs:
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
Der folgende 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
}
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 | Text | 400 |
Falscher Inhaltstyp (nicht application/json ) |
Nicht relevant | Text | 415 |
Die Regeln zum Bestimmen einer Bindungsquelle anhand eines Parameters:
[FromRoute]
[FromQuery]
[FromHeader]
[FromBody]
[FromServices]
BindAsync
-Methode.TryParse
-Methode.
app.Map("/todo/{id}", (int id) => {});
geht id
von der Route aus.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 obige Code:
{
"id": 1,
"name": "Joe Smith"
}
Wenn Folgendes gepostet wird: {
"Id": 1,
"Name": "Joe Smith"
}
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:
Routenhandler unterstützen die folgenden Typen von Rückgabewerten:
IResult
-basiert: Dies schließt Task<IResult>
und ValueTask<IResult>
ein.string
: Dies schließt Task<string>
und ValueTask<string>
ein.T
(ein beliebiger weiterer Typ): Dies schließt Task<T>
und ValueTask<T>
ein.Rückgabewert | Verhalten | Content-Type |
---|---|---|
IResult |
Das Framework ruft IResult.ExecuteAsync auf. | Richtet sich nach der IResult -Implementierung |
string |
Das Framework schreibt die Zeichenfolge direkt in die Antwort. | text/plain |
T (beliebiger anderer Typ) |
Das Framework serialisiert die Antwort im JSON-Format. | application/json |
app.MapGet("/hello", () => "Hello World");
app.MapGet("/hello", () => new { Message = "Hello World" });
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);
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
app.MapGet("/405", () => Results.StatusCode(405));
app.MapGet("/text", () => Results.Text("This is some text"));
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();
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
app.MapGet("/download", () => Results.File("myfile.text"));
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 | N/V | 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 |
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();
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();
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();
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.
Feedback zu ASP.NET Core
ASP.NET Core ist ein Open Source-Projekt. Wählen Sie einen Link aus, um Feedback zu geben:
Ereignisse
Power BI DataViz Weltmeisterschaften
14. Feb., 16 Uhr - 31. März, 16 Uhr
Mit 4 Chancen, ein Konferenzpaket zu gewinnen und es zum LIVE Grand Finale in Las Vegas zu machen
Weitere InformationenTraining
Modul
Erstellen einer Web-API mit minimaler API, ASP.NET Core und .NET - Training
Erfahren Sie, wie Sie eine Web-API mit .NET erstellen. Außerdem erfahren Sie, wie Sie verschiedene Routen einrichten, um sowohl Lesen als auch Schreiben zu verarbeiten.