Krótkie informacje o minimalnych interfejsach API
Uwaga
Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.
Ostrzeżenie
Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz .NET i .NET Core Support Policy (Zasady obsługi platformy .NET Core). Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.
Ważne
Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.
Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.
Ten dokument:
- Zawiera skróconą dokumentację dla minimalnych interfejsów API.
- Jest przeznaczony dla doświadczonych deweloperów. Aby zapoznać się z wprowadzeniem, zobacz Samouczek: tworzenie minimalnego interfejsu API przy użyciu platformy ASP.NET Core.
Minimalne interfejsy API składają się z następujących elementów:
WebApplication
Następujący kod jest generowany przez szablon ASP.NET Core:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Powyższy kod można utworzyć za pomocą wiersza dotnet new web
polecenia lub wybrać pusty szablon sieci Web w programie Visual Studio.
Poniższy kod tworzy element WebApplication (app
) bez jawnego utworzenia elementu WebApplicationBuilder:
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run();
WebApplication.Create
Inicjuje nowe wystąpienie WebApplication klasy ze wstępnie skonfigurowanymi wartościami domyślnymi.
WebApplication
program automatycznie dodaje następujące oprogramowanie pośredniczące w Minimal API applications
zależności od określonych warunków:
UseDeveloperExceptionPage
element jest dodawany jako pierwszy, gdy parametr ma wartośćHostingEnvironment
"Development"
.UseRouting
Jest dodawany drugi, jeśli kod użytkownika nie został jeszcze wywołanyUseRouting
i jeśli istnieją skonfigurowane punkty końcowe, na przykładapp.MapGet
.UseEndpoints
Jest dodawany na końcu potoku oprogramowania pośredniczącego, jeśli są skonfigurowane jakiekolwiek punkty końcowe.UseAuthentication
jest dodawany natychmiast poUseRouting
tym, jak kod użytkownika nie został jeszcze wywołanyUseAuthentication
i czyIAuthenticationSchemeProvider
można go wykryć u dostawcy usług.IAuthenticationSchemeProvider
program jest domyślnie dodawany podczas korzystania z usługAddAuthentication
, a usługa jest wykrywana przy użyciu poleceniaIServiceProviderIsService
.UseAuthorization
Zostanie dodany dalej, jeśli kod użytkownika nie został jeszcze wywołanyUseAuthorization
i czyIAuthorizationHandlerProvider
można go wykryć u dostawcy usług.IAuthorizationHandlerProvider
program jest domyślnie dodawany podczas korzystania z usługAddAuthorization
, a usługa jest wykrywana przy użyciu poleceniaIServiceProviderIsService
.- Oprogramowanie pośredniczące skonfigurowane przez użytkownika i punkty końcowe są dodawane między elementami
UseRouting
iUseEndpoints
.
Poniższy kod jest w rzeczywistości tym, co tworzy automatyczne oprogramowanie pośredniczące dodawane do aplikacji:
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 => {});
W niektórych przypadkach domyślna konfiguracja oprogramowania pośredniczącego nie jest poprawna dla aplikacji i wymaga modyfikacji. Na przykład UseCors należy wywołać metodę przed UseAuthentication i UseAuthorization. Aplikacja musi wywołać metodę UseAuthentication
, a UseAuthorization
jeśli UseCors
jest wywoływana:
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
Jeśli oprogramowanie pośredniczące powinno być uruchamiane przed rozpoczęciem dopasowywania tras, UseRouting należy wywołać metodę , a oprogramowanie pośredniczące powinno zostać umieszczone przed wywołaniem metody UseRouting
. UseEndpoints nie jest wymagany w tym przypadku, ponieważ jest automatycznie dodawany zgodnie z wcześniejszym opisem:
app.Use((context, next) =>
{
return next(context);
});
app.UseRouting();
// other middleware and endpoints
Podczas dodawania oprogramowania pośredniczącego terminalu:
- Oprogramowanie pośredniczące musi zostać dodane po .
UseEndpoints
- Aplikacja musi wywołać metodę
UseRouting
iUseEndpoints
tak, aby oprogramowanie pośredniczące terminalu można było umieścić w odpowiedniej lokalizacji.
app.UseRouting();
app.MapGet("/", () => "hello world");
app.UseEndpoints(e => {});
app.Run(context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
});
Oprogramowanie pośredniczące terminala to oprogramowanie pośredniczące uruchamiane, jeśli żaden punkt końcowy nie obsługuje żądania.
Praca z portami
Po utworzeniu aplikacji internetowej za pomocą programu Visual Studio lub dotnet new
Properties/launchSettings.json
zostanie utworzony plik, który określa porty, na które odpowiada aplikacja. W poniższych przykładach ustawień portów uruchomienie aplikacji z programu Visual Studio zwraca okno dialogowe Unable to connect to web server 'AppName'
błędu . Program Visual Studio zwraca błąd, ponieważ oczekuje portu określonego w Properties/launchSettings.json
elemecie , ale aplikacja używa portu określonego przez app.Run("http://localhost:3000")
. Uruchom następujący port, zmieniając przykłady z wiersza polecenia.
W poniższych sekcjach ustawiono port, na który odpowiada aplikacja.
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run("http://localhost:3000");
W poprzednim kodzie aplikacja odpowiada na port 3000
.
Wiele portów
W poniższym kodzie aplikacja odpowiada na port 3000
i 4000
.
var app = WebApplication.Create(args);
app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");
app.MapGet("/", () => "Hello World");
app.Run();
Ustawianie portu z wiersza polecenia
Następujące polecenie powoduje, że aplikacja odpowiada na port 7777
:
dotnet run --urls="https://localhost:7777"
Kestrel Jeśli punkt końcowy jest również skonfigurowany w appsettings.json
pliku, appsettings.json
używany jest określony adres URL. Aby uzyskać więcej informacji, zobacz Kestrel Konfiguracja punktu końcowego
Odczytywanie portu ze środowiska
Poniższy kod odczytuje port ze środowiska:
var app = WebApplication.Create(args);
var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";
app.MapGet("/", () => "Hello World");
app.Run($"http://localhost:{port}");
Preferowanym sposobem ustawienia portu ze środowiska jest użycie ASPNETCORE_URLS
zmiennej środowiskowej, która jest pokazana w poniższej sekcji.
Ustawianie portów za pomocą zmiennej środowiskowej ASPNETCORE_URLS
Zmienna ASPNETCORE_URLS
środowiskowa jest dostępna do ustawienia portu:
ASPNETCORE_URLS=http://localhost:3000
ASPNETCORE_URLS
obsługuje wiele adresów URL:
ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000
Nasłuchiwanie we wszystkich interfejsach
W poniższych przykładach pokazano nasłuchiwanie we wszystkich interfejsach
http://*:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://*:3000");
app.MapGet("/", () => "Hello World");
app.Run();
http://+:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://+:3000");
app.MapGet("/", () => "Hello World");
app.Run();
http://0.0.0.0:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://0.0.0.0:3000");
app.MapGet("/", () => "Hello World");
app.Run();
Nasłuchiwanie we wszystkich interfejsach przy użyciu ASPNETCORE_URLS
Powyższe przykłady mogą być używane ASPNETCORE_URLS
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
Nasłuchiwanie we wszystkich interfejsach przy użyciu ASPNETCORE_HTTPS_PORTS
Powyższe przykłady mogą używać elementów ASPNETCORE_HTTPS_PORTS
i ASPNETCORE_HTTP_PORTS
.
ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000
Aby uzyskać więcej informacji, zobacz Konfigurowanie punktów końcowych dla serwera internetowego platformy ASP.NET Core Kestrel
Określanie protokołu HTTPS przy użyciu certyfikatu programistycznego
var app = WebApplication.Create(args);
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
Aby uzyskać więcej informacji na temat certyfikatu programistycznego, zobacz Trust the ASP.NET Core HTTPS development certificate on Windows and macOS (Ufaj certyfikatowi programistycznemu ASP.NET Core HTTPS w systemach Windows i macOS).
Określanie protokołu HTTPS przy użyciu certyfikatu niestandardowego
W poniższych sekcjach pokazano, jak określić certyfikat niestandardowy przy użyciu appsettings.json
pliku i za pośrednictwem konfiguracji.
Określanie certyfikatu niestandardowego za pomocą polecenia appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Certificates": {
"Default": {
"Path": "cert.pem",
"KeyPath": "key.pem"
}
}
}
}
Określanie certyfikatu niestandardowego za pomocą konfiguracji
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();
Korzystanie z interfejsów API certyfikatów
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();
Odczytywanie środowiska
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();
Aby uzyskać więcej informacji na temat korzystania ze środowiska, zobacz Use multiple environments in ASP.NET Core (Używanie wielu środowisk w środowisku ASP.NET Core)
Konfigurowanie
Poniższy kod odczytuje z systemu konfiguracji:
var app = WebApplication.Create(args);
var message = app.Configuration["HelloKey"] ?? "Config failed!";
app.MapGet("/", () => message);
app.Run();
Aby uzyskać więcej informacji, zobacz Configuration in ASP.NET Core (Konfiguracja w programie ASP.NET Core)
Rejestrowanie
Poniższy kod zapisuje komunikat podczas uruchamiania aplikacji logowania:
var app = WebApplication.Create(args);
app.Logger.LogInformation("The app started");
app.MapGet("/", () => "Hello World");
app.Run();
Aby uzyskać więcej informacji, zobacz Rejestrowanie na platformie .NET Core i ASP.NET Core
Uzyskiwanie dostępu do kontenera wstrzykiwania zależności (DI)
Poniższy kod pokazuje, jak pobrać usługi z kontenera DI podczas uruchamiania aplikacji:
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();
Poniższy kod pokazuje, jak uzyskać dostęp do kluczy z kontenera DI przy użyciu atrybutu [FromKeyedServices]
:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
var app = builder.Build();
app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) => smallCache.Get("date"));
app.Run();
public interface ICache
{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}
public class SmallCache : ICache
{
public object Get(string key) => $"Resolving {key} from small cache.";
}
Aby uzyskać więcej informacji na temat di, zobacz Wstrzykiwanie zależności w ASP.NET Core.
WebApplicationBuilder
Ta sekcja zawiera przykładowy kod przy użyciu polecenia WebApplicationBuilder.
Zmienianie katalogu głównego zawartości, nazwy aplikacji i środowiska
Poniższy kod ustawia katalog główny zawartości, nazwę aplikacji i środowisko:
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 inicjuje nowe wystąpienie klasy WebApplicationBuilder ze wstępnie skonfigurowanymi wartościami domyślnymi.
Aby uzyskać więcej informacji, zobacz omówienie podstaw platformy ASP.NET Core
Zmienianie katalogu głównego zawartości, nazwy aplikacji i środowiska przy użyciu zmiennych środowiskowych lub wiersza polecenia
W poniższej tabeli przedstawiono zmienną środowiskową i argument wiersza polecenia używany do zmiany katalogu głównego zawartości, nazwy aplikacji i środowiska:
funkcja | Zmienna środowiskowa | Argument wiersza polecenia |
---|---|---|
Nazwa aplikacji | ASPNETCORE_APPLICATIONNAME | --applicationName |
Nazwa środowiska | ASPNETCORE_ENVIRONMENT | --środowisko |
Katalog główny zawartości | ASPNETCORE_CONTENTROOT | --contentRoot |
Dodawanie dostawców konfiguracji
Poniższy przykład dodaje dostawcę konfiguracji INI:
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddIniFile("appsettings.ini");
var app = builder.Build();
Aby uzyskać szczegółowe informacje, zobacz Dostawcy konfiguracji plików w konfiguracji w programie ASP.NET Core.
Konfiguracja odczytu
Domyślnie WebApplicationBuilder konfiguracja odczytu z wielu źródeł, w tym:
appSettings.json
iappSettings.{environment}.json
- Zmienne środowiskowe
- Wiersz polecenia
Aby uzyskać pełną listę źródeł konfiguracji, zobacz Konfiguracja domyślna w konfiguracji w programie ASP.NET Core.
Poniższy kod odczytuje HelloKey
z konfiguracji i wyświetla wartość w punkcie /
końcowym. Jeśli wartość konfiguracji ma wartość null, "Hello" zostanie przypisana do elementu message
:
var builder = WebApplication.CreateBuilder(args);
var message = builder.Configuration["HelloKey"] ?? "Hello";
var app = builder.Build();
app.MapGet("/", () => message);
app.Run();
Odczytywanie środowiska
var builder = WebApplication.CreateBuilder(args);
if (builder.Environment.IsDevelopment())
{
Console.WriteLine($"Running in development.");
}
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Dodawanie dostawców rejestrowania
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();
Dodawanie usług
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();
Dostosowywanie elementu IHostBuilder
Dostęp do istniejących metod rozszerzeń IHostBuilder można uzyskać przy użyciu właściwości Host:
var builder = WebApplication.CreateBuilder(args);
// Wait 30 seconds for graceful shutdown.
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Dostosowywanie obiektu IWebHostBuilder
Dostęp do metod rozszerzeń IWebHostBuilder można uzyskać przy użyciu właściwości WebApplicationBuilder.WebHost .
var builder = WebApplication.CreateBuilder(args);
// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();
var app = builder.Build();
app.MapGet("/", () => "Hello HTTP.sys");
app.Run();
Zmienianie katalogu głównego sieci Web
Domyślnie katalog główny sieci Web jest powiązany z katalogem głównym zawartości w folderze wwwroot
. Katalog główny sieci Web to miejsce, w którym oprogramowanie pośredniczące plików statycznych szuka plików statycznych. Katalog główny sieci Web można zmienić za pomocą WebHostOptions
polecenia , wiersza polecenia lub UseWebRoot metody :
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
// Look for static files in webroot
WebRootPath = "webroot"
});
var app = builder.Build();
app.Run();
Niestandardowy kontener wstrzykiwania zależności (DI)
W poniższym przykładzie użyto funkcji Autofac:
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));
var app = builder.Build();
Dodawanie oprogramowania pośredniczącego
W programie WebApplication
można skonfigurować dowolne istniejące oprogramowanie pośredniczące ASP.NET Core:
var app = WebApplication.Create(args);
// Setup the file server to serve static files.
app.UseFileServer();
app.MapGet("/", () => "Hello World!");
app.Run();
Aby uzyskać więcej informacji, zobacz ASP.NET Core Middleware
Strona wyjątku dla deweloperów
WebApplication.CreateBuilder Inicjuje nowe wystąpienie WebApplicationBuilder klasy ze wstępnie skonfigurowanymi wartościami domyślnymi. Strona wyjątku dewelopera jest włączona w wstępnie skonfigurowanych wartościach domyślnych. Po uruchomieniu następującego kodu w środowisku deweloperskim przejście do /
strony renderuje przyjazną stronę, która pokazuje wyjątek.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>
{
throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});
app.Run();
Oprogramowanie pośredniczące platformy ASP.NET Core
W poniższej tabeli wymieniono niektóre oprogramowanie pośredniczące często używane z minimalnymi interfejsami API.
Oprogramowanie pośredniczące | opis | interfejs API |
---|---|---|
Authentication | Zapewnia obsługę uwierzytelniania. | UseAuthentication |
Autoryzacja | Zapewnia obsługę autoryzacji. | UseAuthorization |
CORS | Konfiguruje współużytkowanie zasobów między źródłami. | UseCors |
Procedura obsługi wyjątków | Globalnie obsługuje wyjątki zgłaszane przez potok oprogramowania pośredniczącego. | UseExceptionHandler |
Przekazane nagłówki | Przekazuje nagłówki przesłane przez serwer proxy do bieżącego żądania. | UseForwardedHeaders |
Przekierowywanie HTTPS | Przekierowuje wszystkie żądania HTTP do protokołu HTTPS. | UseHttpsRedirection |
HTTP Strict Transport Security (HSTS) | Oprogramowanie pośredniczące rozszerzenia zabezpieczeń, które dodaje specjalny nagłówek odpowiedzi. | UseHsts |
Rejestrowanie żądań | Zapewnia obsługę rejestrowania żądań HTTP i odpowiedzi. | UseHttpLogging |
Limity czasu żądania | Zapewnia obsługę konfigurowania limitów czasu żądań, wartości domyślnych globalnych i poszczególnych punktów końcowych. | UseRequestTimeouts |
Rejestrowanie żądań W3C | Zapewnia obsługę rejestrowania żądań HTTP i odpowiedzi w formacie W3C. | UseW3CLogging |
Buforowanie odpowiedzi | Zapewnia obsługę buforowania odpowiedzi. | UseResponseCaching |
Kompresja odpowiedzi | Zapewnia obsługę kompresowania odpowiedzi. | UseResponseCompression |
Sesja | Zapewnia obsługę zarządzania sesjami użytkowników. | UseSession |
Pliki statyczne | Zapewnia obsługę plików statycznych i przeglądania katalogów. | UseStaticFiles, UseFileServer |
Obiekty WebSocket | Włącza protokoły WebSocket. | UseWebSockets |
W poniższych sekcjach omówiono obsługę żądań: routing, powiązanie parametrów i odpowiedzi.
Routing
Skonfigurowana WebApplication
obsługuje Map{Verb}
metodę {Verb}
MapMethods HTTP typu camel-cased, npGet
. , Post
Put
lub Delete
:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "This is a GET");
app.MapPost("/", () => "This is a POST");
app.MapPut("/", () => "This is a PUT");
app.MapDelete("/", () => "This is a DELETE");
app.MapMethods("/options-or-head", new[] { "OPTIONS", "HEAD" },
() => "This is an options or head request ");
app.Run();
Argumenty Delegate przekazywane do tych metod są nazywane "procedurami obsługi tras".
Programy obsługi tras
Programy obsługi tras to metody, które są wykonywane, gdy trasa jest zgodna. Programy obsługi tras mogą być wyrażeniem lambda, funkcją lokalną, metodą wystąpienia lub metodą statyczną. Programy obsługi tras mogą być synchroniczne lub asynchroniczne.
Wyrażenie lambda
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/inline", () => "This is an inline lambda");
var handler = () => "This is a lambda variable";
app.MapGet("/", handler);
app.Run();
Funkcja lokalna
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
string LocalFunction() => "This is local function";
app.MapGet("/", LocalFunction);
app.Run();
Metoda wystąpienia
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";
}
}
Metoda statyczna
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";
}
}
Punkt końcowy zdefiniowany poza Program.cs
Minimalne interfejsy API nie muszą znajdować się w lokalizacji Program.cs
.
Program.cs
using MinAPISeparateFile;
var builder = WebApplication.CreateSlimBuilder(args);
var app = builder.Build();
TodoEndpoints.Map(app);
app.Run();
TodoEndpoints.cs
namespace MinAPISeparateFile;
public static class TodoEndpoints
{
public static void Map(WebApplication app)
{
app.MapGet("/", async context =>
{
// Get all todo items
await context.Response.WriteAsJsonAsync(new { Message = "All todo items" });
});
app.MapGet("/{id}", async context =>
{
// Get one todo item
await context.Response.WriteAsJsonAsync(new { Message = "One todo item" });
});
}
}
Zobacz również Grupy tras w dalszej części tego artykułu.
Nazwane punkty końcowe i generowanie linków
Punkty końcowe mogą mieć nazwy w celu wygenerowania adresów URL do punktu końcowego. Użycie nazwanego punktu końcowego pozwala uniknąć konieczności stosowania twardych ścieżek kodu w aplikacji:
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();
Powyższy kod jest wyświetlany The link to the hello route is /hello
z punktu końcowego /
.
UWAGA: W nazwach punktów końcowych jest rozróżniana wielkość liter.
Nazwy punktów końcowych:
- Musi ona być unikatowa w skali globalnej.
- Są używane jako identyfikator operacji interfejsu OpenAPI, gdy jest włączona obsługa interfejsu OpenAPI. Aby uzyskać więcej informacji, zobacz OpenAPI.
Parametry trasy
Parametry trasy można przechwycić w ramach definicji wzorca trasy:
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();
Powyższy kod zwraca The user id is 3 and book id is 7
z identyfikatora URI /users/3/books/7
.
Procedura obsługi tras może zadeklarować parametry do przechwycenia. Po wysłaniu żądania do trasy z zadeklarowanymi parametrami do przechwycenia parametry są analizowane i przekazywane do programu obsługi. Ułatwia to przechwytywanie wartości w bezpieczny sposób typu. W poprzednim kodzie userId
i bookId
mają wartość int
.
W poprzednim kodzie, jeśli nie można przekonwertować żadnej wartości trasy na int
wartość , zgłaszany jest wyjątek. Żądanie /users/hello/books/3
GET zgłasza następujący wyjątek:
BadHttpRequestException: Failed to bind parameter "int userId" from "hello".
Symbol wieloznaczny i przechwyć wszystkie trasy
Następujące przechwycenie wszystkich tras zwracanych Routing to hello
z punktu końcowego "/posts/hello":
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");
app.Run();
Ograniczenia trasy
Ograniczenia trasy ograniczają zgodne zachowanie trasy.
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();
W poniższej tabeli przedstawiono powyższe szablony tras i ich zachowanie:
Szablon trasy | Przykładowy pasujący identyfikator URI |
---|---|
/todos/{id:int} |
/todos/1 |
/todos/{text} |
/todos/something |
/posts/{slug:regex(^[a-z0-9_-]+$)} |
/posts/mypost |
Aby uzyskać więcej informacji, zobacz Route constraint reference in Routing in ASP.NET Core (Dokumentacja ograniczeń tras w usłudze Routing w usłudze ASP.NET Core).
Grupy tras
Metoda MapGroup rozszerzenia ułatwia organizowanie grup punktów końcowych za pomocą wspólnego prefiksu. Zmniejsza powtarzalny kod i umożliwia dostosowywanie całych grup punktów końcowych za pomocą jednego wywołania metod, takich jak RequireAuthorization i WithMetadata które dodają metadane punktu końcowego.
Na przykład poniższy kod tworzy dwie podobne grupy punktów końcowych:
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;
}
W tym scenariuszu możesz użyć względnego adresu nagłówka Location
w 201 Created
wyniku:
public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
await database.AddAsync(todo);
await database.SaveChangesAsync();
return TypedResults.Created($"{todo.Id}", todo);
}
Pierwsza grupa punktów końcowych będzie pasowała tylko do żądań z prefiksem /public/todos
i jest dostępna bez żadnego uwierzytelniania. Druga grupa punktów końcowych będzie pasowała tylko do żądań poprzedzonych prefiksem /private/todos
i wymaga uwierzytelniania.
QueryPrivateTodos
Fabryka filtrów punktów końcowych to funkcja lokalna, która modyfikuje parametry programu obsługi TodoDb
tras, aby umożliwić dostęp do prywatnych danych zadań do wykonania i przechowywanie ich.
Grupy tras obsługują również grupy zagnieżdżone i złożone wzorce prefiksów z parametrami i ograniczeniami trasy. W poniższym przykładzie program obsługi tras zamapowany na grupę user
może przechwytywać {org}
parametry trasy i {group}
zdefiniowane w prefiksach grup zewnętrznych.
Prefiks może być również pusty. Może to być przydatne w przypadku dodawania metadanych lub filtrów punktu końcowego do grupy punktów końcowych bez zmiany wzorca trasy.
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
Dodanie filtrów lub metadanych do grupy działa tak samo jak dodawanie ich indywidualnie do każdego punktu końcowego przed dodaniem dodatkowych filtrów lub metadanych, które mogły zostać dodane do grupy wewnętrznej lub określonego punktu końcowego.
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);
});
W powyższym przykładzie zewnętrzny filtr będzie rejestrować żądanie przychodzące przed filtrem wewnętrznym, mimo że został dodany w sekundzie. Ponieważ filtry zostały zastosowane do różnych grup, kolejność, którą zostały dodane względem siebie, nie ma znaczenia. Filtry kolejności są dodawane niezależnie od tego, czy są stosowane do tej samej grupy lub określonego punktu końcowego.
Żądanie, aby zarejestrować /outer/inner/
następujące elementy:
/outer group filter
/inner group filter
MapGet filter
Powiązanie parametrów
Powiązanie parametrów to proces konwertowania danych żądania na silnie typizowane parametry, które są wyrażane przez programy obsługi tras. Źródło powiązania określa, skąd są powiązane parametry. Źródła powiązań mogą być jawne lub wnioskowane na podstawie metody HTTP i typu parametru.
Obsługiwane źródła powiązań:
- Wartości tras
- Ciąg zapytania
- Nagłówek
- Treść (jako kod JSON)
- Wartości formularza
- Usługi udostępniane przez wstrzykiwanie zależności
- Niestandardowy
GET
Poniższa procedura obsługi tras używa niektórych z tych źródeł powiązań parametrów:
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 { }
W poniższej tabeli przedstawiono relację między parametrami używanymi w poprzednim przykładzie i skojarzonymi źródłami powiązań.
Parametr | Źródło powiązania |
---|---|
id |
wartość trasy |
page |
ciąg zapytania |
customHeader |
nagłówek |
service |
Udostępniane przez wstrzykiwanie zależności |
Metody GET
HTTP , HEAD
, OPTIONS
i DELETE
nie są niejawnie powiązane z treścią. Aby powiązać z treścią (jako kod JSON) dla tych metod HTTP, powiąż jawnie z elementem [FromBody]
lub odczyt z pliku HttpRequest.
W poniższym przykładzie procedura obsługi tras POST używa powiązania źródła treści (jako kodu JSON) dla parametru person
:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/", (Person person) => { });
record Person(string Name, int Age);
Parametry w poprzednich przykładach są automatycznie powiązane z danymi żądania. Aby zademonstrować wygodę zapewnianą przez powiązanie parametrów, następujące programy obsługi tras pokazują, jak odczytywać dane żądania bezpośrednio z żądania:
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>();
// ...
});
Jawne powiązanie parametrów
Atrybuty mogą służyć do jawnego deklarowania, gdzie parametry są powiązane.
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);
Parametr | Źródło powiązania |
---|---|
id |
wartość trasy o nazwie id |
page |
ciąg zapytania o nazwie "p" |
service |
Udostępniane przez wstrzykiwanie zależności |
contentType |
nagłówek o nazwie "Content-Type" |
Jawne powiązanie z wartości formularza
Atrybut [FromForm]
wiąże wartości formularza:
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.
Alternatywą jest użycie atrybutu [AsParameters]
z typem niestandardowym, który ma właściwości oznaczone jako [FromForm]
. Na przykład następujący kod wiąże się z wartościami formularza z właściwościami struktury rekordu NewTodoRequest
:
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);
Aby uzyskać więcej informacji, zobacz sekcję dotyczącą parametrów asparameters w dalszej części tego artykułu.
Kompletny przykładowy kod znajduje się w repozytorium AspNetCore.Docs.Samples .
Bezpieczne powiązanie z klasy IFormFile i IFormFileCollection
Tworzenie złożonego powiązania formularza jest obsługiwane przy użyciu polecenia IFormFile i IFormFileCollection przy użyciu polecenia [FromForm]
:
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();
Parametry powiązane z żądaniem zawierają [FromForm]
token antyforgery. Token ochrony przed fałszerzami jest weryfikowany po przetworzeniu żądania. Aby uzyskać więcej informacji, zobacz Antiforgery with Minimal APIs (Antiforgery with Minimal APIs).
Aby uzyskać więcej informacji, zobacz Powiązanie formularza w minimalnych interfejsach API.
Kompletny przykładowy kod znajduje się w repozytorium AspNetCore.Docs.Samples .
Powiązanie parametrów z wstrzyknięciem zależności
Powiązanie parametrów dla minimalnych interfejsów API wiąże parametry za pośrednictwem wstrzykiwania zależności, gdy typ jest skonfigurowany jako usługa. Nie jest konieczne jawne zastosowanie atrybutu [FromServices]
do parametru. W poniższym kodzie obie akcje zwracają czas:
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();
Parametry opcjonalne
Parametry zadeklarowane w programach obsługi tras są traktowane zgodnie z wymaganiami:
- Jeśli żądanie pasuje do trasy, procedura obsługi tras jest uruchamiana tylko wtedy, gdy wszystkie wymagane parametry są podane w żądaniu.
- Niepowodzenie podania wszystkich wymaganych parametrów powoduje wystąpienie błędu.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");
app.Run();
Identyfikator URI | result |
---|---|
/products?pageNumber=3 |
3 zwrócone |
/products |
BadHttpRequestException : Wymagany parametr "int pageNumber" nie został podany z ciągu zapytania. |
/products/1 |
Błąd HTTP 404, brak pasującej trasy |
Aby ustawić pageNumber
wartość opcjonalną, zdefiniuj typ jako opcjonalny lub podaj wartość domyślną:
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();
Identyfikator URI | result |
---|---|
/products?pageNumber=3 |
3 zwrócone |
/products |
1 zwrócone |
/products2 |
1 zwrócone |
Poprzednia wartość dopuszczająca wartość null i domyślna ma zastosowanie do wszystkich źródeł:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/products", (Product? product) => { });
app.Run();
Powyższy kod wywołuje metodę z produktem o wartości null, jeśli nie zostanie wysłana żadna treść żądania.
UWAGA: Jeśli podano nieprawidłowe dane i parametr ma wartość null, procedura obsługi tras nie jest uruchamiana.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");
app.Run();
Identyfikator URI | result |
---|---|
/products?pageNumber=3 |
3 Zwracane |
/products |
1 Zwracane |
/products?pageNumber=two |
BadHttpRequestException : Nie można powiązać parametru "Nullable<int> pageNumber" z "dwóch". |
/products/two |
Błąd HTTP 404, brak pasującej trasy |
Aby uzyskać więcej informacji, zobacz sekcję Błędy powiązań.
Typy specjalne
Następujące typy są powiązane bez jawnych atrybutów:
HttpContext: kontekst zawierający wszystkie informacje o bieżącym żądaniu HTTP lub odpowiedzi:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
HttpRequest i HttpResponse: Żądanie HTTP i odpowiedź HTTP:
app.MapGet("/", (HttpRequest request, HttpResponse response) => response.WriteAsync($"Hello World {request.Query["name"]}"));
CancellationToken: token anulowania skojarzony z bieżącym żądaniem HTTP:
app.MapGet("/", async (CancellationToken cancellationToken) => await MakeLongRunningRequestAsync(cancellationToken));
ClaimsPrincipal: użytkownik skojarzony z żądaniem powiązany z elementem HttpContext.User:
app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
Powiąż treść żądania jako element Stream
lub PipeReader
Treść żądania może wiązać się ze scenariuszami Stream
lub PipeReader
, w których użytkownik musi przetwarzać dane i:
- Zapisz dane w magazynie obiektów blob lub zapisz dane w kolejce do dostawcy kolejki.
- Przetwarzanie przechowywanych danych za pomocą procesu roboczego lub funkcji w chmurze.
Na przykład dane mogą być w kolejce do usługi Azure Queue Storage lub przechowywane w usłudze Azure Blob Storage.
Poniższy kod implementuje kolejkę w tle:
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;
}
Poniższy kod wiąże treść żądania z elementem 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);
});
Poniższy kod przedstawia kompletny Program.cs
plik:
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();
- Podczas odczytywania danych
Stream
obiekt jest tym samym obiektem coHttpRequest.Body
. - Treść żądania nie jest domyślnie buforowana. Po odczytaniu treści nie można jej przewijać. Strumień nie może być odczytywany wiele razy.
- Elementy
Stream
iPipeReader
nie mogą być używane poza minimalną procedurą obsługi akcji, ponieważ bazowe zostaną usunięte lub ponownie użyte.
Przekazywanie plików przy użyciu elementu IFormFile i IFormFileCollection
Poniższy kod używa instrukcji IFormFile i IFormFileCollection do przekazywania pliku:
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();
Uwierzytelnione żądania przekazywania plików są obsługiwane przy użyciu nagłówka autoryzacji, certyfikatu klienta lub nagłówka cookie .
Wiązanie z formularzami za pomocą klasy IFormCollection, IFormFile i IFormFileCollection
Powiązanie z parametrów opartych na formularzach przy użyciu parametrów IFormCollection, IFormFilei IFormFileCollection jest obsługiwane. Metadane interfejsu OpenAPI są wnioskowane, aby parametry formularza obsługiwały integrację z interfejsem użytkownika struktury Swagger.
Poniższy kod przekazuje pliki przy użyciu powiązania wnioskowanego z IFormFile
typu:
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();
Ostrzeżenie: Podczas implementowania formularzy aplikacja musi zapobiegać atakom fałszerzowania żądań między witrynami (XSRF/CSRF). W poprzednim kodzie IAntiforgery usługa jest używana do zapobiegania atakom XSRF przez generowanie i walidację tokenu antyforgery:
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();
Aby uzyskać więcej informacji na temat ataków XSRF, zobacz Antiforgery with Minimal APIs (Ochrona przed atakami za pomocą minimalnych interfejsów API)
Aby uzyskać więcej informacji, zobacz Powiązanie formularza w minimalnych interfejsach API;
Wiązanie z kolekcjami i typami złożonymi z formularzy
Powiązanie jest obsługiwane w następujących celach:
Poniższy kod pokazuje:
- Minimalny punkt końcowy, który wiąże dane wejściowe z wieloczęściowym formularzem wejściowym ze złożonym obiektem.
- Jak używać usług antyforgery do obsługi generowania i walidacji tokenów antyforgeryjnych.
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));
}
Powyższy kod:
- Parametr docelowy musi być oznaczony adnotacją z atrybutem
[FromForm]
, aby uściślić parametry, które powinny być odczytywane z treści JSON. - Powiązanie z typów złożonych lub kolekcji nie jest obsługiwane w przypadku minimalnych interfejsów API skompilowanych za pomocą generatora delegatów żądań.
- Znacznik pokazuje dodatkowe ukryte dane wejściowe o nazwie
isCompleted
i wartościfalse
.isCompleted
Jeśli pole wyboru jest zaznaczone podczas przesyłania formularza, obie wartościtrue
ifalse
są przesyłane jako wartości. Jeśli pole wyboru jest niezaznaczone, zostanie przesłana tylko ukryta wartośćfalse
wejściowa. Proces powiązania modelu ASP.NET Core odczytuje tylko pierwszą wartość podczas tworzenia powiązania z wartościąbool
, co powodujetrue
zaznaczenie pól wyboru zaznaczonego ifalse
niezaznaczonego pola wyboru.
Przykład danych formularza przesłanych do poprzedniego punktu końcowego wygląda następująco:
__RequestVerificationToken: CfDJ8Bveip67DklJm5vI2PF2VOUZ594RC8kcGWpTnVV17zCLZi1yrs-CSz426ZRRrQnEJ0gybB0AD7hTU-0EGJXDU-OaJaktgAtWLIaaEWMOWCkoxYYm-9U9eLV7INSUrQ6yBHqdMEE_aJpD4AI72gYiCqc
name: Walk the dog
dueDate: 2024-04-06
isCompleted: true
isCompleted: false
Wiązanie tablic i wartości ciągów z nagłówków i ciągów zapytania
Poniższy kod demonstruje powiązania ciągów zapytania z tablicą typów pierwotnych, tablic ciągów i 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]}");
Powiązanie ciągów zapytania lub wartości nagłówków z tablicą typów złożonych jest obsługiwane, gdy typ został TryParse
zaimplementowany. Poniższy kod wiąże się z tablicą ciągów i zwraca wszystkie elementy z określonymi tagami:
// 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();
});
Poniższy kod przedstawia model i wymaganą TryParse
implementację:
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;
}
}
Poniższy kod wiąże się z tablicą int
:
// GET /todoitems/query-string-ids?ids=1&ids=3
app.MapGet("/todoitems/query-string-ids", async (int[] ids, TodoDb db) =>
{
return await db.Todos
.Where(t => ids.Contains(t.Id))
.ToListAsync();
});
Aby przetestować poprzedni kod, dodaj następujący punkt końcowy, aby wypełnić bazę danych elementami Todo
:
// POST /todoitems/batch
app.MapPost("/todoitems/batch", async (Todo[] todos, TodoDb db) =>
{
await db.Todos.AddRangeAsync(todos);
await db.SaveChangesAsync();
return Results.Ok(todos);
});
Użyj narzędzia, takiego jak HttpRepl
, aby przekazać następujące dane do poprzedniego punktu końcowego:
[
{
"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"
}
}
]
Poniższy kod wiąże się z kluczem X-Todo-Id
nagłówka i zwraca Todo
elementy z pasującymi Id
wartościami:
// 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();
});
Uwaga
Podczas tworzenia powiązania string[]
elementu z ciągu zapytania brak pasującej wartości ciągu zapytania spowoduje, że zamiast wartości null zostanie pusta tablica.
Powiązanie parametrów dla list argumentów za pomocą parametrów [AsParameters]
AsParametersAttribute Umożliwia proste powiązanie parametrów z typami, a nie złożonymi lub cyklicznych powiązań modelu.
Spójrzmy na poniższy kod:
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.
Rozważ następujący GET
punkt końcowy:
app.MapGet("/todoitems/{id}",
async (int Id, TodoDb Db) =>
await Db.Todos.FindAsync(Id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
struct
Następujące elementy mogą służyć do zastępowania poprzednich wyróżnionych parametrów:
struct TodoItemRequest
{
public int Id { get; set; }
public TodoDb Db { get; set; }
}
Refaktoryzowany GET
punkt końcowy używa powyższego struct
atrybutu AsParameters :
app.MapGet("/ap/todoitems/{id}",
async ([AsParameters] TodoItemRequest request) =>
await request.Db.Todos.FindAsync(request.Id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
Poniższy kod przedstawia dodatkowe punkty końcowe w aplikacji:
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();
});
Następujące klasy służą do refaktoryzacji list parametrów:
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!;
}
Poniższy kod przedstawia refaktoryzowane punkty końcowe przy użyciu poleceń AsParameters
oraz poprzednie struct
klasy i :
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();
});
Zastąp powyższe parametry następującymi record
typami:
record TodoItemRequest(int Id, TodoDb Db);
record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);
Użycie elementu z AsParameters
funkcją struct
może być bardziej wydajne niż użycie record
typu.
Kompletny przykładowy kod w repozytorium AspNetCore.Docs.Samples .
Wiązanie niestandardowe
Istnieją dwa sposoby dostosowywania powiązania parametrów:
- W przypadku źródeł tras, zapytań i powiązań nagłówków powiąż typy niestandardowe, dodając metodę statyczną
TryParse
dla typu. - Kontrolowanie procesu wiązania przez zaimplementowanie
BindAsync
metody dla typu.
TryParse
TryParse
ma dwa interfejsy API:
public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);
Poniższy kod jest wyświetlany Point: 12.3, 10.1
za pomocą identyfikatora URI /map?Point=12.3,10.1
:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// GET /map?Point=12.3,10.1
app.MapGet("/map", (Point point) => $"Point: {point.X}, {point.Y}");
app.Run();
public class Point
{
public double X { get; set; }
public double Y { get; set; }
public static bool TryParse(string? value, IFormatProvider? provider,
out Point? point)
{
// Format is "(12.3,10.1)"
var trimmedValue = value?.TrimStart('(').TrimEnd(')');
var segments = trimmedValue?.Split(',',
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (segments?.Length == 2
&& double.TryParse(segments[0], out var x)
&& double.TryParse(segments[1], out var y))
{
point = new Point { X = x, Y = y };
return true;
}
point = null;
return false;
}
}
BindAsync
BindAsync
ma następujące interfejsy API:
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
Poniższy kod jest wyświetlany SortBy:xyz, SortDirection:Desc, CurrentPage:99
za pomocą identyfikatora URI /products?SortBy=xyz&SortDir=Desc&Page=99
:
using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// GET /products?SortBy=xyz&SortDir=Desc&Page=99
app.MapGet("/products", (PagingData pageData) => $"SortBy:{pageData.SortBy}, " +
$"SortDirection:{pageData.SortDirection}, CurrentPage:{pageData.CurrentPage}");
app.Run();
public class PagingData
{
public string? SortBy { get; init; }
public SortDirection SortDirection { get; init; }
public int CurrentPage { get; init; } = 1;
public static ValueTask<PagingData?> BindAsync(HttpContext context,
ParameterInfo parameter)
{
const string sortByKey = "sortBy";
const string sortDirectionKey = "sortDir";
const string currentPageKey = "page";
Enum.TryParse<SortDirection>(context.Request.Query[sortDirectionKey],
ignoreCase: true, out var sortDirection);
int.TryParse(context.Request.Query[currentPageKey], out var page);
page = page == 0 ? 1 : page;
var result = new PagingData
{
SortBy = context.Request.Query[sortByKey],
SortDirection = sortDirection,
CurrentPage = page
};
return ValueTask.FromResult<PagingData?>(result);
}
}
public enum SortDirection
{
Default,
Asc,
Desc
}
Błędy powiązań
Gdy powiązanie zakończy się niepowodzeniem, platforma rejestruje komunikat debugowania i zwraca różne kody stanu do klienta w zależności od trybu awarii.
Tryb awarii | Typ parametru dopuszczalnego do wartości null | Źródło powiązania | Kod stanu |
---|---|---|---|
{ParameterType}.TryParse Zwraca false |
tak | route/query/header | 400 |
{ParameterType}.BindAsync Zwraca null |
tak | niestandardowe | 400 |
{ParameterType}.BindAsync Zgłasza |
nie ma znaczenia | niestandardowe | 500 |
Nie można wykonać deserializacji treści JSON | nie ma znaczenia | treść | 400 |
Nieprawidłowy typ zawartości (nie application/json ) |
nie ma znaczenia | treść | 415 |
Pierwszeństwo powiązania
Reguły określania źródła powiązania z parametru:
- Jawny atrybut zdefiniowany w parametrze (atrybuty From*) w następującej kolejności:
- Wartości tras:
[FromRoute]
- Ciąg zapytania:
[FromQuery]
- Nagłówek:
[FromHeader]
- Ciało:
[FromBody]
- Formularz:
[FromForm]
- Usługa:
[FromServices]
- Wartości parametrów:
[AsParameters]
- Wartości tras:
- Typy specjalne
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
)
- Typ parametru ma prawidłową metodę statyczną
BindAsync
. - Typ parametru jest ciągiem lub ma prawidłową metodę statyczną
TryParse
.- Jeśli nazwa parametru istnieje na przykład w szablonie trasy,
app.Map("/todo/{id}", (int id) => {});
jest ona powiązana z trasą. - Powiązana z ciągu zapytania.
- Jeśli nazwa parametru istnieje na przykład w szablonie trasy,
- Jeśli typ parametru jest usługą dostarczaną przez iniekcję zależności, używa tej usługi jako źródła.
- Parametr pochodzi z treści.
Konfigurowanie opcji deserializacji JSON dla powiązania treści
Źródło powiązania treści używa System.Text.Json do deserializacji. Nie można zmienić tej wartości domyślnej, ale można skonfigurować opcje serializacji i deserializacji JSON.
Globalne konfigurowanie opcji deserializacji JSON
Opcje stosowane globalnie dla aplikacji można skonfigurować, wywołując ConfigureHttpJsonOptionsmetodę . Poniższy przykład zawiera pola publiczne i formaty danych wyjściowych JSON.
var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureHttpJsonOptions(options => {
options.SerializerOptions.WriteIndented = true;
options.SerializerOptions.IncludeFields = true;
});
var app = builder.Build();
app.MapPost("/", (Todo todo) => {
if (todo is not null) {
todo.Name = todo.NameField;
}
return todo;
});
app.Run();
class Todo {
public string? Name { get; set; }
public string? NameField;
public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "nameField":"Walk dog",
// "isComplete":false
// }
Ponieważ przykładowy kod konfiguruje zarówno serializacji, jak i deserializacji, może odczytywać NameField
i uwzględniać NameField
dane wyjściowe w formacie JSON.
Konfigurowanie opcji deserializacji JSON dla punktu końcowego
ReadFromJsonAsync ma przeciążenia, które akceptują JsonSerializerOptions obiekt. Poniższy przykład zawiera pola publiczne i formaty danych wyjściowych JSON.
using System.Text.Json;
var app = WebApplication.Create();
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) {
IncludeFields = true,
WriteIndented = true
};
app.MapPost("/", async (HttpContext context) => {
if (context.Request.HasJsonContentType()) {
var todo = await context.Request.ReadFromJsonAsync<Todo>(options);
if (todo is not null) {
todo.Name = todo.NameField;
}
return Results.Ok(todo);
}
else {
return Results.BadRequest();
}
});
app.Run();
class Todo
{
public string? Name { get; set; }
public string? NameField;
public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "isComplete":false
// }
Ponieważ powyższy kod stosuje dostosowane opcje tylko do deserializacji, dane wyjściowe JSON wykluczają NameField
wartość .
Odczytywanie treści żądania
Odczytywanie treści żądania bezpośrednio przy użyciu parametru HttpContext lub 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();
Powyższy kod ma następujące działanie:
- Uzyskuje dostęp do treści żądania przy użyciu polecenia HttpRequest.BodyReader.
- Kopiuje treść żądania do pliku lokalnego.
Odpowiedzi
Programy obsługi tras obsługują następujące typy zwracanych wartości:
IResult
oparte — obejmujeTask<IResult>
to iValueTask<IResult>
string
- Obejmuje toTask<string>
iValueTask<string>
T
(Dowolny inny typ) — obejmujeTask<T>
to iValueTask<T>
Wartość zwracana | Zachowanie | Typ zawartości |
---|---|---|
IResult |
Struktura wywołuje metodę IResult.ExecuteAsync | Decyzja o wdrożeniu IResult |
string |
Struktura zapisuje ciąg bezpośrednio w odpowiedzi | text/plain |
T (Dowolny inny typ) |
Struktura JSON serializuje odpowiedź | application/json |
Aby uzyskać bardziej szczegółowy przewodnik po zwracaniu wartości procedury obsługi tras, zobacz Tworzenie odpowiedzi w aplikacjach interfejsu API w minimalnej liczbie
Przykładowe wartości zwracane
ciąg zwracane wartości
app.MapGet("/hello", () => "Hello World");
Wartości zwracane w formacie JSON
app.MapGet("/hello", () => new { Message = "Hello World" });
Zwracane wartości TypedResults
Poniższy kod zwraca element TypedResults:
app.MapGet("/hello", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
Zwracanie jest preferowane TypedResults
do zwracania Resultswartości . Aby uzyskać więcej informacji, zobacz TypedResults vs Results (TypdResults a wyniki).
Zwracane wartości IResult
app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));
W poniższym przykładzie użyto wbudowanych typów wyników, aby dostosować odpowiedź:
app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound())
.Produces<Todo>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);
JSON
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
Niestandardowy kod stanu
app.MapGet("/405", () => Results.StatusCode(405));
Text
app.MapGet("/text", () => Results.Text("This is some text"));
Stream
var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () =>
{
var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
// Proxy the response as JSON
return Results.Stream(stream, "application/json");
});
Aby uzyskać więcej przykładów, zobacz Tworzenie odpowiedzi w minimalnych aplikacjach interfejsu API.
Przekierowanie
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
Plik
app.MapGet("/download", () => Results.File("myfile.text"));
Wbudowane wyniki
Typowe pomocniki wyników istnieją w Results klasach statycznych i .TypedResults Zwracanie jest preferowane TypedResults
do zwracania Results
wartości . Aby uzyskać więcej informacji, zobacz TypedResults vs Results (TypdResults a wyniki).
Dostosowywanie wyników
Aplikacje mogą kontrolować odpowiedzi, implementując typ niestandardowy IResult . Poniższy kod jest przykładem typu wyniku HTML:
using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
public static IResult Html(this IResultExtensions resultExtensions, string html)
{
ArgumentNullException.ThrowIfNull(resultExtensions);
return new HtmlResult(html);
}
}
class HtmlResult : IResult
{
private readonly string _html;
public HtmlResult(string html)
{
_html = html;
}
public Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
return httpContext.Response.WriteAsync(_html);
}
}
Zalecamy dodanie metody rozszerzenia w celu Microsoft.AspNetCore.Http.IResultExtensions zwiększenia możliwości odnajdywania tych niestandardowych wyników.
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();
Wpisane wyniki
Interfejs IResult może reprezentować wartości zwracane z minimalnych interfejsów API, które nie korzystają z niejawnej obsługi serializowania zwracanego obiektu do odpowiedzi HTTP. Statyczna klasa Results służy do tworzenia różnych IResult
obiektów reprezentujących różne typy odpowiedzi. Na przykład ustawienie kodu stanu odpowiedzi lub przekierowanie do innego adresu URL.
Implementowane IResult
typy są publiczne, co umożliwia asercji typów podczas testowania. Na przykład:
[TestClass()]
public class WeatherApiTests
{
[TestMethod()]
public void MapWeatherApiTest()
{
var result = WeatherApi.GetAllWeathers();
Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
}
}
Możesz przyjrzeć się typom zwracanych odpowiednich metod w statycznej klasie TypedResults , aby znaleźć prawidłowy typ publiczny IResult
do rzutowania.
Aby uzyskać więcej przykładów, zobacz Tworzenie odpowiedzi w minimalnych aplikacjach interfejsu API.
Filtry
Aby uzyskać więcej informacji, zobacz Filtry w minimalnych aplikacjach interfejsu API.
Autoryzacja
Trasy mogą być chronione przy użyciu zasad autoryzacji. Można je zadeklarować za pomocą atrybutu [Authorize]
lub przy użyciu RequireAuthorization metody :
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();
Powyższy kod można napisać za pomocą RequireAuthorizationpolecenia :
app.MapGet("/auth", () => "This endpoint requires authorization")
.RequireAuthorization();
W poniższym przykładzie użyto autoryzacji opartej na zasadach:
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();
Zezwalanie nieuwierzytelnionym użytkownikom na dostęp do punktu końcowego
Ustawienie [AllowAnonymous]
umożliwia nieuwierzytelnionym użytkownikom dostęp do punktów końcowych:
app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");
app.MapGet("/login2", () => "This endpoint also for all roles.")
.AllowAnonymous();
CORS
Trasy mogą być włączone przez mechanizm CORS przy użyciu zasad MECHANIZMU CORS. Mechanizm CORS można zadeklarować za pomocą atrybutu [EnableCors]
lub przy użyciu RequireCors metody . Następujące przykłady umożliwiają mechanizm CORS:
const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
var app = builder.Build();
app.UseCors();
app.MapGet("/",() => "Hello CORS!");
app.Run();
using Microsoft.AspNetCore.Cors;
const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
var app = builder.Build();
app.UseCors();
app.MapGet("/cors", [EnableCors(MyAllowSpecificOrigins)] () =>
"This endpoint allows cross origin requests!");
app.MapGet("/cors2", () => "This endpoint allows cross origin requests!")
.RequireCors(MyAllowSpecificOrigins);
app.Run();
Aby uzyskać więcej informacji, zobacz Włączanie żądań między źródłami (CORS) w usłudze ASP.NET Core
ValidateScopes i ValidateOnBuild
ValidateScopes i ValidateOnBuild są domyślnie włączone w środowisku deweloperów , ale wyłączone w innych środowiskach.
Gdy ValidateOnBuild
parametr to true
, kontener DI weryfikuje konfigurację usługi w czasie kompilacji. Jeśli konfiguracja usługi jest nieprawidłowa, kompilacja kończy się niepowodzeniem podczas uruchamiania aplikacji, a nie w czasie wykonywania żądania usługi.
Gdy ValidateScopes
parametr to true
, kontener DI sprawdza, czy usługa o określonym zakresie nie jest rozpoznawana z zakresu głównego. Rozwiązanie usługi o określonym zakresie z zakresu głównego może spowodować wyciek pamięci, ponieważ usługa jest przechowywana w pamięci dłużej niż zakres żądania.
ValidateScopes
wartości i ValidateOnBuild
są domyślnie fałszywe w trybach innych niż Programowanie ze względu na wydajność.
Poniższy kod pokazuje ValidateScopes
, że jest domyślnie włączony w trybie programowania, ale wyłączony w trybie wydania:
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 { }
Poniższy kod pokazuje ValidateOnBuild
, że jest domyślnie włączony w trybie programowania, ale wyłączony w trybie wydania:
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 { }
Następujący kod jest ValidateScopes
wyłączany i ValidateOnBuild
w pliku 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;
});
}
Zobacz też
- Krótkie informacje o minimalnych interfejsach API
- Generowanie dokumentów OpenAPI
- Tworzenie odpowiedzi w minimalnych aplikacjach interfejsu API
- Filtry w minimalnych aplikacjach interfejsu API
- Obsługa błędów w minimalnych interfejsach API
- Uwierzytelnianie i autoryzacja w minimalnych interfejsach API
- Testowanie minimalnych aplikacji interfejsu API
- Routing zwariowy
- Identity Punkty końcowe interfejsu API
- Obsługa kontenera wstrzykiwania zależności usługi kluczy
- Spojrzenie za kulisami minimalnych punktów końcowych interfejsu API
- Organizowanie minimalnych interfejsów API ASP.NET Core
- Płynna dyskusja dotycząca walidacji w usłudze GitHub
Ten dokument:
- Zawiera skróconą dokumentację dla minimalnych interfejsów API.
- Jest przeznaczony dla doświadczonych deweloperów. Aby zapoznać się z wprowadzeniem, zobacz Samouczek: tworzenie minimalnego interfejsu API przy użyciu platformy ASP.NET Core
Minimalne interfejsy API składają się z następujących elementów:
WebApplication
Następujący kod jest generowany przez szablon ASP.NET Core:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Powyższy kod można utworzyć za pomocą wiersza dotnet new web
polecenia lub wybrać pusty szablon sieci Web w programie Visual Studio.
Poniższy kod tworzy element WebApplication (app
) bez jawnego utworzenia elementu WebApplicationBuilder:
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run();
WebApplication.Create
Inicjuje nowe wystąpienie WebApplication klasy ze wstępnie skonfigurowanymi wartościami domyślnymi.
WebApplication
program automatycznie dodaje następujące oprogramowanie pośredniczące w Minimal API applications
zależności od określonych warunków:
UseDeveloperExceptionPage
element jest dodawany jako pierwszy, gdy parametr ma wartośćHostingEnvironment
"Development"
.UseRouting
Jest dodawany drugi, jeśli kod użytkownika nie został jeszcze wywołanyUseRouting
i jeśli istnieją skonfigurowane punkty końcowe, na przykładapp.MapGet
.UseEndpoints
Jest dodawany na końcu potoku oprogramowania pośredniczącego, jeśli są skonfigurowane jakiekolwiek punkty końcowe.UseAuthentication
jest dodawany natychmiast poUseRouting
tym, jak kod użytkownika nie został jeszcze wywołanyUseAuthentication
i czyIAuthenticationSchemeProvider
można go wykryć u dostawcy usług.IAuthenticationSchemeProvider
program jest domyślnie dodawany podczas korzystania z usługAddAuthentication
, a usługa jest wykrywana przy użyciu poleceniaIServiceProviderIsService
.UseAuthorization
Zostanie dodany dalej, jeśli kod użytkownika nie został jeszcze wywołanyUseAuthorization
i czyIAuthorizationHandlerProvider
można go wykryć u dostawcy usług.IAuthorizationHandlerProvider
program jest domyślnie dodawany podczas korzystania z usługAddAuthorization
, a usługa jest wykrywana przy użyciu poleceniaIServiceProviderIsService
.- Oprogramowanie pośredniczące skonfigurowane przez użytkownika i punkty końcowe są dodawane między elementami
UseRouting
iUseEndpoints
.
Poniższy kod jest w rzeczywistości tym, co tworzy automatyczne oprogramowanie pośredniczące dodawane do aplikacji:
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 => {});
W niektórych przypadkach domyślna konfiguracja oprogramowania pośredniczącego nie jest poprawna dla aplikacji i wymaga modyfikacji. Na przykład UseCors należy wywołać metodę przed UseAuthentication i UseAuthorization. Aplikacja musi wywołać metodę UseAuthentication
, a UseAuthorization
jeśli UseCors
jest wywoływana:
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
Jeśli oprogramowanie pośredniczące powinno być uruchamiane przed rozpoczęciem dopasowywania tras, UseRouting należy wywołać metodę , a oprogramowanie pośredniczące powinno zostać umieszczone przed wywołaniem metody UseRouting
. UseEndpoints nie jest wymagany w tym przypadku, ponieważ jest automatycznie dodawany zgodnie z wcześniejszym opisem:
app.Use((context, next) =>
{
return next(context);
});
app.UseRouting();
// other middleware and endpoints
Podczas dodawania oprogramowania pośredniczącego terminalu:
- Oprogramowanie pośredniczące musi zostać dodane po .
UseEndpoints
- Aplikacja musi wywołać metodę
UseRouting
iUseEndpoints
tak, aby oprogramowanie pośredniczące terminalu można było umieścić w odpowiedniej lokalizacji.
app.UseRouting();
app.MapGet("/", () => "hello world");
app.UseEndpoints(e => {});
app.Run(context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
});
Oprogramowanie pośredniczące terminala to oprogramowanie pośredniczące uruchamiane, jeśli żaden punkt końcowy nie obsługuje żądania.
Praca z portami
Po utworzeniu aplikacji internetowej za pomocą programu Visual Studio lub dotnet new
Properties/launchSettings.json
zostanie utworzony plik, który określa porty, na które odpowiada aplikacja. W poniższych przykładach ustawień portów uruchomienie aplikacji z programu Visual Studio zwraca okno dialogowe Unable to connect to web server 'AppName'
błędu . Program Visual Studio zwraca błąd, ponieważ oczekuje portu określonego w Properties/launchSettings.json
elemecie , ale aplikacja używa portu określonego przez app.Run("http://localhost:3000")
. Uruchom następujący port, zmieniając przykłady z wiersza polecenia.
W poniższych sekcjach ustawiono port, na który odpowiada aplikacja.
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run("http://localhost:3000");
W poprzednim kodzie aplikacja odpowiada na port 3000
.
Wiele portów
W poniższym kodzie aplikacja odpowiada na port 3000
i 4000
.
var app = WebApplication.Create(args);
app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");
app.MapGet("/", () => "Hello World");
app.Run();
Ustawianie portu z wiersza polecenia
Następujące polecenie powoduje, że aplikacja odpowiada na port 7777
:
dotnet run --urls="https://localhost:7777"
Kestrel Jeśli punkt końcowy jest również skonfigurowany w appsettings.json
pliku, appsettings.json
używany jest określony adres URL. Aby uzyskać więcej informacji, zobacz Kestrel Konfiguracja punktu końcowego
Odczytywanie portu ze środowiska
Poniższy kod odczytuje port ze środowiska:
var app = WebApplication.Create(args);
var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";
app.MapGet("/", () => "Hello World");
app.Run($"http://localhost:{port}");
Preferowanym sposobem ustawienia portu ze środowiska jest użycie ASPNETCORE_URLS
zmiennej środowiskowej, która jest pokazana w poniższej sekcji.
Ustawianie portów za pomocą zmiennej środowiskowej ASPNETCORE_URLS
Zmienna ASPNETCORE_URLS
środowiskowa jest dostępna do ustawienia portu:
ASPNETCORE_URLS=http://localhost:3000
ASPNETCORE_URLS
obsługuje wiele adresów URL:
ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000
Aby uzyskać więcej informacji na temat korzystania ze środowiska, zobacz Use multiple environments in ASP.NET Core (Używanie wielu środowisk w środowisku ASP.NET Core)
Nasłuchiwanie we wszystkich interfejsach
W poniższych przykładach pokazano nasłuchiwanie we wszystkich interfejsach
http://*:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://*:3000");
app.MapGet("/", () => "Hello World");
app.Run();
http://+:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://+:3000");
app.MapGet("/", () => "Hello World");
app.Run();
http://0.0.0.0:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://0.0.0.0:3000");
app.MapGet("/", () => "Hello World");
app.Run();
Nasłuchiwanie we wszystkich interfejsach przy użyciu ASPNETCORE_URLS
Powyższe przykłady mogą być używane ASPNETCORE_URLS
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
Określanie protokołu HTTPS przy użyciu certyfikatu programistycznego
var app = WebApplication.Create(args);
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
Aby uzyskać więcej informacji na temat certyfikatu programistycznego, zobacz Trust the ASP.NET Core HTTPS development certificate on Windows and macOS (Ufaj certyfikatowi programistycznemu ASP.NET Core HTTPS w systemach Windows i macOS).
Określanie protokołu HTTPS przy użyciu certyfikatu niestandardowego
W poniższych sekcjach pokazano, jak określić certyfikat niestandardowy przy użyciu appsettings.json
pliku i za pośrednictwem konfiguracji.
Określanie certyfikatu niestandardowego za pomocą polecenia appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Certificates": {
"Default": {
"Path": "cert.pem",
"KeyPath": "key.pem"
}
}
}
}
Określanie certyfikatu niestandardowego za pomocą konfiguracji
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();
Korzystanie z interfejsów API certyfikatów
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();
Konfigurowanie
Poniższy kod odczytuje z systemu konfiguracji:
var app = WebApplication.Create(args);
var message = app.Configuration["HelloKey"] ?? "Config failed!";
app.MapGet("/", () => message);
app.Run();
Aby uzyskać więcej informacji, zobacz Configuration in ASP.NET Core (Konfiguracja w programie ASP.NET Core)
Rejestrowanie
Poniższy kod zapisuje komunikat podczas uruchamiania aplikacji logowania:
var app = WebApplication.Create(args);
app.Logger.LogInformation("The app started");
app.MapGet("/", () => "Hello World");
app.Run();
Aby uzyskać więcej informacji, zobacz Rejestrowanie na platformie .NET Core i ASP.NET Core
Uzyskiwanie dostępu do kontenera wstrzykiwania zależności (DI)
Poniższy kod pokazuje, jak pobrać usługi z kontenera DI podczas uruchamiania aplikacji:
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();
Aby uzyskać więcej informacji, zobacz Wstrzykiwanie zależności na platformie ASP.NET Core.
WebApplicationBuilder
Ta sekcja zawiera przykładowy kod przy użyciu polecenia WebApplicationBuilder.
Zmienianie katalogu głównego zawartości, nazwy aplikacji i środowiska
Poniższy kod ustawia katalog główny zawartości, nazwę aplikacji i środowisko:
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 inicjuje nowe wystąpienie klasy WebApplicationBuilder ze wstępnie skonfigurowanymi wartościami domyślnymi.
Aby uzyskać więcej informacji, zobacz omówienie podstaw platformy ASP.NET Core
Zmienianie katalogu głównego zawartości, nazwy aplikacji i środowiska według zmiennych środowiskowych lub wiersza polecenia
W poniższej tabeli przedstawiono zmienną środowiskową i argument wiersza polecenia używany do zmiany katalogu głównego zawartości, nazwy aplikacji i środowiska:
funkcja | Zmienna środowiskowa | Argument wiersza polecenia |
---|---|---|
Nazwa aplikacji | ASPNETCORE_APPLICATIONNAME | --applicationName |
Nazwa środowiska | ASPNETCORE_ENVIRONMENT | --środowisko |
Katalog główny zawartości | ASPNETCORE_CONTENTROOT | --contentRoot |
Dodawanie dostawców konfiguracji
Poniższy przykład dodaje dostawcę konfiguracji INI:
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddIniFile("appsettings.ini");
var app = builder.Build();
Aby uzyskać szczegółowe informacje, zobacz Dostawcy konfiguracji plików w konfiguracji w programie ASP.NET Core.
Konfiguracja odczytu
Domyślnie WebApplicationBuilder konfiguracja odczytu z wielu źródeł, w tym:
appSettings.json
iappSettings.{environment}.json
- Zmienne środowiskowe
- Wiersz polecenia
Poniższy kod odczytuje HelloKey
z konfiguracji i wyświetla wartość w punkcie /
końcowym. Jeśli wartość konfiguracji ma wartość null, "Hello" zostanie przypisana do elementu message
:
var builder = WebApplication.CreateBuilder(args);
var message = builder.Configuration["HelloKey"] ?? "Hello";
var app = builder.Build();
app.MapGet("/", () => message);
app.Run();
Aby uzyskać pełną listę źródeł konfiguracji, zobacz Konfiguracja domyślna w konfiguracji w programie ASP.NET Core
Dodawanie dostawców rejestrowania
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();
Dodawanie usług
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();
Dostosowywanie elementu IHostBuilder
Dostęp do istniejących metod rozszerzeń IHostBuilder można uzyskać przy użyciu właściwości Host:
var builder = WebApplication.CreateBuilder(args);
// Wait 30 seconds for graceful shutdown.
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Dostosowywanie obiektu IWebHostBuilder
Dostęp do metod rozszerzeń IWebHostBuilder można uzyskać przy użyciu właściwości WebApplicationBuilder.WebHost .
var builder = WebApplication.CreateBuilder(args);
// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();
var app = builder.Build();
app.MapGet("/", () => "Hello HTTP.sys");
app.Run();
Zmienianie katalogu głównego sieci Web
Domyślnie katalog główny sieci Web jest powiązany z katalogem głównym zawartości w folderze wwwroot
. Katalog główny sieci Web to miejsce, w którym oprogramowanie pośredniczące plików statycznych szuka plików statycznych. Katalog główny sieci Web można zmienić za pomocą WebHostOptions
polecenia , wiersza polecenia lub UseWebRoot metody :
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
// Look for static files in webroot
WebRootPath = "webroot"
});
var app = builder.Build();
app.Run();
Niestandardowy kontener wstrzykiwania zależności (DI)
W poniższym przykładzie użyto funkcji Autofac:
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));
var app = builder.Build();
Dodawanie oprogramowania pośredniczącego
W programie WebApplication
można skonfigurować dowolne istniejące oprogramowanie pośredniczące ASP.NET Core:
var app = WebApplication.Create(args);
// Setup the file server to serve static files.
app.UseFileServer();
app.MapGet("/", () => "Hello World!");
app.Run();
Aby uzyskać więcej informacji, zobacz ASP.NET Core Middleware
Strona wyjątku dla deweloperów
WebApplication.CreateBuilder Inicjuje nowe wystąpienie WebApplicationBuilder klasy ze wstępnie skonfigurowanymi wartościami domyślnymi. Strona wyjątku dewelopera jest włączona w wstępnie skonfigurowanych wartościach domyślnych. Po uruchomieniu następującego kodu w środowisku deweloperskim przejście do /
strony renderuje przyjazną stronę, która pokazuje wyjątek.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>
{
throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});
app.Run();
Oprogramowanie pośredniczące platformy ASP.NET Core
W poniższej tabeli wymieniono niektóre oprogramowanie pośredniczące często używane z minimalnymi interfejsami API.
Oprogramowanie pośredniczące | opis | interfejs API |
---|---|---|
Authentication | Zapewnia obsługę uwierzytelniania. | UseAuthentication |
Autoryzacja | Zapewnia obsługę autoryzacji. | UseAuthorization |
CORS | Konfiguruje współużytkowanie zasobów między źródłami. | UseCors |
Procedura obsługi wyjątków | Globalnie obsługuje wyjątki zgłaszane przez potok oprogramowania pośredniczącego. | UseExceptionHandler |
Przekazane nagłówki | Przekazuje nagłówki przesłane przez serwer proxy do bieżącego żądania. | UseForwardedHeaders |
Przekierowywanie HTTPS | Przekierowuje wszystkie żądania HTTP do protokołu HTTPS. | UseHttpsRedirection |
HTTP Strict Transport Security (HSTS) | Oprogramowanie pośredniczące rozszerzenia zabezpieczeń, które dodaje specjalny nagłówek odpowiedzi. | UseHsts |
Rejestrowanie żądań | Zapewnia obsługę rejestrowania żądań HTTP i odpowiedzi. | UseHttpLogging |
Limity czasu żądania | Zapewnia obsługę konfigurowania limitów czasu żądań, wartości domyślnych globalnych i poszczególnych punktów końcowych. | UseRequestTimeouts |
Rejestrowanie żądań W3C | Zapewnia obsługę rejestrowania żądań HTTP i odpowiedzi w formacie W3C. | UseW3CLogging |
Buforowanie odpowiedzi | Zapewnia obsługę buforowania odpowiedzi. | UseResponseCaching |
Kompresja odpowiedzi | Zapewnia obsługę kompresowania odpowiedzi. | UseResponseCompression |
Sesja | Zapewnia obsługę zarządzania sesjami użytkowników. | UseSession |
Pliki statyczne | Zapewnia obsługę plików statycznych i przeglądania katalogów. | UseStaticFiles, UseFileServer |
Obiekty WebSocket | Włącza protokoły WebSocket. | UseWebSockets |
W poniższych sekcjach omówiono obsługę żądań: routing, powiązanie parametrów i odpowiedzi.
Routing
Skonfigurowana WebApplication
obsługuje Map{Verb}
metodę {Verb}
MapMethods HTTP typu camel-cased, npGet
. , Post
Put
lub Delete
:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "This is a GET");
app.MapPost("/", () => "This is a POST");
app.MapPut("/", () => "This is a PUT");
app.MapDelete("/", () => "This is a DELETE");
app.MapMethods("/options-or-head", new[] { "OPTIONS", "HEAD" },
() => "This is an options or head request ");
app.Run();
Argumenty Delegate przekazywane do tych metod są nazywane "procedurami obsługi tras".
Programy obsługi tras
Programy obsługi tras to metody, które są wykonywane, gdy trasa jest zgodna. Programy obsługi tras mogą być wyrażeniem lambda, funkcją lokalną, metodą wystąpienia lub metodą statyczną. Programy obsługi tras mogą być synchroniczne lub asynchroniczne.
Wyrażenie lambda
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/inline", () => "This is an inline lambda");
var handler = () => "This is a lambda variable";
app.MapGet("/", handler);
app.Run();
Funkcja lokalna
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
string LocalFunction() => "This is local function";
app.MapGet("/", LocalFunction);
app.Run();
Metoda wystąpienia
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";
}
}
Metoda statyczna
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";
}
}
Punkt końcowy zdefiniowany poza Program.cs
Minimalne interfejsy API nie muszą znajdować się w lokalizacji Program.cs
.
Program.cs
using MinAPISeparateFile;
var builder = WebApplication.CreateSlimBuilder(args);
var app = builder.Build();
TodoEndpoints.Map(app);
app.Run();
TodoEndpoints.cs
namespace MinAPISeparateFile;
public static class TodoEndpoints
{
public static void Map(WebApplication app)
{
app.MapGet("/", async context =>
{
// Get all todo items
await context.Response.WriteAsJsonAsync(new { Message = "All todo items" });
});
app.MapGet("/{id}", async context =>
{
// Get one todo item
await context.Response.WriteAsJsonAsync(new { Message = "One todo item" });
});
}
}
Zobacz również Grupy tras w dalszej części tego artykułu.
Nazwane punkty końcowe i generowanie linków
Punkty końcowe mogą mieć nazwy w celu wygenerowania adresów URL do punktu końcowego. Użycie nazwanego punktu końcowego pozwala uniknąć konieczności stosowania twardych ścieżek kodu w aplikacji:
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();
Powyższy kod jest wyświetlany The link to the hello route is /hello
z punktu końcowego /
.
UWAGA: W nazwach punktów końcowych jest rozróżniana wielkość liter.
Nazwy punktów końcowych:
- Musi ona być unikatowa w skali globalnej.
- Są używane jako identyfikator operacji interfejsu OpenAPI, gdy jest włączona obsługa interfejsu OpenAPI. Aby uzyskać więcej informacji, zobacz OpenAPI.
Parametry trasy
Parametry trasy można przechwycić w ramach definicji wzorca trasy:
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();
Powyższy kod zwraca The user id is 3 and book id is 7
z identyfikatora URI /users/3/books/7
.
Procedura obsługi tras może zadeklarować parametry do przechwycenia. Po wysłaniu żądania do trasy z zadeklarowanymi parametrami do przechwycenia parametry są analizowane i przekazywane do programu obsługi. Ułatwia to przechwytywanie wartości w bezpieczny sposób typu. W poprzednim kodzie userId
i bookId
mają wartość int
.
W poprzednim kodzie, jeśli nie można przekonwertować żadnej wartości trasy na int
wartość , zgłaszany jest wyjątek. Żądanie /users/hello/books/3
GET zgłasza następujący wyjątek:
BadHttpRequestException: Failed to bind parameter "int userId" from "hello".
Symbol wieloznaczny i przechwyć wszystkie trasy
Następujące przechwycenie wszystkich tras zwracanych Routing to hello
z punktu końcowego "/posts/hello":
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");
app.Run();
Ograniczenia trasy
Ograniczenia trasy ograniczają zgodne zachowanie trasy.
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();
W poniższej tabeli przedstawiono powyższe szablony tras i ich zachowanie:
Szablon trasy | Przykładowy pasujący identyfikator URI |
---|---|
/todos/{id:int} |
/todos/1 |
/todos/{text} |
/todos/something |
/posts/{slug:regex(^[a-z0-9_-]+$)} |
/posts/mypost |
Aby uzyskać więcej informacji, zobacz Route constraint reference in Routing in ASP.NET Core (Dokumentacja ograniczeń tras w usłudze Routing w usłudze ASP.NET Core).
Grupy tras
Metoda MapGroup rozszerzenia ułatwia organizowanie grup punktów końcowych za pomocą wspólnego prefiksu. Zmniejsza powtarzalny kod i umożliwia dostosowywanie całych grup punktów końcowych za pomocą jednego wywołania metod, takich jak RequireAuthorization i WithMetadata które dodają metadane punktu końcowego.
Na przykład poniższy kod tworzy dwie podobne grupy punktów końcowych:
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;
}
W tym scenariuszu możesz użyć względnego adresu nagłówka Location
w 201 Created
wyniku:
public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
await database.AddAsync(todo);
await database.SaveChangesAsync();
return TypedResults.Created($"{todo.Id}", todo);
}
Pierwsza grupa punktów końcowych będzie pasowała tylko do żądań z prefiksem /public/todos
i jest dostępna bez żadnego uwierzytelniania. Druga grupa punktów końcowych będzie pasowała tylko do żądań poprzedzonych prefiksem /private/todos
i wymaga uwierzytelniania.
QueryPrivateTodos
Fabryka filtrów punktów końcowych to funkcja lokalna, która modyfikuje parametry programu obsługi TodoDb
tras, aby umożliwić dostęp do prywatnych danych zadań do wykonania i przechowywanie ich.
Grupy tras obsługują również grupy zagnieżdżone i złożone wzorce prefiksów z parametrami i ograniczeniami trasy. W poniższym przykładzie program obsługi tras zamapowany na grupę user
może przechwytywać {org}
parametry trasy i {group}
zdefiniowane w prefiksach grup zewnętrznych.
Prefiks może być również pusty. Może to być przydatne w przypadku dodawania metadanych lub filtrów punktu końcowego do grupy punktów końcowych bez zmiany wzorca trasy.
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
Dodanie filtrów lub metadanych do grupy działa tak samo jak dodawanie ich indywidualnie do każdego punktu końcowego przed dodaniem dodatkowych filtrów lub metadanych, które mogły zostać dodane do grupy wewnętrznej lub określonego punktu końcowego.
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);
});
W powyższym przykładzie zewnętrzny filtr będzie rejestrować żądanie przychodzące przed filtrem wewnętrznym, mimo że został dodany w sekundzie. Ponieważ filtry zostały zastosowane do różnych grup, kolejność, którą zostały dodane względem siebie, nie ma znaczenia. Filtry kolejności są dodawane niezależnie od tego, czy są stosowane do tej samej grupy lub określonego punktu końcowego.
Żądanie, aby zarejestrować /outer/inner/
następujące elementy:
/outer group filter
/inner group filter
MapGet filter
Powiązanie parametrów
Powiązanie parametrów to proces konwertowania danych żądania na silnie typizowane parametry, które są wyrażane przez programy obsługi tras. Źródło powiązania określa, skąd są powiązane parametry. Źródła powiązań mogą być jawne lub wnioskowane na podstawie metody HTTP i typu parametru.
Obsługiwane źródła powiązań:
- Wartości tras
- Ciąg zapytania
- Nagłówek
- Treść (jako kod JSON)
- Usługi udostępniane przez wstrzykiwanie zależności
- Niestandardowy
Powiązanie z wartości formularza nie jest natywnie obsługiwane na platformie .NET 6 i 7.
GET
Poniższa procedura obsługi tras używa niektórych z tych źródeł powiązań parametrów:
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 { }
W poniższej tabeli przedstawiono relację między parametrami używanymi w poprzednim przykładzie i skojarzonymi źródłami powiązań.
Parametr | Źródło powiązania |
---|---|
id |
wartość trasy |
page |
ciąg zapytania |
customHeader |
nagłówek |
service |
Udostępniane przez wstrzykiwanie zależności |
Metody GET
HTTP , HEAD
, OPTIONS
i DELETE
nie są niejawnie powiązane z treścią. Aby powiązać z treścią (jako kod JSON) dla tych metod HTTP, powiąż jawnie z elementem [FromBody]
lub odczyt z pliku HttpRequest.
W poniższym przykładzie procedura obsługi tras POST używa powiązania źródła treści (jako kodu JSON) dla parametru person
:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/", (Person person) => { });
record Person(string Name, int Age);
Parametry w poprzednich przykładach są automatycznie powiązane z danymi żądania. Aby zademonstrować wygodę zapewnianą przez powiązanie parametrów, następujące programy obsługi tras pokazują, jak odczytywać dane żądania bezpośrednio z żądania:
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>();
// ...
});
Jawne powiązanie parametrów
Atrybuty mogą służyć do jawnego deklarowania, gdzie parametry są powiązane.
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);
Parametr | Źródło powiązania |
---|---|
id |
wartość trasy o nazwie id |
page |
ciąg zapytania o nazwie "p" |
service |
Udostępniane przez wstrzykiwanie zależności |
contentType |
nagłówek o nazwie "Content-Type" |
Uwaga
Powiązanie z wartości formularza nie jest natywnie obsługiwane na platformie .NET 6 i 7.
Powiązanie parametrów z wstrzyknięciem zależności
Powiązanie parametrów dla minimalnych interfejsów API wiąże parametry za pośrednictwem wstrzykiwania zależności, gdy typ jest skonfigurowany jako usługa. Nie jest konieczne jawne zastosowanie atrybutu [FromServices]
do parametru. W poniższym kodzie obie akcje zwracają czas:
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();
Parametry opcjonalne
Parametry zadeklarowane w programach obsługi tras są traktowane zgodnie z wymaganiami:
- Jeśli żądanie pasuje do trasy, procedura obsługi tras jest uruchamiana tylko wtedy, gdy wszystkie wymagane parametry są podane w żądaniu.
- Niepowodzenie podania wszystkich wymaganych parametrów powoduje wystąpienie błędu.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");
app.Run();
Identyfikator URI | result |
---|---|
/products?pageNumber=3 |
3 zwrócone |
/products |
BadHttpRequestException : Wymagany parametr "int pageNumber" nie został podany z ciągu zapytania. |
/products/1 |
Błąd HTTP 404, brak pasującej trasy |
Aby ustawić pageNumber
wartość opcjonalną, zdefiniuj typ jako opcjonalny lub podaj wartość domyślną:
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();
Identyfikator URI | result |
---|---|
/products?pageNumber=3 |
3 zwrócone |
/products |
1 zwrócone |
/products2 |
1 zwrócone |
Poprzednia wartość dopuszczająca wartość null i domyślna ma zastosowanie do wszystkich źródeł:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/products", (Product? product) => { });
app.Run();
Powyższy kod wywołuje metodę z produktem o wartości null, jeśli nie zostanie wysłana żadna treść żądania.
UWAGA: Jeśli podano nieprawidłowe dane i parametr ma wartość null, procedura obsługi tras nie jest uruchamiana.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");
app.Run();
Identyfikator URI | result |
---|---|
/products?pageNumber=3 |
3 Zwracane |
/products |
1 Zwracane |
/products?pageNumber=two |
BadHttpRequestException : Nie można powiązać parametru "Nullable<int> pageNumber" z "dwóch". |
/products/two |
Błąd HTTP 404, brak pasującej trasy |
Aby uzyskać więcej informacji, zobacz sekcję Błędy powiązań.
Typy specjalne
Następujące typy są powiązane bez jawnych atrybutów:
HttpContext: kontekst zawierający wszystkie informacje o bieżącym żądaniu HTTP lub odpowiedzi:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
HttpRequest i HttpResponse: Żądanie HTTP i odpowiedź HTTP:
app.MapGet("/", (HttpRequest request, HttpResponse response) => response.WriteAsync($"Hello World {request.Query["name"]}"));
CancellationToken: token anulowania skojarzony z bieżącym żądaniem HTTP:
app.MapGet("/", async (CancellationToken cancellationToken) => await MakeLongRunningRequestAsync(cancellationToken));
ClaimsPrincipal: użytkownik skojarzony z żądaniem powiązany z elementem HttpContext.User:
app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
Powiąż treść żądania jako element Stream
lub PipeReader
Treść żądania może wiązać się ze scenariuszami Stream
lub PipeReader
, w których użytkownik musi przetwarzać dane i:
- Zapisz dane w magazynie obiektów blob lub zapisz dane w kolejce do dostawcy kolejki.
- Przetwarzanie przechowywanych danych za pomocą procesu roboczego lub funkcji w chmurze.
Na przykład dane mogą być w kolejce do usługi Azure Queue Storage lub przechowywane w usłudze Azure Blob Storage.
Poniższy kod implementuje kolejkę w tle:
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;
}
Poniższy kod wiąże treść żądania z elementem 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);
});
Poniższy kod przedstawia kompletny Program.cs
plik:
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();
- Podczas odczytywania danych
Stream
obiekt jest tym samym obiektem coHttpRequest.Body
. - Treść żądania nie jest domyślnie buforowana. Po odczytaniu treści nie można jej przewijać. Strumień nie może być odczytywany wiele razy.
- Elementy
Stream
iPipeReader
nie mogą być używane poza minimalną procedurą obsługi akcji, ponieważ bazowe zostaną usunięte lub ponownie użyte.
Przekazywanie plików przy użyciu elementu IFormFile i IFormFileCollection
Poniższy kod używa instrukcji IFormFile i IFormFileCollection do przekazywania pliku:
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();
Uwierzytelnione żądania przekazywania plików są obsługiwane przy użyciu nagłówka autoryzacji, certyfikatu klienta lub nagłówka cookie .
Nie ma wbudowanej obsługi antyforgery w ASP.NET Core 7.0. Antiforgery jest dostępny w ASP.NET Core 8.0 lub nowszym. Można go jednak zaimplementować przy użyciu IAntiforgery
usługi.
Wiązanie tablic i wartości ciągów z nagłówków i ciągów zapytania
Poniższy kod demonstruje powiązania ciągów zapytania z tablicą typów pierwotnych, tablic ciągów i 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]}");
Powiązanie ciągów zapytania lub wartości nagłówków z tablicą typów złożonych jest obsługiwane, gdy typ został TryParse
zaimplementowany. Poniższy kod wiąże się z tablicą ciągów i zwraca wszystkie elementy z określonymi tagami:
// 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();
});
Poniższy kod przedstawia model i wymaganą TryParse
implementację:
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;
}
}
Poniższy kod wiąże się z tablicą int
:
// GET /todoitems/query-string-ids?ids=1&ids=3
app.MapGet("/todoitems/query-string-ids", async (int[] ids, TodoDb db) =>
{
return await db.Todos
.Where(t => ids.Contains(t.Id))
.ToListAsync();
});
Aby przetestować poprzedni kod, dodaj następujący punkt końcowy, aby wypełnić bazę danych elementami Todo
:
// POST /todoitems/batch
app.MapPost("/todoitems/batch", async (Todo[] todos, TodoDb db) =>
{
await db.Todos.AddRangeAsync(todos);
await db.SaveChangesAsync();
return Results.Ok(todos);
});
Użyj narzędzia do testowania interfejsu API, takiego jak HttpRepl
przekazywanie następujących danych do poprzedniego punktu końcowego:
[
{
"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"
}
}
]
Poniższy kod wiąże się z kluczem X-Todo-Id
nagłówka i zwraca Todo
elementy z pasującymi Id
wartościami:
// 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();
});
Uwaga
Podczas tworzenia powiązania string[]
elementu z ciągu zapytania brak pasującej wartości ciągu zapytania spowoduje, że zamiast wartości null zostanie pusta tablica.
Powiązanie parametrów dla list argumentów za pomocą parametrów [AsParameters]
AsParametersAttribute Umożliwia proste powiązanie parametrów z typami, a nie złożonymi lub cyklicznych powiązań modelu.
Spójrzmy na poniższy kod:
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.
Rozważ następujący GET
punkt końcowy:
app.MapGet("/todoitems/{id}",
async (int Id, TodoDb Db) =>
await Db.Todos.FindAsync(Id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
struct
Następujące elementy mogą służyć do zastępowania poprzednich wyróżnionych parametrów:
struct TodoItemRequest
{
public int Id { get; set; }
public TodoDb Db { get; set; }
}
Refaktoryzowany GET
punkt końcowy używa powyższego struct
atrybutu AsParameters :
app.MapGet("/ap/todoitems/{id}",
async ([AsParameters] TodoItemRequest request) =>
await request.Db.Todos.FindAsync(request.Id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
Poniższy kod przedstawia dodatkowe punkty końcowe w aplikacji:
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();
});
Następujące klasy służą do refaktoryzacji list parametrów:
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!;
}
Poniższy kod przedstawia refaktoryzowane punkty końcowe przy użyciu poleceń AsParameters
oraz poprzednie struct
klasy i :
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();
});
Zastąp powyższe parametry następującymi record
typami:
record TodoItemRequest(int Id, TodoDb Db);
record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);
Użycie elementu z AsParameters
funkcją struct
może być bardziej wydajne niż użycie record
typu.
Kompletny przykładowy kod w repozytorium AspNetCore.Docs.Samples .
Wiązanie niestandardowe
Istnieją dwa sposoby dostosowywania powiązania parametrów:
- W przypadku źródeł tras, zapytań i powiązań nagłówków powiąż typy niestandardowe, dodając metodę statyczną
TryParse
dla typu. - Kontrolowanie procesu wiązania przez zaimplementowanie
BindAsync
metody dla typu.
TryParse
TryParse
ma dwa interfejsy API:
public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);
Poniższy kod jest wyświetlany Point: 12.3, 10.1
za pomocą identyfikatora URI /map?Point=12.3,10.1
:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// GET /map?Point=12.3,10.1
app.MapGet("/map", (Point point) => $"Point: {point.X}, {point.Y}");
app.Run();
public class Point
{
public double X { get; set; }
public double Y { get; set; }
public static bool TryParse(string? value, IFormatProvider? provider,
out Point? point)
{
// Format is "(12.3,10.1)"
var trimmedValue = value?.TrimStart('(').TrimEnd(')');
var segments = trimmedValue?.Split(',',
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (segments?.Length == 2
&& double.TryParse(segments[0], out var x)
&& double.TryParse(segments[1], out var y))
{
point = new Point { X = x, Y = y };
return true;
}
point = null;
return false;
}
}
BindAsync
BindAsync
ma następujące interfejsy API:
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
Poniższy kod jest wyświetlany SortBy:xyz, SortDirection:Desc, CurrentPage:99
za pomocą identyfikatora URI /products?SortBy=xyz&SortDir=Desc&Page=99
:
using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// GET /products?SortBy=xyz&SortDir=Desc&Page=99
app.MapGet("/products", (PagingData pageData) => $"SortBy:{pageData.SortBy}, " +
$"SortDirection:{pageData.SortDirection}, CurrentPage:{pageData.CurrentPage}");
app.Run();
public class PagingData
{
public string? SortBy { get; init; }
public SortDirection SortDirection { get; init; }
public int CurrentPage { get; init; } = 1;
public static ValueTask<PagingData?> BindAsync(HttpContext context,
ParameterInfo parameter)
{
const string sortByKey = "sortBy";
const string sortDirectionKey = "sortDir";
const string currentPageKey = "page";
Enum.TryParse<SortDirection>(context.Request.Query[sortDirectionKey],
ignoreCase: true, out var sortDirection);
int.TryParse(context.Request.Query[currentPageKey], out var page);
page = page == 0 ? 1 : page;
var result = new PagingData
{
SortBy = context.Request.Query[sortByKey],
SortDirection = sortDirection,
CurrentPage = page
};
return ValueTask.FromResult<PagingData?>(result);
}
}
public enum SortDirection
{
Default,
Asc,
Desc
}
Błędy powiązań
Gdy powiązanie zakończy się niepowodzeniem, platforma rejestruje komunikat debugowania i zwraca różne kody stanu do klienta w zależności od trybu awarii.
Tryb awarii | Typ parametru dopuszczalnego do wartości null | Źródło powiązania | Kod stanu |
---|---|---|---|
{ParameterType}.TryParse Zwraca false |
tak | route/query/header | 400 |
{ParameterType}.BindAsync Zwraca null |
tak | niestandardowe | 400 |
{ParameterType}.BindAsync Zgłasza |
nie ma znaczenia | niestandardowe | 500 |
Nie można wykonać deserializacji treści JSON | nie ma znaczenia | treść | 400 |
Nieprawidłowy typ zawartości (nie application/json ) |
nie ma znaczenia | treść | 415 |
Pierwszeństwo powiązania
Reguły określania źródła powiązania z parametru:
- Jawny atrybut zdefiniowany w parametrze (atrybuty From*) w następującej kolejności:
- Wartości tras:
[FromRoute]
- Ciąg zapytania:
[FromQuery]
- Nagłówek:
[FromHeader]
- Ciało:
[FromBody]
- Usługa:
[FromServices]
- Wartości parametrów:
[AsParameters]
- Wartości tras:
- Typy specjalne
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
)
- Typ parametru ma prawidłową metodę statyczną
BindAsync
. - Typ parametru jest ciągiem lub ma prawidłową metodę statyczną
TryParse
.- Jeśli nazwa parametru istnieje w szablonie trasy. W elemecie
app.Map("/todo/{id}", (int id) => {});
id
jest powiązana z trasą. - Powiązana z ciągu zapytania.
- Jeśli nazwa parametru istnieje w szablonie trasy. W elemecie
- Jeśli typ parametru jest usługą dostarczaną przez iniekcję zależności, używa tej usługi jako źródła.
- Parametr pochodzi z treści.
Konfigurowanie opcji deserializacji JSON dla powiązania treści
Źródło powiązania treści używa System.Text.Json do deserializacji. Nie można zmienić tej wartości domyślnej, ale można skonfigurować opcje serializacji i deserializacji JSON.
Globalne konfigurowanie opcji deserializacji JSON
Opcje stosowane globalnie dla aplikacji można skonfigurować, wywołując ConfigureHttpJsonOptionsmetodę . Poniższy przykład zawiera pola publiczne i formaty danych wyjściowych JSON.
var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureHttpJsonOptions(options => {
options.SerializerOptions.WriteIndented = true;
options.SerializerOptions.IncludeFields = true;
});
var app = builder.Build();
app.MapPost("/", (Todo todo) => {
if (todo is not null) {
todo.Name = todo.NameField;
}
return todo;
});
app.Run();
class Todo {
public string? Name { get; set; }
public string? NameField;
public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "nameField":"Walk dog",
// "isComplete":false
// }
Ponieważ przykładowy kod konfiguruje zarówno serializacji, jak i deserializacji, może odczytywać NameField
i uwzględniać NameField
dane wyjściowe w formacie JSON.
Konfigurowanie opcji deserializacji JSON dla punktu końcowego
ReadFromJsonAsync ma przeciążenia, które akceptują JsonSerializerOptions obiekt. Poniższy przykład zawiera pola publiczne i formaty danych wyjściowych JSON.
using System.Text.Json;
var app = WebApplication.Create();
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) {
IncludeFields = true,
WriteIndented = true
};
app.MapPost("/", async (HttpContext context) => {
if (context.Request.HasJsonContentType()) {
var todo = await context.Request.ReadFromJsonAsync<Todo>(options);
if (todo is not null) {
todo.Name = todo.NameField;
}
return Results.Ok(todo);
}
else {
return Results.BadRequest();
}
});
app.Run();
class Todo
{
public string? Name { get; set; }
public string? NameField;
public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "isComplete":false
// }
Ponieważ powyższy kod stosuje dostosowane opcje tylko do deserializacji, dane wyjściowe JSON wykluczają NameField
wartość .
Odczytywanie treści żądania
Odczytywanie treści żądania bezpośrednio przy użyciu parametru HttpContext lub 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();
Powyższy kod ma następujące działanie:
- Uzyskuje dostęp do treści żądania przy użyciu polecenia HttpRequest.BodyReader.
- Kopiuje treść żądania do pliku lokalnego.
Odpowiedzi
Programy obsługi tras obsługują następujące typy zwracanych wartości:
IResult
oparte — obejmujeTask<IResult>
to iValueTask<IResult>
string
- Obejmuje toTask<string>
iValueTask<string>
T
(Dowolny inny typ) — obejmujeTask<T>
to iValueTask<T>
Wartość zwracana | Zachowanie | Typ zawartości |
---|---|---|
IResult |
Struktura wywołuje metodę IResult.ExecuteAsync | Decyzja o wdrożeniu IResult |
string |
Struktura zapisuje ciąg bezpośrednio w odpowiedzi | text/plain |
T (Dowolny inny typ) |
Struktura JSON serializuje odpowiedź | application/json |
Aby uzyskać bardziej szczegółowy przewodnik po zwracaniu wartości procedury obsługi tras, zobacz Tworzenie odpowiedzi w aplikacjach interfejsu API w minimalnej liczbie
Przykładowe wartości zwracane
ciąg zwracane wartości
app.MapGet("/hello", () => "Hello World");
Wartości zwracane w formacie JSON
app.MapGet("/hello", () => new { Message = "Hello World" });
Zwracane wartości TypedResults
Poniższy kod zwraca element TypedResults:
app.MapGet("/hello", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
Zwracanie jest preferowane TypedResults
do zwracania Resultswartości . Aby uzyskać więcej informacji, zobacz TypedResults vs Results (TypdResults a wyniki).
Zwracane wartości IResult
app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));
W poniższym przykładzie użyto wbudowanych typów wyników, aby dostosować odpowiedź:
app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound())
.Produces<Todo>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);
JSON
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
Niestandardowy kod stanu
app.MapGet("/405", () => Results.StatusCode(405));
Text
app.MapGet("/text", () => Results.Text("This is some text"));
Stream
var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () =>
{
var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
// Proxy the response as JSON
return Results.Stream(stream, "application/json");
});
Aby uzyskać więcej przykładów, zobacz Tworzenie odpowiedzi w minimalnych aplikacjach interfejsu API.
Przekierowanie
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
Plik
app.MapGet("/download", () => Results.File("myfile.text"));
Wbudowane wyniki
Typowe pomocniki wyników istnieją w Results klasach statycznych i .TypedResults Zwracanie jest preferowane TypedResults
do zwracania Results
wartości . Aby uzyskać więcej informacji, zobacz TypedResults vs Results (TypdResults a wyniki).
Dostosowywanie wyników
Aplikacje mogą kontrolować odpowiedzi, implementując typ niestandardowy IResult . Poniższy kod jest przykładem typu wyniku HTML:
using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
public static IResult Html(this IResultExtensions resultExtensions, string html)
{
ArgumentNullException.ThrowIfNull(resultExtensions);
return new HtmlResult(html);
}
}
class HtmlResult : IResult
{
private readonly string _html;
public HtmlResult(string html)
{
_html = html;
}
public Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
return httpContext.Response.WriteAsync(_html);
}
}
Zalecamy dodanie metody rozszerzenia w celu Microsoft.AspNetCore.Http.IResultExtensions zwiększenia możliwości odnajdywania tych niestandardowych wyników.
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();
Wpisane wyniki
Interfejs IResult może reprezentować wartości zwracane z minimalnych interfejsów API, które nie korzystają z niejawnej obsługi serializowania zwracanego obiektu do odpowiedzi HTTP. Statyczna klasa Results służy do tworzenia różnych IResult
obiektów reprezentujących różne typy odpowiedzi. Na przykład ustawienie kodu stanu odpowiedzi lub przekierowanie do innego adresu URL.
Implementowane IResult
typy są publiczne, co umożliwia asercji typów podczas testowania. Na przykład:
[TestClass()]
public class WeatherApiTests
{
[TestMethod()]
public void MapWeatherApiTest()
{
var result = WeatherApi.GetAllWeathers();
Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
}
}
Możesz przyjrzeć się typom zwracanych odpowiednich metod w statycznej klasie TypedResults , aby znaleźć prawidłowy typ publiczny IResult
do rzutowania.
Aby uzyskać więcej przykładów, zobacz Tworzenie odpowiedzi w minimalnych aplikacjach interfejsu API.
Filtry
Zobacz Filtry w minimalnych aplikacjach interfejsu API
Autoryzacja
Trasy mogą być chronione przy użyciu zasad autoryzacji. Można je zadeklarować za pomocą atrybutu [Authorize]
lub przy użyciu RequireAuthorization metody :
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();
Powyższy kod można napisać za pomocą RequireAuthorizationpolecenia :
app.MapGet("/auth", () => "This endpoint requires authorization")
.RequireAuthorization();
W poniższym przykładzie użyto autoryzacji opartej na zasadach:
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();
Zezwalanie nieuwierzytelnionym użytkownikom na dostęp do punktu końcowego
Ustawienie [AllowAnonymous]
umożliwia nieuwierzytelnionym użytkownikom dostęp do punktów końcowych:
app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");
app.MapGet("/login2", () => "This endpoint also for all roles.")
.AllowAnonymous();
CORS
Trasy mogą być włączone przez mechanizm CORS przy użyciu zasad MECHANIZMU CORS. Mechanizm CORS można zadeklarować za pomocą atrybutu [EnableCors]
lub przy użyciu RequireCors metody . Następujące przykłady umożliwiają mechanizm CORS:
const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
var app = builder.Build();
app.UseCors();
app.MapGet("/",() => "Hello CORS!");
app.Run();
using Microsoft.AspNetCore.Cors;
const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
var app = builder.Build();
app.UseCors();
app.MapGet("/cors", [EnableCors(MyAllowSpecificOrigins)] () =>
"This endpoint allows cross origin requests!");
app.MapGet("/cors2", () => "This endpoint allows cross origin requests!")
.RequireCors(MyAllowSpecificOrigins);
app.Run();
Aby uzyskać więcej informacji, zobacz Włączanie żądań między źródłami (CORS) w usłudze ASP.NET Core
Zobacz też
Ten dokument:
- Zawiera skróconą dokumentację dla minimalnych interfejsów API.
- Jest przeznaczony dla doświadczonych deweloperów. Aby zapoznać się z wprowadzeniem, zobacz Samouczek: tworzenie minimalnego interfejsu API przy użyciu platformy ASP.NET Core
Minimalne interfejsy API składają się z następujących elementów:
- WebApplication i WebApplicationBuilder
- Programy obsługi tras
WebApplication
Następujący kod jest generowany przez szablon ASP.NET Core:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Powyższy kod można utworzyć za pomocą wiersza dotnet new web
polecenia lub wybrać pusty szablon sieci Web w programie Visual Studio.
Poniższy kod tworzy element WebApplication (app
) bez jawnego utworzenia elementu WebApplicationBuilder:
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run();
WebApplication.Create
Inicjuje nowe wystąpienie WebApplication klasy ze wstępnie skonfigurowanymi wartościami domyślnymi.
Praca z portami
Po utworzeniu aplikacji internetowej za pomocą programu Visual Studio lub dotnet new
Properties/launchSettings.json
zostanie utworzony plik, który określa porty, na które odpowiada aplikacja. W poniższych przykładach ustawień portów uruchomienie aplikacji z programu Visual Studio zwraca okno dialogowe Unable to connect to web server 'AppName'
błędu . Uruchom następujący port, zmieniając przykłady z wiersza polecenia.
W poniższych sekcjach ustawiono port, na który odpowiada aplikacja.
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run("http://localhost:3000");
W poprzednim kodzie aplikacja odpowiada na port 3000
.
Wiele portów
W poniższym kodzie aplikacja odpowiada na port 3000
i 4000
.
var app = WebApplication.Create(args);
app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");
app.MapGet("/", () => "Hello World");
app.Run();
Ustawianie portu z wiersza polecenia
Następujące polecenie powoduje, że aplikacja odpowiada na port 7777
:
dotnet run --urls="https://localhost:7777"
Kestrel Jeśli punkt końcowy jest również skonfigurowany w appsettings.json
pliku, appsettings.json
używany jest określony adres URL. Aby uzyskać więcej informacji, zobacz Kestrel Konfiguracja punktu końcowego
Odczytywanie portu ze środowiska
Poniższy kod odczytuje port ze środowiska:
var app = WebApplication.Create(args);
var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";
app.MapGet("/", () => "Hello World");
app.Run($"http://localhost:{port}");
Preferowanym sposobem ustawienia portu ze środowiska jest użycie ASPNETCORE_URLS
zmiennej środowiskowej, która jest pokazana w poniższej sekcji.
Ustawianie portów za pomocą zmiennej środowiskowej ASPNETCORE_URLS
Zmienna ASPNETCORE_URLS
środowiskowa jest dostępna do ustawienia portu:
ASPNETCORE_URLS=http://localhost:3000
ASPNETCORE_URLS
obsługuje wiele adresów URL:
ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000
Nasłuchiwanie we wszystkich interfejsach
W poniższych przykładach pokazano nasłuchiwanie we wszystkich interfejsach
http://*:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://*:3000");
app.MapGet("/", () => "Hello World");
app.Run();
http://+:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://+:3000");
app.MapGet("/", () => "Hello World");
app.Run();
http://0.0.0.0:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://0.0.0.0:3000");
app.MapGet("/", () => "Hello World");
app.Run();
Nasłuchiwanie we wszystkich interfejsach przy użyciu ASPNETCORE_URLS
Powyższe przykłady mogą być używane ASPNETCORE_URLS
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
Określanie protokołu HTTPS przy użyciu certyfikatu programistycznego
var app = WebApplication.Create(args);
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
Aby uzyskać więcej informacji na temat certyfikatu programistycznego, zobacz Trust the ASP.NET Core HTTPS development certificate on Windows and macOS (Ufaj certyfikatowi programistycznemu ASP.NET Core HTTPS w systemach Windows i macOS).
Określanie protokołu HTTPS przy użyciu certyfikatu niestandardowego
W poniższych sekcjach pokazano, jak określić certyfikat niestandardowy przy użyciu appsettings.json
pliku i za pośrednictwem konfiguracji.
Określanie certyfikatu niestandardowego za pomocą polecenia appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Certificates": {
"Default": {
"Path": "cert.pem",
"KeyPath": "key.pem"
}
}
}
}
Określanie certyfikatu niestandardowego za pomocą konfiguracji
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();
Korzystanie z interfejsów API certyfikatów
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();
Odczytywanie środowiska
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();
Aby uzyskać więcej informacji na temat korzystania ze środowiska, zobacz Use multiple environments in ASP.NET Core (Używanie wielu środowisk w środowisku ASP.NET Core)
Konfigurowanie
Poniższy kod odczytuje z systemu konfiguracji:
var app = WebApplication.Create(args);
var message = app.Configuration["HelloKey"] ?? "Hello";
app.MapGet("/", () => message);
app.Run();
Aby uzyskać więcej informacji, zobacz Configuration in ASP.NET Core (Konfiguracja w programie ASP.NET Core)
Rejestrowanie
Poniższy kod zapisuje komunikat podczas uruchamiania aplikacji logowania:
var app = WebApplication.Create(args);
app.Logger.LogInformation("The app started");
app.MapGet("/", () => "Hello World");
app.Run();
Aby uzyskać więcej informacji, zobacz Rejestrowanie na platformie .NET Core i ASP.NET Core
Uzyskiwanie dostępu do kontenera wstrzykiwania zależności (DI)
Poniższy kod pokazuje, jak pobrać usługi z kontenera DI podczas uruchamiania aplikacji:
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();
Aby uzyskać więcej informacji, zobacz Wstrzykiwanie zależności na platformie ASP.NET Core.
WebApplicationBuilder
Ta sekcja zawiera przykładowy kod przy użyciu polecenia WebApplicationBuilder.
Zmienianie katalogu głównego zawartości, nazwy aplikacji i środowiska
Poniższy kod ustawia katalog główny zawartości, nazwę aplikacji i środowisko:
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 inicjuje nowe wystąpienie klasy WebApplicationBuilder ze wstępnie skonfigurowanymi wartościami domyślnymi.
Aby uzyskać więcej informacji, zobacz omówienie podstaw platformy ASP.NET Core
Zmienianie katalogu głównego zawartości, nazwy aplikacji i środowiska według zmiennych środowiskowych lub wiersza polecenia
W poniższej tabeli przedstawiono zmienną środowiskową i argument wiersza polecenia używany do zmiany katalogu głównego zawartości, nazwy aplikacji i środowiska:
funkcja | Zmienna środowiskowa | Argument wiersza polecenia |
---|---|---|
Nazwa aplikacji | ASPNETCORE_APPLICATIONNAME | --applicationName |
Nazwa środowiska | ASPNETCORE_ENVIRONMENT | --środowisko |
Katalog główny zawartości | ASPNETCORE_CONTENTROOT | --contentRoot |
Dodawanie dostawców konfiguracji
Poniższy przykład dodaje dostawcę konfiguracji INI:
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddIniFile("appsettings.ini");
var app = builder.Build();
Aby uzyskać szczegółowe informacje, zobacz Dostawcy konfiguracji plików w konfiguracji w programie ASP.NET Core.
Konfiguracja odczytu
Domyślnie WebApplicationBuilder konfiguracja odczytu z wielu źródeł, w tym:
appSettings.json
iappSettings.{environment}.json
- Zmienne środowiskowe
- Wiersz polecenia
Aby uzyskać pełną listę źródeł konfiguracji, zobacz Konfiguracja domyślna w konfiguracji w programie ASP.NET Core
Poniższy kod odczytuje HelloKey
z konfiguracji i wyświetla wartość w punkcie /
końcowym. Jeśli wartość konfiguracji ma wartość null, "Hello" zostanie przypisana do elementu message
:
var builder = WebApplication.CreateBuilder(args);
var message = builder.Configuration["HelloKey"] ?? "Hello";
var app = builder.Build();
app.MapGet("/", () => message);
app.Run();
Odczytywanie środowiska
var builder = WebApplication.CreateBuilder(args);
var message = builder.Configuration["HelloKey"] ?? "Hello";
var app = builder.Build();
app.MapGet("/", () => message);
app.Run();
Dodawanie dostawców rejestrowania
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();
Dodawanie usług
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();
Dostosowywanie elementu IHostBuilder
Dostęp do istniejących metod rozszerzeń IHostBuilder można uzyskać przy użyciu właściwości Host:
var builder = WebApplication.CreateBuilder(args);
// Wait 30 seconds for graceful shutdown.
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Dostosowywanie obiektu IWebHostBuilder
Dostęp do metod rozszerzeń IWebHostBuilder można uzyskać przy użyciu właściwości WebApplicationBuilder.WebHost .
var builder = WebApplication.CreateBuilder(args);
// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();
var app = builder.Build();
app.MapGet("/", () => "Hello HTTP.sys");
app.Run();
Zmienianie katalogu głównego sieci Web
Domyślnie katalog główny sieci Web jest powiązany z katalogem głównym zawartości w folderze wwwroot
. Katalog główny sieci Web to miejsce, w którym oprogramowanie pośredniczące plików statycznych szuka plików statycznych. Katalog główny sieci Web można zmienić za pomocą WebHostOptions
polecenia , wiersza polecenia lub UseWebRoot metody :
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
// Look for static files in webroot
WebRootPath = "webroot"
});
var app = builder.Build();
app.Run();
Niestandardowy kontener wstrzykiwania zależności (DI)
W poniższym przykładzie użyto funkcji Autofac:
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));
var app = builder.Build();
Dodawanie oprogramowania pośredniczącego
W programie WebApplication
można skonfigurować dowolne istniejące oprogramowanie pośredniczące ASP.NET Core:
var app = WebApplication.Create(args);
// Setup the file server to serve static files.
app.UseFileServer();
app.MapGet("/", () => "Hello World!");
app.Run();
Aby uzyskać więcej informacji, zobacz ASP.NET Core Middleware
Strona wyjątku dla deweloperów
WebApplication.CreateBuilder Inicjuje nowe wystąpienie WebApplicationBuilder klasy ze wstępnie skonfigurowanymi wartościami domyślnymi. Strona wyjątku dewelopera jest włączona w wstępnie skonfigurowanych wartościach domyślnych. Po uruchomieniu następującego kodu w środowisku deweloperskim przejście do /
strony renderuje przyjazną stronę, która pokazuje wyjątek.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>
{
throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});
app.Run();
Oprogramowanie pośredniczące platformy ASP.NET Core
W poniższej tabeli wymieniono niektóre oprogramowanie pośredniczące często używane z minimalnymi interfejsami API.
Oprogramowanie pośredniczące | opis | interfejs API |
---|---|---|
Authentication | Zapewnia obsługę uwierzytelniania. | UseAuthentication |
Autoryzacja | Zapewnia obsługę autoryzacji. | UseAuthorization |
CORS | Konfiguruje współużytkowanie zasobów między źródłami. | UseCors |
Procedura obsługi wyjątków | Globalnie obsługuje wyjątki zgłaszane przez potok oprogramowania pośredniczącego. | UseExceptionHandler |
Przekazane nagłówki | Przekazuje nagłówki przesłane przez serwer proxy do bieżącego żądania. | UseForwardedHeaders |
Przekierowywanie HTTPS | Przekierowuje wszystkie żądania HTTP do protokołu HTTPS. | UseHttpsRedirection |
HTTP Strict Transport Security (HSTS) | Oprogramowanie pośredniczące rozszerzenia zabezpieczeń, które dodaje specjalny nagłówek odpowiedzi. | UseHsts |
Rejestrowanie żądań | Zapewnia obsługę rejestrowania żądań HTTP i odpowiedzi. | UseHttpLogging |
Rejestrowanie żądań W3C | Zapewnia obsługę rejestrowania żądań HTTP i odpowiedzi w formacie W3C. | UseW3CLogging |
Buforowanie odpowiedzi | Zapewnia obsługę buforowania odpowiedzi. | UseResponseCaching |
Kompresja odpowiedzi | Zapewnia obsługę kompresowania odpowiedzi. | UseResponseCompression |
Sesja | Zapewnia obsługę zarządzania sesjami użytkowników. | UseSession |
Pliki statyczne | Zapewnia obsługę plików statycznych i przeglądania katalogów. | UseStaticFiles, UseFileServer |
Obiekty WebSocket | Włącza protokoły WebSocket. | UseWebSockets |
Obsługa żądań
W poniższych sekcjach omówiono routing, powiązanie parametrów i odpowiedzi.
Routing
Skonfigurowana WebApplication
obsługa i Map{Verb}
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();
Programy obsługi tras
Programy obsługi tras to metody, które są wykonywane, gdy trasa jest zgodna. Programy obsługi tras mogą być funkcją dowolnego kształtu, w tym synchroniczną lub asynchroniczną. Programy obsługi tras mogą być wyrażeniem lambda, funkcją lokalną, metodą wystąpienia lub metodą statyczną.
Wyrażenie lambda
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/inline", () => "This is an inline lambda");
var handler = () => "This is a lambda variable";
app.MapGet("/", handler);
app.Run();
Funkcja lokalna
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
string LocalFunction() => "This is local function";
app.MapGet("/", LocalFunction);
app.Run();
Metoda wystąpienia
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";
}
}
Metoda statyczna
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";
}
}
Nazwane punkty końcowe i generowanie linków
Punkty końcowe mogą mieć nazwy w celu wygenerowania adresów URL do punktu końcowego. Użycie nazwanego punktu końcowego pozwala uniknąć konieczności stosowania twardych ścieżek kodu w aplikacji:
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();
Powyższy kod jest wyświetlany The link to the hello endpoint is /hello
z punktu końcowego /
.
UWAGA: W nazwach punktów końcowych jest rozróżniana wielkość liter.
Nazwy punktów końcowych:
- Musi ona być unikatowa w skali globalnej.
- Są używane jako identyfikator operacji interfejsu OpenAPI, gdy jest włączona obsługa interfejsu OpenAPI. Aby uzyskać więcej informacji, zobacz OpenAPI.
Parametry trasy
Parametry trasy można przechwycić w ramach definicji wzorca trasy:
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();
Powyższy kod zwraca The user id is 3 and book id is 7
z identyfikatora URI /users/3/books/7
.
Procedura obsługi tras może zadeklarować parametry do przechwycenia. Gdy żądanie jest kierowane z parametrami zadeklarowanymi do przechwycenia, parametry są analizowane i przekazywane do procedury obsługi. Ułatwia to przechwytywanie wartości w bezpieczny sposób typu. W poprzednim kodzie userId
i bookId
mają wartość int
.
W poprzednim kodzie, jeśli nie można przekonwertować żadnej wartości trasy na int
wartość , zgłaszany jest wyjątek. Żądanie /users/hello/books/3
GET zgłasza następujący wyjątek:
BadHttpRequestException: Failed to bind parameter "int userId" from "hello".
Symbol wieloznaczny i przechwyć wszystkie trasy
Następujące przechwycenie wszystkich tras zwracanych Routing to hello
z punktu końcowego "/posts/hello":
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");
app.Run();
Ograniczenia trasy
Ograniczenia trasy ograniczają zgodne zachowanie trasy.
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();
W poniższej tabeli przedstawiono powyższe szablony tras i ich zachowanie:
Szablon trasy | Przykładowy pasujący identyfikator URI |
---|---|
/todos/{id:int} |
/todos/1 |
/todos/{text} |
/todos/something |
/posts/{slug:regex(^[a-z0-9_-]+$)} |
/posts/mypost |
Aby uzyskać więcej informacji, zobacz Route constraint reference in Routing in ASP.NET Core (Dokumentacja ograniczeń tras w usłudze Routing w usłudze ASP.NET Core).
Powiązanie parametrów
Powiązanie parametrów to proces konwertowania danych żądania na silnie typizowane parametry, które są wyrażane przez programy obsługi tras. Źródło powiązania określa, skąd są powiązane parametry. Źródła powiązań mogą być jawne lub wnioskowane na podstawie metody HTTP i typu parametru.
Obsługiwane źródła powiązań:
- Wartości tras
- Ciąg zapytania
- Nagłówek
- Treść (jako kod JSON)
- Usługi udostępniane przez wstrzykiwanie zależności
- Niestandardowy
Uwaga
Powiązanie z wartości formularza nie jest natywnie obsługiwane na platformie .NET.
W poniższym przykładzie procedura obsługi tras GET używa niektórych z tych źródeł powiązań parametrów:
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 { }
W poniższej tabeli przedstawiono relację między parametrami używanymi w poprzednim przykładzie i skojarzonymi źródłami powiązań.
Parametr | Źródło powiązania |
---|---|
id |
wartość trasy |
page |
ciąg zapytania |
customHeader |
nagłówek |
service |
Udostępniane przez wstrzykiwanie zależności |
Metody GET
HTTP , HEAD
, OPTIONS
i DELETE
nie są niejawnie powiązane z treścią. Aby powiązać z treścią (jako kod JSON) dla tych metod HTTP, powiąż jawnie z elementem [FromBody]
lub odczyt z pliku HttpRequest.
W poniższym przykładzie procedura obsługi tras POST używa powiązania źródła treści (jako kodu JSON) dla parametru person
:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/", (Person person) => { });
record Person(string Name, int Age);
Parametry w poprzednich przykładach są automatycznie powiązane z danymi żądania. Aby zademonstrować wygodę zapewnianą przez powiązanie parametrów, następujące przykładowe programy obsługi tras pokazują, jak odczytywać dane żądania bezpośrednio z żądania:
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>();
// ...
});
Jawne powiązanie parametrów
Atrybuty mogą służyć do jawnego deklarowania, gdzie parametry są powiązane.
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);
Parametr | Źródło powiązania |
---|---|
id |
wartość trasy o nazwie id |
page |
ciąg zapytania o nazwie "p" |
service |
Udostępniane przez wstrzykiwanie zależności |
contentType |
nagłówek o nazwie "Content-Type" |
Uwaga
Powiązanie z wartości formularza nie jest natywnie obsługiwane na platformie .NET.
Powiązanie parametrów z di
Powiązanie parametrów dla minimalnych interfejsów API wiąże parametry za pośrednictwem wstrzykiwania zależności, gdy typ jest skonfigurowany jako usługa. Nie jest konieczne jawne zastosowanie atrybutu [FromServices]
do parametru. W poniższym kodzie obie akcje zwracają czas:
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();
Parametry opcjonalne
Parametry zadeklarowane w programach obsługi tras są traktowane zgodnie z wymaganiami:
- Jeśli żądanie pasuje do trasy, procedura obsługi tras jest uruchamiana tylko wtedy, gdy wszystkie wymagane parametry są podane w żądaniu.
- Niepowodzenie podania wszystkich wymaganych parametrów powoduje wystąpienie błędu.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");
app.Run();
Identyfikator URI | result |
---|---|
/products?pageNumber=3 |
3 zwrócone |
/products |
BadHttpRequestException : Wymagany parametr "int pageNumber" nie został podany z ciągu zapytania. |
/products/1 |
Błąd HTTP 404, brak pasującej trasy |
Aby ustawić pageNumber
wartość opcjonalną, zdefiniuj typ jako opcjonalny lub podaj wartość domyślną:
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();
Identyfikator URI | result |
---|---|
/products?pageNumber=3 |
3 zwrócone |
/products |
1 zwrócone |
/products2 |
1 zwrócone |
Poprzednia wartość dopuszczająca wartość null i domyślna ma zastosowanie do wszystkich źródeł:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/products", (Product? product) => { });
app.Run();
Powyższy kod wywołuje metodę z produktem o wartości null, jeśli nie zostanie wysłana żadna treść żądania.
UWAGA: Jeśli podano nieprawidłowe dane i parametr ma wartość null, procedura obsługi tras nie jest uruchamiana.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");
app.Run();
Identyfikator URI | result |
---|---|
/products?pageNumber=3 |
3 Zwracane |
/products |
1 Zwracane |
/products?pageNumber=two |
BadHttpRequestException : Nie można powiązać parametru "Nullable<int> pageNumber" z "dwóch". |
/products/two |
Błąd HTTP 404, brak pasującej trasy |
Aby uzyskać więcej informacji, zobacz sekcję Błędy powiązań.
Typy specjalne
Następujące typy są powiązane bez jawnych atrybutów:
HttpContext: kontekst zawierający wszystkie informacje o bieżącym żądaniu HTTP lub odpowiedzi:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
HttpRequest i HttpResponse: Żądanie HTTP i odpowiedź HTTP:
app.MapGet("/", (HttpRequest request, HttpResponse response) => response.WriteAsync($"Hello World {request.Query["name"]}"));
CancellationToken: token anulowania skojarzony z bieżącym żądaniem HTTP:
app.MapGet("/", async (CancellationToken cancellationToken) => await MakeLongRunningRequestAsync(cancellationToken));
ClaimsPrincipal: użytkownik skojarzony z żądaniem powiązany z elementem HttpContext.User:
app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
Wiązanie niestandardowe
Istnieją dwa sposoby dostosowywania powiązania parametrów:
- W przypadku źródeł tras, zapytań i powiązań nagłówków powiąż typy niestandardowe, dodając metodę statyczną
TryParse
dla typu. - Kontrolowanie procesu wiązania przez zaimplementowanie
BindAsync
metody dla typu.
TryParse
TryParse
ma dwa interfejsy API:
public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);
Poniższy kod jest wyświetlany Point: 12.3, 10.1
za pomocą identyfikatora URI /map?Point=12.3,10.1
:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// GET /map?Point=12.3,10.1
app.MapGet("/map", (Point point) => $"Point: {point.X}, {point.Y}");
app.Run();
public class Point
{
public double X { get; set; }
public double Y { get; set; }
public static bool TryParse(string? value, IFormatProvider? provider,
out Point? point)
{
// Format is "(12.3,10.1)"
var trimmedValue = value?.TrimStart('(').TrimEnd(')');
var segments = trimmedValue?.Split(',',
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (segments?.Length == 2
&& double.TryParse(segments[0], out var x)
&& double.TryParse(segments[1], out var y))
{
point = new Point { X = x, Y = y };
return true;
}
point = null;
return false;
}
}
BindAsync
BindAsync
ma następujące interfejsy API:
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
Poniższy kod jest wyświetlany SortBy:xyz, SortDirection:Desc, CurrentPage:99
za pomocą identyfikatora URI /products?SortBy=xyz&SortDir=Desc&Page=99
:
using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// GET /products?SortBy=xyz&SortDir=Desc&Page=99
app.MapGet("/products", (PagingData pageData) => $"SortBy:{pageData.SortBy}, " +
$"SortDirection:{pageData.SortDirection}, CurrentPage:{pageData.CurrentPage}");
app.Run();
public class PagingData
{
public string? SortBy { get; init; }
public SortDirection SortDirection { get; init; }
public int CurrentPage { get; init; } = 1;
public static ValueTask<PagingData?> BindAsync(HttpContext context,
ParameterInfo parameter)
{
const string sortByKey = "sortBy";
const string sortDirectionKey = "sortDir";
const string currentPageKey = "page";
Enum.TryParse<SortDirection>(context.Request.Query[sortDirectionKey],
ignoreCase: true, out var sortDirection);
int.TryParse(context.Request.Query[currentPageKey], out var page);
page = page == 0 ? 1 : page;
var result = new PagingData
{
SortBy = context.Request.Query[sortByKey],
SortDirection = sortDirection,
CurrentPage = page
};
return ValueTask.FromResult<PagingData?>(result);
}
}
public enum SortDirection
{
Default,
Asc,
Desc
}
Błędy powiązań
Gdy powiązanie zakończy się niepowodzeniem, platforma rejestruje komunikat debugowania i zwraca różne kody stanu do klienta w zależności od trybu awarii.
Tryb awarii | Typ parametru dopuszczalnego do wartości null | Źródło powiązania | Kod stanu |
---|---|---|---|
{ParameterType}.TryParse Zwraca false |
tak | route/query/header | 400 |
{ParameterType}.BindAsync Zwraca null |
tak | niestandardowe | 400 |
{ParameterType}.BindAsync Zgłasza |
nie ma znaczenia | niestandardowe | 500 |
Nie można wykonać deserializacji treści JSON | nie ma znaczenia | treść | 400 |
Nieprawidłowy typ zawartości (nie application/json ) |
nie ma znaczenia | treść | 415 |
Pierwszeństwo powiązania
Reguły określania źródła powiązania z parametru:
- Jawny atrybut zdefiniowany w parametrze (atrybuty From*) w następującej kolejności:
- Wartości tras:
[FromRoute]
- Ciąg zapytania:
[FromQuery]
- Nagłówek:
[FromHeader]
- Ciało:
[FromBody]
- Usługa:
[FromServices]
- Wartości tras:
- Typy specjalne
- Typ parametru ma prawidłową
BindAsync
metodę. - Typ parametru jest ciągiem lub ma prawidłową
TryParse
metodę.- Jeśli nazwa parametru istnieje w szablonie trasy. W elemecie
app.Map("/todo/{id}", (int id) => {});
id
jest powiązana z trasą. - Powiązana z ciągu zapytania.
- Jeśli nazwa parametru istnieje w szablonie trasy. W elemecie
- Jeśli typ parametru jest usługą dostarczaną przez iniekcję zależności, używa tej usługi jako źródła.
- Parametr pochodzi z treści.
Dostosowywanie powiązania JSON
Źródło powiązania treści używa System.Text.Json do dese serializacji. Nie można zmienić tego ustawienia domyślnego, ale powiązanie można dostosować przy użyciu innych technik opisanych wcześniej. Aby dostosować opcje serializatora JSON, użyj kodu podobnego do następującego:
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;
}
Powyższy kod ma następujące działanie:
- Konfiguruje domyślne opcje JSON danych wejściowych i wyjściowych.
- Zwraca następujący kod JSON
Podczas publikowania{ "id": 1, "name": "Joe Smith" }
{ "Id": 1, "Name": "Joe Smith" }
Odczytywanie treści żądania
Odczytywanie treści żądania bezpośrednio przy użyciu parametru HttpContext lub 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();
Powyższy kod ma następujące działanie:
- Uzyskuje dostęp do treści żądania przy użyciu polecenia HttpRequest.BodyReader.
- Kopiuje treść żądania do pliku lokalnego.
Odpowiedzi
Programy obsługi tras obsługują następujące typy zwracanych wartości:
IResult
oparte — obejmujeTask<IResult>
to iValueTask<IResult>
string
- Obejmuje toTask<string>
iValueTask<string>
T
(Dowolny inny typ) — obejmujeTask<T>
to iValueTask<T>
Wartość zwracana | Zachowanie | Typ zawartości |
---|---|---|
IResult |
Struktura wywołuje metodę IResult.ExecuteAsync | Decyzja o wdrożeniu IResult |
string |
Struktura zapisuje ciąg bezpośrednio w odpowiedzi | text/plain |
T (Dowolny inny typ) |
Struktura serializuje odpowiedź w formacie JSON | application/json |
Przykładowe wartości zwracane
ciąg zwracane wartości
app.MapGet("/hello", () => "Hello World");
Wartości zwracane w formacie JSON
app.MapGet("/hello", () => new { Message = "Hello World" });
Zwracane wartości IResult
app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));
W poniższym przykładzie użyto wbudowanych typów wyników, aby dostosować odpowiedź:
app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound())
.Produces<Todo>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);
JSON
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
Niestandardowy kod stanu
app.MapGet("/405", () => Results.StatusCode(405));
Text
app.MapGet("/text", () => Results.Text("This is some text"));
Stream
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () =>
{
var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
// Proxy the response as JSON
return Results.Stream(stream, "application/json");
});
app.Run();
Przekierowanie
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
Plik
app.MapGet("/download", () => Results.File("myfile.text"));
Wbudowane wyniki
Typowe pomocniki wyników istnieją w klasie statycznej Microsoft.AspNetCore.Http.Results
.
opis | Typ odpowiedzi | Kod stanu | interfejs API |
---|---|---|---|
Pisanie odpowiedzi JSON przy użyciu opcji zaawansowanych | application/json | 200 | Results.Json |
Pisanie odpowiedzi JSON | application/json | 200 | Wyniki.OK |
Pisanie odpowiedzi tekstowej | tekst/zwykły (domyślny), konfigurowalny | 200 | Results.Text |
Zapisywanie odpowiedzi jako bajtów | application/octet-stream (ustawienie domyślne), konfigurowalne | 200 | Results.Bytes |
Zapisywanie strumienia bajtów w odpowiedzi | application/octet-stream (ustawienie domyślne), konfigurowalne | 200 | Results.Stream |
Przesyłanie strumieniowe pliku do odpowiedzi do pobrania za pomocą nagłówka content-disposition | application/octet-stream (ustawienie domyślne), konfigurowalne | 200 | Results.File |
Ustaw kod stanu na 404 z opcjonalną odpowiedzią JSON | Nie dotyczy | 404 | Results.NotFound |
Ustaw kod stanu na 204 | Nie dotyczy | 204 | Results.NoContent |
Ustaw kod stanu na 422 z opcjonalną odpowiedzią JSON | Nie dotyczy | 422 | Results.UnprocessableEntity |
Ustaw kod stanu na 400 z opcjonalną odpowiedzią JSON | Nie dotyczy | 400 | Results.BadRequest |
Ustaw kod stanu na 409 z opcjonalną odpowiedzią JSON | Nie dotyczy | 409 | Results.Conflict |
Pisanie obiektu JSON ze szczegółami problemu w odpowiedzi | Nie dotyczy | 500 (ustawienie domyślne), konfigurowalne | Results.Problem |
Pisanie obiektu JSON ze szczegółami problemu w odpowiedzi z błędami walidacji | Nie dotyczy | Nie dotyczy, można skonfigurować | Results.ValidationProblem |
Dostosowywanie wyników
Aplikacje mogą kontrolować odpowiedzi, implementując typ niestandardowy IResult . Poniższy kod jest przykładem typu wyniku HTML:
using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
public static IResult Html(this IResultExtensions resultExtensions, string html)
{
ArgumentNullException.ThrowIfNull(resultExtensions);
return new HtmlResult(html);
}
}
class HtmlResult : IResult
{
private readonly string _html;
public HtmlResult(string html)
{
_html = html;
}
public Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
return httpContext.Response.WriteAsync(_html);
}
}
Zalecamy dodanie metody rozszerzenia w celu Microsoft.AspNetCore.Http.IResultExtensions zwiększenia możliwości odnajdywania tych niestandardowych wyników.
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();
Autoryzacja
Trasy mogą być chronione przy użyciu zasad autoryzacji. Można je zadeklarować za pomocą atrybutu [Authorize]
lub przy użyciu RequireAuthorization metody :
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();
Powyższy kod można napisać za pomocą RequireAuthorizationpolecenia :
app.MapGet("/auth", () => "This endpoint requires authorization")
.RequireAuthorization();
W poniższym przykładzie użyto autoryzacji opartej na zasadach:
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();
Zezwalanie nieuwierzytelnionym użytkownikom na dostęp do punktu końcowego
Ustawienie [AllowAnonymous]
umożliwia nieuwierzytelnionym użytkownikom dostęp do punktów końcowych:
app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");
app.MapGet("/login2", () => "This endpoint also for all roles.")
.AllowAnonymous();
CORS
Trasy mogą być włączone przez mechanizm CORS przy użyciu zasad MECHANIZMU CORS. Mechanizm CORS można zadeklarować za pomocą atrybutu [EnableCors]
lub przy użyciu RequireCors metody . Następujące przykłady umożliwiają mechanizm CORS:
const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
var app = builder.Build();
app.UseCors();
app.MapGet("/",() => "Hello CORS!");
app.Run();
using Microsoft.AspNetCore.Cors;
const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
var app = builder.Build();
app.UseCors();
app.MapGet("/cors", [EnableCors(MyAllowSpecificOrigins)] () =>
"This endpoint allows cross origin requests!");
app.MapGet("/cors2", () => "This endpoint allows cross origin requests!")
.RequireCors(MyAllowSpecificOrigins);
app.Run();
Aby uzyskać więcej informacji, zobacz Włączanie żądań między źródłami (CORS) w usłudze ASP.NET Core