Udostępnij za pośrednictwem


Krótkie informacje o minimalnych interfejsach API

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.

Ostrzeżenie

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.

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ą, zobacz wersję tego artykułu platformy .NET 9.

Ten dokument:

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łany UseRouting i jeśli istnieją skonfigurowane punkty końcowe, na przykład app.MapGet.
  • UseEndpoints Jest dodawany na końcu potoku oprogramowania pośredniczącego, jeśli są skonfigurowane jakiekolwiek punkty końcowe.
  • UseAuthentication jest dodawany natychmiast po UseRouting tym, jak kod użytkownika nie został jeszcze wywołany UseAuthentication i czy IAuthenticationSchemeProvider można go wykryć u dostawcy usług. IAuthenticationSchemeProvider program jest domyślnie dodawany podczas korzystania z usług AddAuthentication, a usługa jest wykrywana przy użyciu polecenia IServiceProviderIsService.
  • UseAuthorization Zostanie dodany dalej, jeśli kod użytkownika nie został jeszcze wywołany UseAuthorization i czy IAuthorizationHandlerProvider można go wykryć u dostawcy usług. IAuthorizationHandlerProvider program jest domyślnie dodawany podczas korzystania z usług AddAuthorization, a usługa jest wykrywana przy użyciu polecenia IServiceProviderIsService.
  • Oprogramowanie pośredniczące skonfigurowane przez użytkownika i punkty końcowe są dodawane między elementami UseRouting i UseEndpoints.

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 i UseEndpoints 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 newProperties/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.jsonelemecie , 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 i appSettings.{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ą WebHostOptionspolecenia , 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 WebApplicationmoż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. , PostPut 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.

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 intwartość , 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 GETHTTP , HEAD, OPTIONSi 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 co HttpRequest.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 i PipeReader 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:

  • Kolekcje, na przykład Lista i Słownik
  • Typy złożone, na przykład lub TodoProject

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ści false. isCompleted Jeśli pole wyboru jest zaznaczone podczas przesyłania formularza, obie wartości true i false 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 powoduje true zaznaczenie pól wyboru zaznaczonego i false 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:

  1. W przypadku źródeł tras, zapytań i powiązań nagłówków powiąż typy niestandardowe, dodając metodę statyczną TryParse dla typu.
  2. 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:

  1. Jawny atrybut zdefiniowany w parametrze (atrybuty From*) w następującej kolejności:
    1. Wartości tras: [FromRoute]
    2. Ciąg zapytania: [FromQuery]
    3. Nagłówek: [FromHeader]
    4. Ciało: [FromBody]
    5. Formularz: [FromForm]
    6. Usługa: [FromServices]
    7. Wartości parametrów: [AsParameters]
  2. Typy specjalne
    1. HttpContext
    2. HttpRequest (HttpContext.Request)
    3. HttpResponse (HttpContext.Response)
    4. ClaimsPrincipal (HttpContext.User)
    5. CancellationToken (HttpContext.RequestAborted)
    6. IFormCollection (HttpContext.Request.Form)
    7. IFormFileCollection (HttpContext.Request.Form.Files)
    8. IFormFile (HttpContext.Request.Form.Files[paramName])
    9. Stream (HttpContext.Request.Body)
    10. PipeReader (HttpContext.Request.BodyReader)
  3. Typ parametru ma prawidłową metodę statyczną BindAsync .
  4. Typ parametru jest ciągiem lub ma prawidłową metodę statyczną TryParse .
    1. Jeśli nazwa parametru istnieje na przykład w szablonie trasy, app.Map("/todo/{id}", (int id) => {});jest ona powiązana z trasą.
    2. Powiązana z ciągu zapytania.
  5. Jeśli typ parametru jest usługą dostarczaną przez iniekcję zależności, używa tej usługi jako źródła.
  6. 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ą NameFieldwartość .

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:

  1. IResult oparte — obejmuje Task<IResult> to i ValueTask<IResult>
  2. string - Obejmuje to Task<string> i ValueTask<string>
  3. T (Dowolny inny typ) — obejmuje Task<T> to i ValueTask<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 Resultswartoś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ż

Ten dokument:

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łany UseRouting i jeśli istnieją skonfigurowane punkty końcowe, na przykład app.MapGet.
  • UseEndpoints Jest dodawany na końcu potoku oprogramowania pośredniczącego, jeśli są skonfigurowane jakiekolwiek punkty końcowe.
  • UseAuthentication jest dodawany natychmiast po UseRouting tym, jak kod użytkownika nie został jeszcze wywołany UseAuthentication i czy IAuthenticationSchemeProvider można go wykryć u dostawcy usług. IAuthenticationSchemeProvider program jest domyślnie dodawany podczas korzystania z usług AddAuthentication, a usługa jest wykrywana przy użyciu polecenia IServiceProviderIsService.
  • UseAuthorization Zostanie dodany dalej, jeśli kod użytkownika nie został jeszcze wywołany UseAuthorization i czy IAuthorizationHandlerProvider można go wykryć u dostawcy usług. IAuthorizationHandlerProvider program jest domyślnie dodawany podczas korzystania z usług AddAuthorization, a usługa jest wykrywana przy użyciu polecenia IServiceProviderIsService.
  • Oprogramowanie pośredniczące skonfigurowane przez użytkownika i punkty końcowe są dodawane między elementami UseRouting i UseEndpoints.

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 i UseEndpoints 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 newProperties/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.jsonelemecie , 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 i appSettings.{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ą WebHostOptionspolecenia , 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 WebApplicationmoż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. , PostPut 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.

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 intwartość , 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 GETHTTP , HEAD, OPTIONSi 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 co HttpRequest.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 i PipeReader 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:

  1. W przypadku źródeł tras, zapytań i powiązań nagłówków powiąż typy niestandardowe, dodając metodę statyczną TryParse dla typu.
  2. 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:

  1. Jawny atrybut zdefiniowany w parametrze (atrybuty From*) w następującej kolejności:
    1. Wartości tras: [FromRoute]
    2. Ciąg zapytania: [FromQuery]
    3. Nagłówek: [FromHeader]
    4. Ciało: [FromBody]
    5. Usługa: [FromServices]
    6. Wartości parametrów: [AsParameters]
  2. Typy specjalne
    1. HttpContext
    2. HttpRequest (HttpContext.Request)
    3. HttpResponse (HttpContext.Response)
    4. ClaimsPrincipal (HttpContext.User)
    5. CancellationToken (HttpContext.RequestAborted)
    6. IFormFileCollection (HttpContext.Request.Form.Files)
    7. IFormFile (HttpContext.Request.Form.Files[paramName])
    8. Stream (HttpContext.Request.Body)
    9. PipeReader (HttpContext.Request.BodyReader)
  3. Typ parametru ma prawidłową metodę statyczną BindAsync .
  4. Typ parametru jest ciągiem lub ma prawidłową metodę statyczną TryParse .
    1. Jeśli nazwa parametru istnieje w szablonie trasy. W elemecie app.Map("/todo/{id}", (int id) => {});id jest powiązana z trasą.
    2. Powiązana z ciągu zapytania.
  5. Jeśli typ parametru jest usługą dostarczaną przez iniekcję zależności, używa tej usługi jako źródła.
  6. 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ą NameFieldwartość .

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:

  1. IResult oparte — obejmuje Task<IResult> to i ValueTask<IResult>
  2. string - Obejmuje to Task<string> i ValueTask<string>
  3. T (Dowolny inny typ) — obejmuje Task<T> to i ValueTask<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 Resultswartoś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:

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.

Praca z portami

Po utworzeniu aplikacji internetowej za pomocą programu Visual Studio lub dotnet newProperties/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 i appSettings.{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ą WebHostOptionspolecenia , 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 WebApplicationmoż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";
    }
}

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 intwartość , 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 GETHTTP , HEAD, OPTIONSi 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:

  1. W przypadku źródeł tras, zapytań i powiązań nagłówków powiąż typy niestandardowe, dodając metodę statyczną TryParse dla typu.
  2. 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:

  1. Jawny atrybut zdefiniowany w parametrze (atrybuty From*) w następującej kolejności:
    1. Wartości tras: [FromRoute]
    2. Ciąg zapytania: [FromQuery]
    3. Nagłówek: [FromHeader]
    4. Ciało: [FromBody]
    5. Usługa: [FromServices]
  2. Typy specjalne
    1. HttpContext
    2. HttpRequest (HttpContext.Request)
    3. HttpResponse (HttpContext.Response)
    4. ClaimsPrincipal (HttpContext.User)
    5. CancellationToken (HttpContext.RequestAborted)
  3. Typ parametru ma prawidłową BindAsync metodę.
  4. Typ parametru jest ciągiem lub ma prawidłową TryParse metodę.
    1. Jeśli nazwa parametru istnieje w szablonie trasy. W elemecie app.Map("/todo/{id}", (int id) => {});id jest powiązana z trasą.
    2. Powiązana z ciągu zapytania.
  5. Jeśli typ parametru jest usługą dostarczaną przez iniekcję zależności, używa tej usługi jako źródła.
  6. 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
    {
      "id": 1,
      "name": "Joe Smith"
    }
    
    Podczas publikowania
    {
      "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:

  1. IResult oparte — obejmuje Task<IResult> to i ValueTask<IResult>
  2. string - Obejmuje to Task<string> i ValueTask<string>
  3. T (Dowolny inny typ) — obejmuje Task<T> to i ValueTask<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

Zobacz też

Obsługa interfejsu OpenAPI w minimalnych interfejsach API