Referensi cepat API minimal

Dokumen ini:

API minimal terdiri dari:

WebApplication

Kode berikut dihasilkan oleh templat ASP.NET Core:

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

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

app.Run();

Kode sebelumnya dapat dibuat melalui dotnet new web pada baris perintah atau memilih templat Web Kosong di Visual Studio.

Kode berikut membuat WebApplication (app) tanpa secara eksplisit membuat WebApplicationBuilder:

var app = WebApplication.Create(args);

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

app.Run();

WebApplication.Create menginisialisasi instans WebApplication baru kelas dengan default yang telah dikonfigurasi sebelumnya.

WebApplication secara otomatis menambahkan middleware berikut tergantung Minimal API applications pada kondisi tertentu:

  • UseDeveloperExceptionPage ditambahkan terlebih dahulu ketika HostingEnvironment adalah "Development".
  • UseRouting ditambahkan kedua jika kode pengguna belum memanggil UseRouting dan jika ada titik akhir yang dikonfigurasi, misalnya app.MapGet.
  • UseEndpoints ditambahkan di akhir alur middleware jika ada titik akhir yang dikonfigurasi.
  • UseAuthentication ditambahkan segera setelah UseRouting jika kode pengguna belum memanggil UseAuthentication dan apakah IAuthenticationSchemeProvider dapat terdeteksi di penyedia layanan. IAuthenticationSchemeProvider ditambahkan secara default saat menggunakan AddAuthentication, dan layanan terdeteksi menggunakan IServiceProviderIsService.
  • UseAuthorization ditambahkan berikutnya jika kode pengguna belum memanggil UseAuthorization dan apakah IAuthorizationHandlerProvider dapat terdeteksi di penyedia layanan. IAuthorizationHandlerProvider ditambahkan secara default saat menggunakan AddAuthorization, dan layanan terdeteksi menggunakan IServiceProviderIsService.
  • Middleware dan titik akhir yang dikonfigurasi pengguna ditambahkan antara UseRouting dan UseEndpoints.

Kode berikut secara efektif adalah apa yang ditambahkan middleware otomatis ke aplikasi:

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

Dalam beberapa kasus, konfigurasi middleware default tidak benar untuk aplikasi dan memerlukan modifikasi. Misalnya, UseCors harus dipanggil sebelum UseAuthentication dan UseAuthorization. Aplikasi perlu memanggil UseAuthentication dan UseAuthorization jika UseCors dipanggil:

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

Jika middleware harus dijalankan sebelum pencocokan rute terjadi, UseRouting harus dipanggil dan middleware harus ditempatkan sebelum panggilan ke UseRouting. UseEndpoints tidak diperlukan dalam kasus ini karena ditambahkan secara otomatis seperti yang dijelaskan sebelumnya:

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

app.UseRouting();

// other middleware and endpoints

Saat menambahkan middleware terminal:

  • Middleware harus ditambahkan setelah UseEndpoints.
  • Aplikasi perlu memanggil UseRouting dan UseEndpoints sehingga middleware terminal dapat ditempatkan di lokasi yang benar.
app.UseRouting();

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

app.UseEndpoints(e => {});

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

Middleware terminal adalah middleware yang berjalan jika tidak ada titik akhir yang menangani permintaan.

Bekerja dengan port

Saat aplikasi web dibuat dengan Visual Studio atau dotnet new, Properties/launchSettings.json file dibuat yang menentukan port yang ditanggapi aplikasi. Di sampel pengaturan port berikut, menjalankan aplikasi dari Visual Studio mengembalikan dialog Unable to connect to web server 'AppName'kesalahan . Visual Studio mengembalikan kesalahan karena mengharapkan port yang ditentukan dalam Properties/launchSettings.json, tetapi aplikasi menggunakan port yang ditentukan oleh app.Run("http://localhost:3000"). Jalankan sampel perubahan port berikut dari baris perintah.

Bagian berikut mengatur port yang direspons aplikasi.

var app = WebApplication.Create(args);

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

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

Dalam kode sebelumnya, aplikasi merespons port 3000.

Beberapa port

Dalam kode berikut, aplikasi merespons port 3000 dan 4000.

var app = WebApplication.Create(args);

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

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

app.Run();

Mengatur port dari baris perintah

Perintah berikut membuat aplikasi merespons port 7777:

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

Kestrel Jika titik akhir juga dikonfigurasi dalam appsettings.json file, appsettings.json URL yang ditentukan file akan digunakan. Untuk informasi selengkapnya, lihat Kestrel konfigurasi titik akhir

Membaca port dari lingkungan

Kode berikut membaca port dari lingkungan:

var app = WebApplication.Create(args);

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

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

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

Cara yang disukai untuk mengatur port dari lingkungan adalah dengan menggunakan ASPNETCORE_URLS variabel lingkungan, yang ditunjukkan di bagian berikut.

Mengatur port melalui variabel lingkungan ASPNETCORE_URLS

Variabel ASPNETCORE_URLS lingkungan tersedia untuk mengatur port:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS mendukung beberapa URL:

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

Dengarkan semua antarmuka

Sampel berikut menunjukkan mendengarkan di semua antarmuka

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

Dengarkan semua antarmuka menggunakan ASPNETCORE_URLS

Sampel sebelumnya dapat menggunakan ASPNETCORE_URLS

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

Dengarkan semua antarmuka menggunakan ASPNETCORE_HTTPS_PORTS

Sampel sebelumnya dapat menggunakan ASPNETCORE_HTTPS_PORTS dan ASPNETCORE_HTTP_PORTS.

ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000

Untuk informasi selengkapnya, lihat Mengonfigurasi titik akhir untuk server web ASP.NET Core Kestrel

Tentukan HTTPS dengan sertifikat pengembangan

var app = WebApplication.Create(args);

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

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

app.Run();

Untuk informasi selengkapnya tentang sertifikat pengembangan, lihat Mempercayai sertifikat pengembangan ASP.NET Core HTTPS di Windows dan macOS.

Tentukan HTTPS menggunakan sertifikat kustom

Bagian berikut menunjukkan cara menentukan sertifikat kustom menggunakan appsettings.json file dan melalui konfigurasi.

Tentukan sertifikat kustom dengan appsettings.json

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

Tentukan sertifikat kustom melalui konfigurasi

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

Menggunakan API sertifikat

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

Membaca lingkungan

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

Untuk informasi selengkapnya menggunakan lingkungan, lihat Menggunakan beberapa lingkungan di ASP.NET Core

Konfigurasi

Kode berikut membaca dari sistem konfigurasi:

var app = WebApplication.Create(args);

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

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

app.Run();

Untuk informasi selengkapnya, lihat Konfigurasi di ASP.NET Core

Pencatatan

Kode berikut menulis pesan ke startup aplikasi masuk:

var app = WebApplication.Create(args);

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

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

app.Run();

Untuk informasi selengkapnya, lihat Pengelogan di .NET Core dan ASP.NET Core

Mengakses kontainer Injeksi Dependensi (DI)

Kode berikut menunjukkan cara mendapatkan layanan dari kontainer DI selama pengaktifan aplikasi:


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

Kode berikut menunjukkan cara mengakses kunci dari kontainer DI menggunakan [FromKeyedServices] atribut :

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

Untuk informasi selengkapnya tentang DI, lihat Injeksi dependensi di ASP.NET Core.

WebApplicationBuilder

Bagian ini berisi kode sampel menggunakan WebApplicationBuilder.

Mengubah akar konten, nama aplikasi, dan lingkungan

Kode berikut mengatur akar konten, nama aplikasi, dan lingkungan:

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 menginisialisasi instans baru kelas WebApplicationBuilder dengan default yang telah dikonfigurasi sebelumnya.

Untuk informasi selengkapnya, lihat gambaran umum dasar-dasar ASP.NET Core

Mengubah akar konten, nama aplikasi, dan lingkungan dengan menggunakan variabel lingkungan atau baris perintah

Tabel berikut ini memperlihatkan variabel lingkungan dan argumen baris perintah yang digunakan untuk mengubah akar konten, nama aplikasi, dan lingkungan:

fitur Variabel lingkungan Argumen baris perintah
Nama aplikasi ASPNETCORE_APPLICATIONNAME --applicationName
Nama lingkungan ASPNETCORE_ENVIRONMENT --Lingkungan
Akar konten ASPNETCORE_CONTENTROOT --contentRoot

Menambahkan penyedia konfigurasi

Contoh berikut menambahkan penyedia konfigurasi INI:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

Untuk informasi terperinci, lihat Penyedia konfigurasi file di Konfigurasi di ASP.NET Core.

Membaca konfigurasi

Secara default WebApplicationBuilder , konfigurasi baca dari beberapa sumber, termasuk:

  • appSettings.json dan appSettings.{environment}.json
  • Variabel lingkungan
  • Baris perintah

Untuk daftar lengkap sumber konfigurasi yang dibaca, lihat Konfigurasi default di Konfigurasi di ASP.NET Core.

Kode berikut membaca HelloKey dari konfigurasi dan menampilkan nilai di / titik akhir. Jika nilai konfigurasi null, "Halo" ditetapkan ke message:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Membaca lingkungan

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Menambahkan penyedia pengelogan

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

Menambahkan layanan

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

Menyesuaikan IHostBuilder

Metode ekstensi yang ada di IHostBuilder dapat diakses menggunakan properti 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();

Menyesuaikan IWebHostBuilder

Metode ekstensi pada IWebHostBuilder dapat diakses menggunakan properti 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();

Mengubah akar web

Secara default, akar web relatif terhadap akar konten di wwwroot folder. Akar web adalah tempat middleware file statis mencari file statis. Akar web dapat diubah dengan WebHostOptions, baris perintah, atau dengan UseWebRoot metode :

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

var app = builder.Build();

app.Run();

Kontainer injeksi dependensi kustom (DI)

Contoh berikut menggunakan 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();

Tambahkan Middleware

Middleware ASP.NET Core yang ada dapat dikonfigurasi pada WebApplication:

var app = WebApplication.Create(args);

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

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

app.Run();

Untuk informasi selengkapnya, lihat ASP.NET Core Middleware

Halaman pengecualian pengembang

WebApplication.CreateBuilder menginisialisasi instans WebApplicationBuilder baru kelas dengan default yang telah dikonfigurasi sebelumnya. Halaman pengecualian pengembang diaktifkan dalam default yang telah dikonfigurasi sebelumnya. Saat kode berikut dijalankan di lingkungan pengembangan, navigasi untuk / merender halaman ramah yang menunjukkan pengecualian.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Run();

ASP.NET Core Middleware

Tabel berikut mencantumkan beberapa middleware yang sering digunakan dengan API minimal.

Middleware Deskripsi API
Autentikasi Menyediakan dukungan autentikasi. UseAuthentication
Otorisasi Menyediakan dukungan otorisasi. UseAuthorization
CORS Mengonfigurasi Cross-Origin Resource Sharing. UseCors
Handler Pengecualian Secara global menangani pengecualian yang dilemparkan oleh alur middleware. UseExceptionHandler
Header yang Diteruskan Meneruskan header yang diproksi ke permintaan saat ini. UseForwardedHeaders
Pengalihan HTTPS Mengalihkan semua permintaan HTTP ke HTTPS. UseHttpsRedirection
HTTP Strict Transport Security (HSTS) Middleware peningkatan keamanan yang menambahkan header respons khusus. UseHsts
Pengelogan Permintaan Menyediakan dukungan untuk mencatat permintaan dan respons HTTP. UseHttpLogging
Batas Waktu Permintaan Menyediakan dukungan untuk mengonfigurasi batas waktu permintaan, default global, dan per titik akhir. UseRequestTimeouts
Pengelogan Permintaan W3C Menyediakan dukungan untuk mencatat permintaan dan respons HTTP dalam format W3C. UseW3CLogging
Penembolokan Respons Menyediakan dukungan untuk respons penembolokan. UseResponseCaching
Pemadatan Respons Memberikan dukungan untuk memadatkan respons. UseResponseCompression
Sesi Menyediakan dukungan untuk mengelola sesi pengguna. UseSession
File Statis Menyediakan dukungan untuk menyajikan file statis dan penjelajahan direktori. UseStaticFiles, UseFileServer
WebSocket Mengaktifkan protokol WebSocket. UseWebSockets

Bagian berikut mencakup penanganan permintaan: perutean, pengikatan parameter, dan respons.

Perutean

Dukungan yang dikonfigurasi WebApplicationMap{Verb} dan MapMethods di mana {Verb} adalah metode HTTP camel-cased seperti Get, , PostPut atau 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();

Argumen Delegate yang diteruskan ke metode ini disebut "penangan rute".

Penangan Rute

Penangan rute adalah metode yang dijalankan saat rute cocok. Penangan rute dapat berupa ekspresi lambda, fungsi lokal, metode instans, atau metode statis. Penangan rute bisa sinkron atau asinkron.

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

Fungsi lokal

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

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

app.MapGet("/", LocalFunction);

app.Run();

Metode instans

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

Metode statis

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

Titik akhir yang ditentukan di luar Program.cs

API minimal tidak harus berada di 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" });
        });
    }
}

Lihat juga Merutekan grup nanti di artikel ini.

Titik akhir dapat diberikan nama untuk menghasilkan URL ke titik akhir. Menggunakan titik akhir bernama menghindari harus melakukan jalur kode keras di aplikasi:

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

Kode sebelumnya ditampilkan The link to the hello endpoint is /hello dari / titik akhir.

CATATAN: Nama titik akhir peka huruf besar/kecil.

Nama titik akhir:

  • Harus unik secara global.
  • Digunakan sebagai id operasi OpenAPI saat dukungan OpenAPI diaktifkan. Untuk informasi selengkapnya, lihat OpenAPI.

Parameter Rute

Parameter rute dapat ditangkap sebagai bagian dari definisi pola rute:

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

Kode sebelumnya dikembalikan The user id is 3 and book id is 7 dari URI /users/3/books/7.

Handler rute dapat mendeklarasikan parameter yang akan diambil. Ketika permintaan dibuat ke rute dengan parameter yang dinyatakan untuk diambil, parameter diurai dan diteruskan ke handler. Ini memudahkan untuk menangkap nilai dengan cara yang aman. Dalam kode sebelumnya, userId dan bookId keduanya int.

Dalam kode sebelumnya, jika salah satu nilai rute tidak dapat dikonversi ke int, pengecualian akan dilemparkan. Permintaan /users/hello/books/3 GET melemparkan pengecualian berikut:

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

Kartubebas dan tangkap semua rute

Berikut ini menangkap semua rute yang Routing to hello dikembalikan dari titik akhir '/posts/hello':

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

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

app.Run();

Batasan rute

Batasan rute membatasi perilaku pencocokan rute.

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

Tabel berikut menunjukkan templat rute sebelumnya dan perilakunya:

Templat Rute Contoh URI yang Cocok
/todos/{id:int} /todos/1
/todos/{text} /todos/something
/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

Untuk informasi selengkapnya, lihat Referensi batasan rute dalam Perutean di ASP.NET Core.

Grup rute

Metode MapGroup ekstensi membantu mengatur grup titik akhir dengan awalan umum. Ini mengurangi kode berulang dan memungkinkan untuk menyesuaikan seluruh grup titik akhir dengan satu panggilan ke metode seperti RequireAuthorization dan WithMetadata yang menambahkan metadata titik akhir.

Misalnya, kode berikut membuat dua grup titik akhir serupa:

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

Dalam skenario ini, Anda dapat menggunakan alamat relatif untuk Location header dalam hasil 201 Created :

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

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

Grup titik akhir pertama hanya akan cocok dengan permintaan yang diawali dan /public/todos dapat diakses tanpa autentikasi apa pun. Grup titik akhir kedua hanya akan cocok dengan permintaan yang diawali dan /private/todos memerlukan autentikasi.

Pabrik QueryPrivateTodos filter titik akhir adalah fungsi lokal yang memodifikasi parameter handler TodoDb rute untuk memungkinkan mengakses dan menyimpan data todo privat.

Grup rute juga mendukung grup berlapis dan pola awalan kompleks dengan parameter rute dan batasan. Dalam contoh berikut, dan handler rute yang dipetakan user ke grup dapat menangkap {org} parameter rute dan {group} yang ditentukan dalam awalan grup luar.

Awalan juga dapat kosong. Ini dapat berguna untuk menambahkan metadata titik akhir atau filter ke sekelompok titik akhir tanpa mengubah pola rute.

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

Menambahkan filter atau metadata ke grup berulah dengan cara yang sama seperti menambahkannya satu per satu ke setiap titik akhir sebelum menambahkan filter atau metadata tambahan yang mungkin telah ditambahkan ke grup dalam atau titik akhir tertentu.

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

Dalam contoh di atas, filter luar akan mencatat permintaan masuk sebelum filter dalam meskipun ditambahkan kedua. Karena filter diterapkan ke grup yang berbeda, urutan yang ditambahkan relatif satu sama lain tidak masalah. Filter pesanan ditambahkan tidak masalah jika diterapkan ke grup yang sama atau titik akhir tertentu.

Permintaan untuk /outer/inner/ mencatat hal berikut:

/outer group filter
/inner group filter
MapGet filter

Pengikatan parameter

Pengikatan parameter adalah proses mengonversi data permintaan menjadi parameter yang diekspresikan dengan kuat yang dinyatakan oleh penangan rute. Sumber pengikatan menentukan dari mana parameter terikat. Sumber pengikatan dapat eksplisit atau disimpulkan berdasarkan metode HTTP dan jenis parameter.

Sumber pengikatan yang didukung:

  • Nilai rute
  • Untai kueri
  • Header
  • Isi (sebagai JSAKTIF)
  • Nilai formulir
  • Layanan yang disediakan oleh injeksi dependensi
  • Kustom

Handler rute berikut GET menggunakan beberapa sumber pengikatan parameter ini:

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

Tabel berikut menunjukkan hubungan antara parameter yang digunakan dalam contoh sebelumnya dan sumber pengikatan terkait.

Parameter Sumber Pengikatan
id nilai rute
page string kueri
customHeader {i>header
service Disediakan oleh injeksi dependensi

Metode HTTP , HEAD, OPTIONS, dan DELETE tidak secara implisit GETmengikat dari isi. Untuk mengikat dari isi (sebagai JSAKTIF) untuk metode HTTP ini, ikat secara eksplisit dengan [FromBody] atau baca dari HttpRequest.

Contoh handler rute POST berikut menggunakan sumber pengikatan isi (sebagai JSAKTIF) untuk person parameter :

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

record Person(string Name, int Age);

Parameter dalam contoh sebelumnya semuanya terikat dari data permintaan secara otomatis. Untuk menunjukkan kenyamanan yang disediakan pengikatan parameter, penangan rute berikut menunjukkan cara membaca data permintaan langsung dari permintaan:

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

    // ...
});

Pengikatan Parameter Eksplisit

Atribut dapat digunakan untuk secara eksplisit menyatakan dari mana parameter terikat.

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();


app.MapGet("/{id}", ([FromRoute] int id,
                     [FromQuery(Name = "p")] int page,
                     [FromServices] Service service,
                     [FromHeader(Name = "Content-Type")] string contentType) 
                     => {});

class Service { }

record Person(string Name, int Age);
Parameter Sumber Pengikatan
id nilai rute dengan nama id
page string kueri dengan nama "p"
service Disediakan oleh injeksi dependensi
contentType header dengan nama "Content-Type"

Pengikatan eksplisit dari nilai formulir

Atribut [FromForm] mengikat nilai formulir:

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.

Alternatifnya adalah menggunakan [AsParameters] atribut dengan jenis kustom yang memiliki properti yang dianotasi dengan [FromForm]. Misalnya, kode berikut mengikat dari nilai formulir ke properti NewTodoRequest struktur rekaman:

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

Untuk informasi selengkapnya, lihat bagian tentang AsParameters nanti di artikel ini.

Kode sampel lengkap ada di repositori AspNetCore.Docs.Samples.

Pengikatan aman dari IFormFile dan IFormFileCollection

Pengikatan formulir kompleks didukung menggunakan IFormFile dan IFormFileCollection menggunakan [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();

Parameter yang terikat pada permintaan dengan [FromForm] menyertakan token anti-pemalsuan. Token anti-pemalsuan divalidasi ketika permintaan diproses. Untuk informasi selengkapnya, lihat Antiforgery dengan API Minimal.

Untuk informasi selengkapnya, lihat Pengikatan formulir di API minimal.

Kode sampel lengkap ada di repositori AspNetCore.Docs.Samples.

Pengikatan parameter dengan injeksi dependensi

Pengikatan parameter untuk API minimal mengikat parameter melalui injeksi dependensi saat jenis dikonfigurasi sebagai layanan. Tidak perlu menerapkan [FromServices] atribut secara eksplisit ke parameter. Dalam kode berikut, kedua tindakan mengembalikan waktu:

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

Parameter opsional

Parameter yang dideklarasikan dalam handler rute diperlakukan sesuai kebutuhan:

  • Jika permintaan cocok dengan rute, handler rute hanya berjalan jika semua parameter yang diperlukan disediakan dalam permintaan.
  • Kegagalan untuk memberikan semua parameter yang diperlukan menghasilkan kesalahan.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");

app.Run();
URI hasil
/products?pageNumber=3 3 dikembalikan
/products BadHttpRequestException: Parameter yang diperlukan "int pageNumber" tidak disediakan dari string kueri.
/products/1 Kesalahan HTTP 404, tidak ada rute yang cocok

Untuk membuat pageNumber opsional, tentukan jenis sebagai opsional atau berikan nilai default:

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

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

string ListProducts(int pageNumber = 1) => $"Requesting page {pageNumber}";

app.MapGet("/products2", ListProducts);

app.Run();
URI hasil
/products?pageNumber=3 3 dikembalikan
/products 1 dikembalikan
/products2 1 dikembalikan

Nilai nullable dan default sebelumnya berlaku untuk semua sumber:

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

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

app.Run();

Kode sebelumnya memanggil metode dengan produk null jika tidak ada isi permintaan yang dikirim.

CATATAN: Jika data yang tidak valid disediakan dan parameter dapat diubah ke null, handler rute tidak dijalankan.

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

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

app.Run();
URI hasil
/products?pageNumber=3 3 Kembali
/products 1 Kembali
/products?pageNumber=two BadHttpRequestException: Gagal mengikat parameter "Nullable<int> pageNumber" dari "dua".
/products/two Kesalahan HTTP 404, tidak ada rute yang cocok

Lihat bagian Kegagalan Pengikatan untuk informasi selengkapnya.

Jenis khusus

Jenis berikut terikat tanpa atribut eksplisit:

  • HttpContext: Konteks yang menyimpan semua informasi tentang permintaan atau respons HTTP saat ini:

    app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
    
  • HttpRequest dan HttpResponse: Permintaan HTTP dan respons HTTP:

    app.MapGet("/", (HttpRequest request, HttpResponse response) =>
        response.WriteAsync($"Hello World {request.Query["name"]}"));
    
  • CancellationToken: Token pembatalan yang terkait dengan permintaan HTTP saat ini:

    app.MapGet("/", async (CancellationToken cancellationToken) => 
        await MakeLongRunningRequestAsync(cancellationToken));
    
  • ClaimsPrincipal: Pengguna yang terkait dengan permintaan, terikat dari HttpContext.User:

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

Mengikat isi permintaan sebagai Stream atau PipeReader

Isi permintaan dapat mengikat sebagai Stream atau PipeReader untuk mendukung skenario secara efisien di mana pengguna harus memproses data dan:

  • Simpan data ke penyimpanan blob atau antrekan data ke penyedia antrean.
  • Proses data yang disimpan dengan proses pekerja atau fungsi cloud.

Misalnya, data mungkin diantrekan ke penyimpanan Azure Queue atau disimpan di penyimpanan Azure Blob.

Kode berikut mengimplementasikan antrean latar belakang:

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

Kode berikut mengikat isi permintaan ke 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);
});

Kode berikut menunjukkan file lengkap Program.cs :

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();
  • Saat membaca data, Stream adalah objek yang sama dengan HttpRequest.Body.
  • Isi permintaan tidak di-buffer secara default. Setelah tubuh dibaca, itu tidak dapat digulung balik. Aliran tidak dapat dibaca beberapa kali.
  • Stream dan PipeReader tidak dapat digunakan di luar handler tindakan minimal karena buffer yang mendasar akan dibuang atau digunakan kembali.

Unggahan file menggunakan IFormFile dan IFormFileCollection

Kode berikut menggunakan IFormFile dan IFormFileCollection untuk mengunggah file:

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

Permintaan unggahan file yang diautentikasi didukung menggunakan header Otorisasi, sertifikat klien, atau cookie header.

Pengikatan ke formulir dengan IFormCollection, IFormFile, dan IFormFileCollection

Pengikatan dari parameter berbasis formulir menggunakan IFormCollection, IFormFile, dan IFormFileCollection didukung. Metadata OpenAPI disimpulkan untuk parameter formulir untuk mendukung integrasi dengan antarmuka pengguna Swagger.

Kode berikut mengunggah file menggunakan pengikatan yang disimpulkan dari jenis :IFormFile

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

Peringatan: Saat menerapkan formulir, aplikasi harus mencegahserangan Pemalsuan Permintaan Lintas Situs (XSRF/CSRF). Dalam kode sebelumnya, IAntiforgery layanan digunakan untuk mencegah serangan XSRF dengan menghasilkan dan memvalidasi token anti-pemalsuan:

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

Untuk informasi selengkapnya tentang serangan XSRF, lihat Antiforgery dengan API Minimal

Untuk informasi selengkapnya, lihat Pengikatan formulir dalam API minimal;

Ikat ke koleksi dan jenis kompleks dari formulir

Pengikatan didukung untuk:

  • Koleksi, misalnya Daftar dan Kamus
  • Jenis kompleks, misalnya, Todo atau Project

Kode berikut menunjukkan:

  • Titik akhir minimal yang mengikat input formulir multi-bagian ke objek kompleks.
  • Cara menggunakan layanan anti-pemalsuan untuk mendukung pembuatan dan validasi token anti-pemalsuan.
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAntiforgery();

var app = builder.Build();

app.UseAntiforgery();

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

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

app.Run();

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

Dalam kode sebelumnya:

  • Parameter target harus diannotasi dengan [FromForm] atribut untuk membedakan dari parameter yang harus dibaca dari isi JSON.
  • Pengikatan dari jenis kompleks atau koleksi tidak didukung untuk API minimal yang dikompilasi dengan Generator Delegasi Permintaan.
  • Markup menunjukkan input tersembunyi tambahan dengan nama isCompleted dan nilai false. Jika kotak isCompleted centang dicentang saat formulir dikirimkan, kedua nilai true dan false dikirimkan sebagai nilai. Jika kotak centang tidak dicentang, hanya nilai false input tersembunyi yang dikirimkan. Proses pengikatan model ASP.NET Core hanya membaca nilai pertama saat mengikat bool nilai, yang menghasilkan kotak centang yang dicentang true dan false untuk kotak centang yang tidak dicentang.

Contoh data formulir yang dikirimkan ke titik akhir sebelumnya terlihat sebagai berikut:

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

Mengikat array dan nilai string dari header dan string kueri

Kode berikut menunjukkan pengikatan string kueri ke array jenis primitif, array string, dan 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]}");

Mengikat string kueri atau nilai header ke array jenis kompleks didukung ketika jenis telah TryParse diimplementasikan. Kode berikut mengikat ke array string dan mengembalikan semua item dengan tag yang ditentukan:

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

Kode berikut menunjukkan model dan implementasi yang diperlukan TryParse :

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

Kode berikut mengikat ke int array:

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

Untuk menguji kode sebelumnya, tambahkan titik akhir berikut untuk mengisi database dengan Todo item:

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

    return Results.Ok(todos);
});

Gunakan alat seperti HttpRepl untuk meneruskan data berikut ke titik akhir sebelumnya:

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

Kode berikut mengikat ke kunci X-Todo-Id header dan mengembalikan Todo item dengan nilai yang Id cocok:

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

Catatan

Saat mengikat string[] dari string kueri, tidak adanya nilai string kueri yang cocok akan menghasilkan array kosong alih-alih nilai null.

Pengikatan parameter untuk daftar argumen dengan [AsParameters]

AsParametersAttribute memungkinkan pengikatan parameter sederhana ke jenis dan bukan pengikatan model yang kompleks atau rekursif.

Pertimbangkan gambar berikut:

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.

Pertimbangkan titik akhir berikut GET :

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

Berikut ini struct dapat digunakan untuk mengganti parameter yang disorot sebelumnya:

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

Titik akhir yang direfaktor GET menggunakan yang sebelumnya struct dengan atribut 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());

Kode berikut menunjukkan titik akhir tambahan di aplikasi:

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

Kelas berikut digunakan untuk merefaktor daftar parameter:

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

Kode berikut menunjukkan titik akhir yang direfaktor menggunakan AsParameters dan kelas dan sebelumnya struct :

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

Jenis berikut record dapat digunakan untuk mengganti parameter sebelumnya:

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

struct Menggunakan dengan AsParameters bisa lebih berkinerja daripada menggunakan record jenis.

Kode sampel lengkap di repositori AspNetCore.Docs.Samples.

Pengikatan Kustom

Ada dua cara untuk menyesuaikan pengikatan parameter:

  1. Untuk sumber pengikatan rute, kueri, dan header, ikat jenis kustom dengan menambahkan metode statis TryParse untuk jenis tersebut.
  2. Kontrol proses pengikatan dengan menerapkan BindAsync metode pada jenis.

TryParse

TryParse memiliki dua API:

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

Kode berikut ditampilkan Point: 12.3, 10.1 dengan 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 memiliki API berikut:

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

Kode berikut ditampilkan SortBy:xyz, SortDirection:Desc, CurrentPage:99 dengan 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
}

Kegagalan pengikatan

Saat pengikatan gagal, kerangka kerja mencatat pesan debug dan mengembalikan berbagai kode status ke klien tergantung pada mode kegagalan.

Mode kegagalan Jenis Parameter Nullable Sumber Pengikatan Kode status
{ParameterType}.TryParse menghasilkan false yes route/query/header 400
{ParameterType}.BindAsync menghasilkan null yes custom 400
{ParameterType}.BindAsync Melempar tidak penting custom 500
Kegagalan untuk mendeserialisasi JSisi ON tidak penting body 400
Tipe isi salah (bukan application/json) tidak penting body 415

Prioritas Pengikatan

Aturan untuk menentukan sumber pengikatan dari parameter:

  1. Atribut eksplisit yang ditentukan pada parameter (Dari* atribut) dalam urutan berikut:
    1. Nilai rute: [FromRoute]
    2. String kueri: [FromQuery]
    3. Header: [FromHeader]
    4. Tubuh: [FromBody]
    5. Bentuk: [FromForm]
    6. Layanan: [FromServices]
    7. Nilai parameter: [AsParameters]
  2. Jenis khusus
    1. HttpContext
    2. HttpRequestAku akan menemuinya.HttpContext.Request
    3. HttpResponseAku akan menemuinya.HttpContext.Response
    4. ClaimsPrincipalAku akan menemuinya.HttpContext.User
    5. CancellationTokenAku akan menemuinya.HttpContext.RequestAborted
    6. IFormCollectionAku akan menemuinya.HttpContext.Request.Form
    7. IFormFileCollectionAku akan menemuinya.HttpContext.Request.Form.Files
    8. IFormFileAku akan menemuinya.HttpContext.Request.Form.Files[paramName]
    9. StreamAku akan menemuinya.HttpContext.Request.Body
    10. PipeReaderAku akan menemuinya.HttpContext.Request.BodyReader
  3. Jenis parameter memiliki metode statis BindAsync yang valid.
  4. Jenis parameter adalah string atau memiliki metode statis TryParse yang valid.
    1. Jika nama parameter ada di templat rute misalnya, app.Map("/todo/{id}", (int id) => {});, maka terikat dari rute.
    2. Terikat dari string kueri.
  5. Jika jenis parameter adalah layanan yang disediakan oleh injeksi dependensi, ia menggunakan layanan tersebut sebagai sumbernya.
  6. Parameternya adalah dari isinya.

Mengonfigurasi JSopsi deserialisasi ON untuk pengikatan isi

Sumber pengikatan isi menggunakan System.Text.Json untuk deserialisasi. Tidak dimungkinkan untuk mengubah default ini, tetapi JSopsi serialisasi dan deserialisasi ON dapat dikonfigurasi.

Mengonfigurasi JSopsi deserialisasi ON secara global

Opsi yang berlaku secara global untuk aplikasi dapat dikonfigurasi dengan memanggil ConfigureHttpJsonOptions. Contoh berikut mencakup bidang publik dan format JSoutput ON.

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

Karena kode sampel mengonfigurasi serialisasi dan deserialisasi, kode tersebut dapat membaca NameField dan menyertakan NameField dalam output JSON.

Mengonfigurasi JSopsi deserialisasi ON untuk titik akhir

ReadFromJsonAsync memiliki kelebihan beban yang menerima JsonSerializerOptions objek. Contoh berikut mencakup bidang publik dan format JSoutput ON.

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

Karena kode sebelumnya menerapkan opsi yang disesuaikan hanya untuk deserialisasi, output JSON mengecualikan NameField.

Membaca isi permintaan

Baca isi permintaan secara langsung menggunakan HttpContext parameter atau 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();

Kode sebelumnya:

Respons

Handler rute mendukung jenis nilai pengembalian berikut:

  1. IResult berbasis - Ini termasuk Task<IResult> dan ValueTask<IResult>
  2. string - Ini termasuk Task<string> dan ValueTask<string>
  3. T (Jenis lainnya) - Ini termasuk Task<T> dan ValueTask<T>
Nilai hasil Perilaku Content-Type
IResult Kerangka kerja memanggil IResult.ExecuteAsync Diputuskan IResult oleh implementasi
string Kerangka kerja menulis string langsung ke respons text/plain
T (Jenis lainnya) Kerangka kerja JSON-serialisasi respons application/json

Untuk panduan yang lebih mendalam untuk merutekan nilai pengembalian handler, lihat Membuat respons di aplikasi API Minimal

Contoh nilai pengembalian

nilai pengembalian string

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

JSNILAI yang dikembalikan ON

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

Mengembalikan TypedResults

Kode berikut mengembalikan TypedResults:

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

TypedResults Mengembalikan lebih disukai untuk mengembalikan Results. Untuk informasi selengkapnya, lihat TypedResults vs Results.

Nilai pengembalian IResult

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

Contoh berikut menggunakan jenis hasil bawaan untuk mengkustomisasi respons:

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

JSPADA

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

Kode Status Kustom

app.MapGet("/405", () => Results.StatusCode(405));

Teks

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

Lihat Membuat respons di aplikasi API Minimal untuk contoh selengkapnya.

Pengalihan

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

File

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

Hasil bawaan

Pembantu hasil umum ada di Results kelas statis dan TypedResults . TypedResults Mengembalikan lebih disukai untuk mengembalikan Results. Untuk informasi selengkapnya, lihat TypedResults vs Results.

Menyesuaikan hasil

Aplikasi dapat mengontrol respons dengan menerapkan jenis kustom IResult . Kode berikut adalah contoh jenis hasil 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);
    }
}

Sebaiknya tambahkan metode ekstensi untuk Microsoft.AspNetCore.Http.IResultExtensions membuat hasil kustom ini lebih dapat ditemukan.

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

Hasil yang ditik

Antarmuka IResult dapat mewakili nilai yang dikembalikan dari API minimal yang tidak menggunakan dukungan implisit untuk JSON yang menserialisasikan objek yang dikembalikan ke respons HTTP. Kelas Hasil statis digunakan untuk membuat berbagai IResult objek yang mewakili berbagai jenis respons. Misalnya, mengatur kode status respons atau mengalihkan ke URL lain.

Jenis yang IResult diterapkan bersifat publik, memungkinkan pernyataan jenis saat pengujian. Contohnya:

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

Anda dapat melihat jenis pengembalian metode yang sesuai pada kelas TypedResults statis untuk menemukan jenis publik IResult yang benar untuk ditransmisikan.

Lihat Membuat respons di aplikasi API Minimal untuk contoh selengkapnya.

Filter

Lihat:

Authorization

Rute dapat dilindungi menggunakan kebijakan otorisasi. Ini dapat dideklarasikan melalui [Authorize] atribut atau dengan menggunakan RequireAuthorization metode :

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

Kode sebelumnya dapat ditulis dengan RequireAuthorization:

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

Sampel berikut menggunakan otorisasi berbasis kebijakan:

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

Perbolehkan pengguna yang tidak diaauthenticated untuk mengakses titik akhir

memungkinkan [AllowAnonymous] pengguna yang tidak diaauthenticated untuk mengakses titik akhir:

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


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

CORS

Rute dapat diaktifkan CORS menggunakan kebijakan CORS. CORS dapat dideklarasikan melalui [EnableCors] atribut atau dengan menggunakan RequireCors metode . Sampel berikut mengaktifkan 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();

Untuk informasi selengkapnya, lihat Mengaktifkan Permintaan Lintas Asal (CORS) di ASP.NET Core

ValidateScopes dan ValidateOnBuild

ValidateScopes dan ValidateOnBuild diaktifkan secara default di lingkungan Pengembangan tetapi dinonaktifkan di lingkungan lain.

Ketika ValidateOnBuild adalah true, kontainer DI memvalidasi konfigurasi layanan pada waktu build. Jika konfigurasi layanan tidak valid, build gagal pada startup aplikasi, bukan pada runtime saat layanan diminta.

Ketika ValidateScopes adalah true, kontainer DI memvalidasi bahwa layanan tercakup tidak diselesaikan dari cakupan akar. Menyelesaikan layanan tercakup dari cakupan akar dapat mengakibatkan kebocoran memori karena layanan dipertahankan dalam memori lebih lama dari cakupan permintaan.

ValidateScopes dan ValidateOnBuild salah secara default dalam mode non-Pengembangan karena alasan performa.

Kode berikut menunjukkan ValidateScopes diaktifkan secara default dalam mode pengembangan tetapi dinonaktifkan dalam mode rilis:

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

Kode berikut menunjukkan ValidateOnBuild diaktifkan secara default dalam mode pengembangan tetapi dinonaktifkan dalam mode rilis:

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

Kode berikut menonaktifkan ValidateScopes dan ValidateOnBuild dalam 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;
    });
}

Lihat juga

Dokumen ini:

API minimal terdiri dari:

WebApplication

Kode berikut dihasilkan oleh templat ASP.NET Core:

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

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

app.Run();

Kode sebelumnya dapat dibuat melalui dotnet new web pada baris perintah atau memilih templat Web Kosong di Visual Studio.

Kode berikut membuat WebApplication (app) tanpa secara eksplisit membuat WebApplicationBuilder:

var app = WebApplication.Create(args);

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

app.Run();

WebApplication.Create menginisialisasi instans WebApplication baru kelas dengan default yang telah dikonfigurasi sebelumnya.

WebApplication secara otomatis menambahkan middleware berikut tergantung Minimal API applications pada kondisi tertentu:

  • UseDeveloperExceptionPage ditambahkan terlebih dahulu ketika HostingEnvironment adalah "Development".
  • UseRouting ditambahkan kedua jika kode pengguna belum memanggil UseRouting dan jika ada titik akhir yang dikonfigurasi, misalnya app.MapGet.
  • UseEndpoints ditambahkan di akhir alur middleware jika ada titik akhir yang dikonfigurasi.
  • UseAuthentication ditambahkan segera setelah UseRouting jika kode pengguna belum memanggil UseAuthentication dan apakah IAuthenticationSchemeProvider dapat terdeteksi di penyedia layanan. IAuthenticationSchemeProvider ditambahkan secara default saat menggunakan AddAuthentication, dan layanan terdeteksi menggunakan IServiceProviderIsService.
  • UseAuthorization ditambahkan berikutnya jika kode pengguna belum memanggil UseAuthorization dan apakah IAuthorizationHandlerProvider dapat terdeteksi di penyedia layanan. IAuthorizationHandlerProvider ditambahkan secara default saat menggunakan AddAuthorization, dan layanan terdeteksi menggunakan IServiceProviderIsService.
  • Middleware dan titik akhir yang dikonfigurasi pengguna ditambahkan antara UseRouting dan UseEndpoints.

Kode berikut secara efektif adalah apa yang ditambahkan middleware otomatis ke aplikasi:

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

Dalam beberapa kasus, konfigurasi middleware default tidak benar untuk aplikasi dan memerlukan modifikasi. Misalnya, UseCors harus dipanggil sebelum UseAuthentication dan UseAuthorization. Aplikasi perlu memanggil UseAuthentication dan UseAuthorization jika UseCors dipanggil:

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

Jika middleware harus dijalankan sebelum pencocokan rute terjadi, UseRouting harus dipanggil dan middleware harus ditempatkan sebelum panggilan ke UseRouting. UseEndpoints tidak diperlukan dalam kasus ini karena ditambahkan secara otomatis seperti yang dijelaskan sebelumnya:

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

app.UseRouting();

// other middleware and endpoints

Saat menambahkan middleware terminal:

  • Middleware harus ditambahkan setelah UseEndpoints.
  • Aplikasi perlu memanggil UseRouting dan UseEndpoints sehingga middleware terminal dapat ditempatkan di lokasi yang benar.
app.UseRouting();

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

app.UseEndpoints(e => {});

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

Middleware terminal adalah middleware yang berjalan jika tidak ada titik akhir yang menangani permintaan.

Bekerja dengan port

Saat aplikasi web dibuat dengan Visual Studio atau dotnet new, Properties/launchSettings.json file dibuat yang menentukan port yang ditanggapi aplikasi. Di sampel pengaturan port berikut, menjalankan aplikasi dari Visual Studio mengembalikan dialog Unable to connect to web server 'AppName'kesalahan . Visual Studio mengembalikan kesalahan karena mengharapkan port yang ditentukan dalam Properties/launchSettings.json, tetapi aplikasi menggunakan port yang ditentukan oleh app.Run("http://localhost:3000"). Jalankan sampel perubahan port berikut dari baris perintah.

Bagian berikut mengatur port yang direspons aplikasi.

var app = WebApplication.Create(args);

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

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

Dalam kode sebelumnya, aplikasi merespons port 3000.

Beberapa port

Dalam kode berikut, aplikasi merespons port 3000 dan 4000.

var app = WebApplication.Create(args);

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

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

app.Run();

Mengatur port dari baris perintah

Perintah berikut membuat aplikasi merespons port 7777:

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

Kestrel Jika titik akhir juga dikonfigurasi dalam appsettings.json file, appsettings.json URL yang ditentukan file akan digunakan. Untuk informasi selengkapnya, lihat Kestrel konfigurasi titik akhir

Membaca port dari lingkungan

Kode berikut membaca port dari lingkungan:

var app = WebApplication.Create(args);

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

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

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

Cara yang disukai untuk mengatur port dari lingkungan adalah dengan menggunakan ASPNETCORE_URLS variabel lingkungan, yang ditunjukkan di bagian berikut.

Mengatur port melalui variabel lingkungan ASPNETCORE_URLS

Variabel ASPNETCORE_URLS lingkungan tersedia untuk mengatur port:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS mendukung beberapa URL:

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

Untuk informasi selengkapnya menggunakan lingkungan, lihat Menggunakan beberapa lingkungan di ASP.NET Core

Dengarkan semua antarmuka

Sampel berikut menunjukkan mendengarkan di semua antarmuka

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

Dengarkan semua antarmuka menggunakan ASPNETCORE_URLS

Sampel sebelumnya dapat menggunakan ASPNETCORE_URLS

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

Tentukan HTTPS dengan sertifikat pengembangan

var app = WebApplication.Create(args);

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

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

app.Run();

Untuk informasi selengkapnya tentang sertifikat pengembangan, lihat Mempercayai sertifikat pengembangan ASP.NET Core HTTPS di Windows dan macOS.

Tentukan HTTPS menggunakan sertifikat kustom

Bagian berikut menunjukkan cara menentukan sertifikat kustom menggunakan appsettings.json file dan melalui konfigurasi.

Tentukan sertifikat kustom dengan appsettings.json

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

Tentukan sertifikat kustom melalui konfigurasi

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

Menggunakan API sertifikat

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

Konfigurasi

Kode berikut membaca dari sistem konfigurasi:

var app = WebApplication.Create(args);

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

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

app.Run();

Untuk informasi selengkapnya, lihat Konfigurasi di ASP.NET Core

Pencatatan

Kode berikut menulis pesan ke startup aplikasi masuk:

var app = WebApplication.Create(args);

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

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

app.Run();

Untuk informasi selengkapnya, lihat Pengelogan di .NET Core dan ASP.NET Core

Mengakses kontainer Injeksi Dependensi (DI)

Kode berikut menunjukkan cara mendapatkan layanan dari kontainer DI selama pengaktifan aplikasi:


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

Untuk informasi lebih lanjut, lihat Injeksi dependensi di ASP.NET Core.

WebApplicationBuilder

Bagian ini berisi kode sampel menggunakan WebApplicationBuilder.

Mengubah akar konten, nama aplikasi, dan lingkungan

Kode berikut mengatur akar konten, nama aplikasi, dan lingkungan:

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 menginisialisasi instans baru kelas WebApplicationBuilder dengan default yang telah dikonfigurasi sebelumnya.

Untuk informasi selengkapnya, lihat gambaran umum dasar-dasar ASP.NET Core

Mengubah akar konten, nama aplikasi, dan lingkungan menurut variabel lingkungan atau baris perintah

Tabel berikut ini memperlihatkan variabel lingkungan dan argumen baris perintah yang digunakan untuk mengubah akar konten, nama aplikasi, dan lingkungan:

fitur Variabel lingkungan Argumen baris perintah
Nama aplikasi ASPNETCORE_APPLICATIONNAME --applicationName
Nama lingkungan ASPNETCORE_ENVIRONMENT --Lingkungan
Akar konten ASPNETCORE_CONTENTROOT --contentRoot

Menambahkan penyedia konfigurasi

Contoh berikut menambahkan penyedia konfigurasi INI:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

Untuk informasi terperinci, lihat Penyedia konfigurasi file di Konfigurasi di ASP.NET Core.

Membaca konfigurasi

Secara default WebApplicationBuilder , konfigurasi baca dari beberapa sumber, termasuk:

  • appSettings.json dan appSettings.{environment}.json
  • Variabel lingkungan
  • Baris perintah

Kode berikut membaca HelloKey dari konfigurasi dan menampilkan nilai di / titik akhir. Jika nilai konfigurasi null, "Halo" ditetapkan ke message:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Untuk daftar lengkap sumber konfigurasi yang dibaca, lihat Konfigurasi default dalam Konfigurasi di ASP.NET Core

Menambahkan penyedia pengelogan

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

Menambahkan layanan

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

Menyesuaikan IHostBuilder

Metode ekstensi yang ada di IHostBuilder dapat diakses menggunakan properti 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();

Menyesuaikan IWebHostBuilder

Metode ekstensi pada IWebHostBuilder dapat diakses menggunakan properti 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();

Mengubah akar web

Secara default, akar web relatif terhadap akar konten di wwwroot folder. Akar web adalah tempat middleware file statis mencari file statis. Akar web dapat diubah dengan WebHostOptions, baris perintah, atau dengan UseWebRoot metode :

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

var app = builder.Build();

app.Run();

Kontainer injeksi dependensi kustom (DI)

Contoh berikut menggunakan 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();

Tambahkan Middleware

Middleware ASP.NET Core yang ada dapat dikonfigurasi pada WebApplication:

var app = WebApplication.Create(args);

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

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

app.Run();

Untuk informasi selengkapnya, lihat ASP.NET Core Middleware

Halaman pengecualian pengembang

WebApplication.CreateBuilder menginisialisasi instans WebApplicationBuilder baru kelas dengan default yang telah dikonfigurasi sebelumnya. Halaman pengecualian pengembang diaktifkan dalam default yang telah dikonfigurasi sebelumnya. Saat kode berikut dijalankan di lingkungan pengembangan, navigasi untuk / merender halaman ramah yang menunjukkan pengecualian.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Run();

ASP.NET Core Middleware

Tabel berikut mencantumkan beberapa middleware yang sering digunakan dengan API minimal.

Middleware Deskripsi API
Autentikasi Menyediakan dukungan autentikasi. UseAuthentication
Otorisasi Menyediakan dukungan otorisasi. UseAuthorization
CORS Mengonfigurasi Cross-Origin Resource Sharing. UseCors
Handler Pengecualian Secara global menangani pengecualian yang dilemparkan oleh alur middleware. UseExceptionHandler
Header yang Diteruskan Meneruskan header yang diproksi ke permintaan saat ini. UseForwardedHeaders
Pengalihan HTTPS Mengalihkan semua permintaan HTTP ke HTTPS. UseHttpsRedirection
HTTP Strict Transport Security (HSTS) Middleware peningkatan keamanan yang menambahkan header respons khusus. UseHsts
Pengelogan Permintaan Menyediakan dukungan untuk mencatat permintaan dan respons HTTP. UseHttpLogging
Batas Waktu Permintaan Menyediakan dukungan untuk mengonfigurasi batas waktu permintaan, default global, dan per titik akhir. UseRequestTimeouts
Pengelogan Permintaan W3C Menyediakan dukungan untuk mencatat permintaan dan respons HTTP dalam format W3C. UseW3CLogging
Penembolokan Respons Menyediakan dukungan untuk respons penembolokan. UseResponseCaching
Pemadatan Respons Memberikan dukungan untuk memadatkan respons. UseResponseCompression
Sesi Menyediakan dukungan untuk mengelola sesi pengguna. UseSession
File Statis Menyediakan dukungan untuk menyajikan file statis dan penjelajahan direktori. UseStaticFiles, UseFileServer
WebSocket Mengaktifkan protokol WebSocket. UseWebSockets

Bagian berikut mencakup penanganan permintaan: perutean, pengikatan parameter, dan respons.

Perutean

Dukungan yang dikonfigurasi WebApplicationMap{Verb} dan MapMethods di mana {Verb} adalah metode HTTP camel-cased seperti Get, , PostPut atau 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();

Argumen Delegate yang diteruskan ke metode ini disebut "penangan rute".

Penangan Rute

Penangan rute adalah metode yang dijalankan saat rute cocok. Penangan rute dapat berupa ekspresi lambda, fungsi lokal, metode instans, atau metode statis. Penangan rute bisa sinkron atau asinkron.

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

Fungsi lokal

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

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

app.MapGet("/", LocalFunction);

app.Run();

Metode instans

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

Metode statis

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

Titik akhir yang ditentukan di luar Program.cs

API minimal tidak harus berada di 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" });
        });
    }
}

Lihat juga Merutekan grup nanti di artikel ini.

Titik akhir dapat diberikan nama untuk menghasilkan URL ke titik akhir. Menggunakan titik akhir bernama menghindari harus melakukan jalur kode keras di aplikasi:

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

Kode sebelumnya ditampilkan The link to the hello endpoint is /hello dari / titik akhir.

CATATAN: Nama titik akhir peka huruf besar/kecil.

Nama titik akhir:

  • Harus unik secara global.
  • Digunakan sebagai id operasi OpenAPI saat dukungan OpenAPI diaktifkan. Untuk informasi selengkapnya, lihat OpenAPI.

Parameter Rute

Parameter rute dapat ditangkap sebagai bagian dari definisi pola rute:

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

Kode sebelumnya dikembalikan The user id is 3 and book id is 7 dari URI /users/3/books/7.

Handler rute dapat mendeklarasikan parameter yang akan diambil. Ketika permintaan dibuat ke rute dengan parameter yang dinyatakan untuk diambil, parameter diurai dan diteruskan ke handler. Ini memudahkan untuk menangkap nilai dengan cara yang aman. Dalam kode sebelumnya, userId dan bookId keduanya int.

Dalam kode sebelumnya, jika salah satu nilai rute tidak dapat dikonversi ke int, pengecualian akan dilemparkan. Permintaan /users/hello/books/3 GET melemparkan pengecualian berikut:

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

Kartubebas dan tangkap semua rute

Berikut ini menangkap semua rute yang Routing to hello dikembalikan dari titik akhir '/posts/hello':

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

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

app.Run();

Batasan rute

Batasan rute membatasi perilaku pencocokan rute.

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

Tabel berikut menunjukkan templat rute sebelumnya dan perilakunya:

Templat Rute Contoh URI yang Cocok
/todos/{id:int} /todos/1
/todos/{text} /todos/something
/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

Untuk informasi selengkapnya, lihat Referensi batasan rute dalam Perutean di ASP.NET Core.

Grup rute

Metode MapGroup ekstensi membantu mengatur grup titik akhir dengan awalan umum. Ini mengurangi kode berulang dan memungkinkan untuk menyesuaikan seluruh grup titik akhir dengan satu panggilan ke metode seperti RequireAuthorization dan WithMetadata yang menambahkan metadata titik akhir.

Misalnya, kode berikut membuat dua grup titik akhir serupa:

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

Dalam skenario ini, Anda dapat menggunakan alamat relatif untuk Location header dalam hasil 201 Created :

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

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

Grup titik akhir pertama hanya akan cocok dengan permintaan yang diawali dan /public/todos dapat diakses tanpa autentikasi apa pun. Grup titik akhir kedua hanya akan cocok dengan permintaan yang diawali dan /private/todos memerlukan autentikasi.

Pabrik QueryPrivateTodos filter titik akhir adalah fungsi lokal yang memodifikasi parameter handler TodoDb rute untuk memungkinkan mengakses dan menyimpan data todo privat.

Grup rute juga mendukung grup berlapis dan pola awalan kompleks dengan parameter rute dan batasan. Dalam contoh berikut, dan handler rute yang dipetakan user ke grup dapat menangkap {org} parameter rute dan {group} yang ditentukan dalam awalan grup luar.

Awalan juga dapat kosong. Ini dapat berguna untuk menambahkan metadata titik akhir atau filter ke sekelompok titik akhir tanpa mengubah pola rute.

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

Menambahkan filter atau metadata ke grup berulah dengan cara yang sama seperti menambahkannya satu per satu ke setiap titik akhir sebelum menambahkan filter atau metadata tambahan yang mungkin telah ditambahkan ke grup dalam atau titik akhir tertentu.

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

Dalam contoh di atas, filter luar akan mencatat permintaan masuk sebelum filter dalam meskipun ditambahkan kedua. Karena filter diterapkan ke grup yang berbeda, urutan yang ditambahkan relatif satu sama lain tidak masalah. Filter pesanan ditambahkan tidak masalah jika diterapkan ke grup yang sama atau titik akhir tertentu.

Permintaan untuk /outer/inner/ mencatat hal berikut:

/outer group filter
/inner group filter
MapGet filter

Pengikatan parameter

Pengikatan parameter adalah proses mengonversi data permintaan menjadi parameter yang diekspresikan dengan kuat yang dinyatakan oleh penangan rute. Sumber pengikatan menentukan dari mana parameter terikat. Sumber pengikatan dapat eksplisit atau disimpulkan berdasarkan metode HTTP dan jenis parameter.

Sumber pengikatan yang didukung:

  • Nilai rute
  • Untai kueri
  • Header
  • Isi (sebagai JSAKTIF)
  • Layanan yang disediakan oleh injeksi dependensi
  • Kustom

Pengikatan dari nilai formulir tidak didukung secara asli di .NET 6 dan 7.

Handler rute berikut GET menggunakan beberapa sumber pengikatan parameter ini:

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

Tabel berikut menunjukkan hubungan antara parameter yang digunakan dalam contoh sebelumnya dan sumber pengikatan terkait.

Parameter Sumber Pengikatan
id nilai rute
page string kueri
customHeader {i>header
service Disediakan oleh injeksi dependensi

Metode HTTP , HEAD, OPTIONS, dan DELETE tidak secara implisit GETmengikat dari isi. Untuk mengikat dari isi (sebagai JSAKTIF) untuk metode HTTP ini, ikat secara eksplisit dengan [FromBody] atau baca dari HttpRequest.

Contoh handler rute POST berikut menggunakan sumber pengikatan isi (sebagai JSAKTIF) untuk person parameter :

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

record Person(string Name, int Age);

Parameter dalam contoh sebelumnya semuanya terikat dari data permintaan secara otomatis. Untuk menunjukkan kenyamanan yang disediakan pengikatan parameter, penangan rute berikut menunjukkan cara membaca data permintaan langsung dari permintaan:

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

    // ...
});

Pengikatan Parameter Eksplisit

Atribut dapat digunakan untuk secara eksplisit menyatakan dari mana parameter terikat.

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();


app.MapGet("/{id}", ([FromRoute] int id,
                     [FromQuery(Name = "p")] int page,
                     [FromServices] Service service,
                     [FromHeader(Name = "Content-Type")] string contentType) 
                     => {});

class Service { }

record Person(string Name, int Age);
Parameter Sumber Pengikatan
id nilai rute dengan nama id
page string kueri dengan nama "p"
service Disediakan oleh injeksi dependensi
contentType header dengan nama "Content-Type"

Catatan

Pengikatan dari nilai formulir tidak didukung secara asli di .NET 6 dan 7.

Pengikatan parameter dengan injeksi dependensi

Pengikatan parameter untuk API minimal mengikat parameter melalui injeksi dependensi saat jenis dikonfigurasi sebagai layanan. Tidak perlu menerapkan [FromServices] atribut secara eksplisit ke parameter. Dalam kode berikut, kedua tindakan mengembalikan waktu:

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

Parameter opsional

Parameter yang dideklarasikan dalam handler rute diperlakukan sesuai kebutuhan:

  • Jika permintaan cocok dengan rute, handler rute hanya berjalan jika semua parameter yang diperlukan disediakan dalam permintaan.
  • Kegagalan untuk memberikan semua parameter yang diperlukan menghasilkan kesalahan.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");

app.Run();
URI hasil
/products?pageNumber=3 3 dikembalikan
/products BadHttpRequestException: Parameter yang diperlukan "int pageNumber" tidak disediakan dari string kueri.
/products/1 Kesalahan HTTP 404, tidak ada rute yang cocok

Untuk membuat pageNumber opsional, tentukan jenis sebagai opsional atau berikan nilai default:

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

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

string ListProducts(int pageNumber = 1) => $"Requesting page {pageNumber}";

app.MapGet("/products2", ListProducts);

app.Run();
URI hasil
/products?pageNumber=3 3 dikembalikan
/products 1 dikembalikan
/products2 1 dikembalikan

Nilai nullable dan default sebelumnya berlaku untuk semua sumber:

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

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

app.Run();

Kode sebelumnya memanggil metode dengan produk null jika tidak ada isi permintaan yang dikirim.

CATATAN: Jika data yang tidak valid disediakan dan parameter dapat diubah ke null, handler rute tidak dijalankan.

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

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

app.Run();
URI hasil
/products?pageNumber=3 3 Kembali
/products 1 Kembali
/products?pageNumber=two BadHttpRequestException: Gagal mengikat parameter "Nullable<int> pageNumber" dari "dua".
/products/two Kesalahan HTTP 404, tidak ada rute yang cocok

Lihat bagian Kegagalan Pengikatan untuk informasi selengkapnya.

Jenis khusus

Jenis berikut terikat tanpa atribut eksplisit:

  • HttpContext: Konteks yang menyimpan semua informasi tentang permintaan atau respons HTTP saat ini:

    app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
    
  • HttpRequest dan HttpResponse: Permintaan HTTP dan respons HTTP:

    app.MapGet("/", (HttpRequest request, HttpResponse response) =>
        response.WriteAsync($"Hello World {request.Query["name"]}"));
    
  • CancellationToken: Token pembatalan yang terkait dengan permintaan HTTP saat ini:

    app.MapGet("/", async (CancellationToken cancellationToken) => 
        await MakeLongRunningRequestAsync(cancellationToken));
    
  • ClaimsPrincipal: Pengguna yang terkait dengan permintaan, terikat dari HttpContext.User:

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

Mengikat isi permintaan sebagai Stream atau PipeReader

Isi permintaan dapat mengikat sebagai Stream atau PipeReader untuk mendukung skenario secara efisien di mana pengguna harus memproses data dan:

  • Simpan data ke penyimpanan blob atau antrekan data ke penyedia antrean.
  • Proses data yang disimpan dengan proses pekerja atau fungsi cloud.

Misalnya, data mungkin diantrekan ke penyimpanan Azure Queue atau disimpan di penyimpanan Azure Blob.

Kode berikut mengimplementasikan antrean latar belakang:

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

Kode berikut mengikat isi permintaan ke 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);
});

Kode berikut menunjukkan file lengkap Program.cs :

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();
  • Saat membaca data, Stream adalah objek yang sama dengan HttpRequest.Body.
  • Isi permintaan tidak di-buffer secara default. Setelah tubuh dibaca, itu tidak dapat digulung balik. Aliran tidak dapat dibaca beberapa kali.
  • Stream dan PipeReader tidak dapat digunakan di luar handler tindakan minimal karena buffer yang mendasar akan dibuang atau digunakan kembali.

Unggahan file menggunakan IFormFile dan IFormFileCollection

Kode berikut menggunakan IFormFile dan IFormFileCollection untuk mengunggah file:

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

Permintaan unggahan file yang diautentikasi didukung menggunakan header Otorisasi, sertifikat klien, atau cookie header.

Tidak ada dukungan bawaan untuk antiforgery di ASP.NET Core 7.0. Antiforgery tersedia di ASP.NET Core 8.0 dan yang lebih baru. Namun, dapat diimplementasikan menggunakan IAntiforgery layanan .

Mengikat array dan nilai string dari header dan string kueri

Kode berikut menunjukkan pengikatan string kueri ke array jenis primitif, array string, dan 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]}");

Mengikat string kueri atau nilai header ke array jenis kompleks didukung ketika jenis telah TryParse diimplementasikan. Kode berikut mengikat ke array string dan mengembalikan semua item dengan tag yang ditentukan:

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

Kode berikut menunjukkan model dan implementasi yang diperlukan TryParse :

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

Kode berikut mengikat ke int array:

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

Untuk menguji kode sebelumnya, tambahkan titik akhir berikut untuk mengisi database dengan Todo item:

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

    return Results.Ok(todos);
});

Gunakan alat pengujian API seperti HttpRepl untuk meneruskan data berikut ke titik akhir sebelumnya:

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

Kode berikut mengikat ke kunci X-Todo-Id header dan mengembalikan Todo item dengan nilai yang Id cocok:

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

Catatan

Saat mengikat string[] dari string kueri, tidak adanya nilai string kueri yang cocok akan menghasilkan array kosong alih-alih nilai null.

Pengikatan parameter untuk daftar argumen dengan [AsParameters]

AsParametersAttribute memungkinkan pengikatan parameter sederhana ke jenis dan bukan pengikatan model yang kompleks atau rekursif.

Pertimbangkan gambar berikut:

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.

Pertimbangkan titik akhir berikut GET :

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

Berikut ini struct dapat digunakan untuk mengganti parameter yang disorot sebelumnya:

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

Titik akhir yang direfaktor GET menggunakan yang sebelumnya struct dengan atribut 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());

Kode berikut menunjukkan titik akhir tambahan di aplikasi:

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

Kelas berikut digunakan untuk merefaktor daftar parameter:

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

Kode berikut menunjukkan titik akhir yang direfaktor menggunakan AsParameters dan kelas dan sebelumnya struct :

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

Jenis berikut record dapat digunakan untuk mengganti parameter sebelumnya:

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

struct Menggunakan dengan AsParameters bisa lebih berkinerja daripada menggunakan record jenis.

Kode sampel lengkap di repositori AspNetCore.Docs.Samples.

Pengikatan Kustom

Ada dua cara untuk menyesuaikan pengikatan parameter:

  1. Untuk sumber pengikatan rute, kueri, dan header, ikat jenis kustom dengan menambahkan metode statis TryParse untuk jenis tersebut.
  2. Kontrol proses pengikatan dengan menerapkan BindAsync metode pada jenis.

TryParse

TryParse memiliki dua API:

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

Kode berikut ditampilkan Point: 12.3, 10.1 dengan 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 memiliki API berikut:

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

Kode berikut ditampilkan SortBy:xyz, SortDirection:Desc, CurrentPage:99 dengan 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
}

Kegagalan pengikatan

Saat pengikatan gagal, kerangka kerja mencatat pesan debug dan mengembalikan berbagai kode status ke klien tergantung pada mode kegagalan.

Mode kegagalan Jenis Parameter Nullable Sumber Pengikatan Kode status
{ParameterType}.TryParse menghasilkan false yes route/query/header 400
{ParameterType}.BindAsync menghasilkan null yes custom 400
{ParameterType}.BindAsync Melempar tidak masalah custom 500
Kegagalan untuk mendeserialisasi JSisi ON tidak masalah body 400
Tipe isi salah (bukan application/json) tidak masalah body 415

Prioritas Pengikatan

Aturan untuk menentukan sumber pengikatan dari parameter:

  1. Atribut eksplisit yang ditentukan pada parameter (Dari* atribut) dalam urutan berikut:
    1. Nilai rute: [FromRoute]
    2. String kueri: [FromQuery]
    3. Header: [FromHeader]
    4. Tubuh: [FromBody]
    5. Layanan: [FromServices]
    6. Nilai parameter: [AsParameters]
  2. Jenis khusus
    1. HttpContext
    2. HttpRequestAku akan menemuinya.HttpContext.Request
    3. HttpResponseAku akan menemuinya.HttpContext.Response
    4. ClaimsPrincipalAku akan menemuinya.HttpContext.User
    5. CancellationTokenAku akan menemuinya.HttpContext.RequestAborted
    6. IFormFileCollectionAku akan menemuinya.HttpContext.Request.Form.Files
    7. IFormFileAku akan menemuinya.HttpContext.Request.Form.Files[paramName]
    8. StreamAku akan menemuinya.HttpContext.Request.Body
    9. PipeReaderAku akan menemuinya.HttpContext.Request.BodyReader
  3. Jenis parameter memiliki metode statis BindAsync yang valid.
  4. Jenis parameter adalah string atau memiliki metode statis TryParse yang valid.
    1. Jika nama parameter ada di templat rute misalnya app.Map("/todo/{id}", (int id) => {});, maka terikat dari rute.
    2. Terikat dari string kueri.
  5. Jika jenis parameter adalah layanan yang disediakan oleh injeksi dependensi, ia menggunakan layanan tersebut sebagai sumbernya.
  6. Parameternya adalah dari isinya.

Mengonfigurasi JSopsi deserialisasi ON untuk pengikatan isi

Sumber pengikatan isi menggunakan System.Text.Json untuk deserialisasi. Tidak dimungkinkan untuk mengubah default ini, tetapi JSopsi serialisasi dan deserialisasi ON dapat dikonfigurasi.

Mengonfigurasi JSopsi deserialisasi ON secara global

Opsi yang berlaku secara global untuk aplikasi dapat dikonfigurasi dengan memanggil ConfigureHttpJsonOptions. Contoh berikut mencakup bidang publik dan format JSoutput ON.

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

Karena kode sampel mengonfigurasi serialisasi dan deserialisasi, kode tersebut dapat membaca NameField dan menyertakan NameField dalam output JSON.

Mengonfigurasi JSopsi deserialisasi ON untuk titik akhir

ReadFromJsonAsync memiliki kelebihan beban yang menerima JsonSerializerOptions objek. Contoh berikut mencakup bidang publik dan format JSoutput ON.

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

Karena kode sebelumnya menerapkan opsi yang disesuaikan hanya untuk deserialisasi, output JSON mengecualikan NameField.

Membaca isi permintaan

Baca isi permintaan secara langsung menggunakan HttpContext parameter atau 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();

Kode sebelumnya:

Respons

Handler rute mendukung jenis nilai pengembalian berikut:

  1. IResult berbasis - Ini termasuk Task<IResult> dan ValueTask<IResult>
  2. string - Ini termasuk Task<string> dan ValueTask<string>
  3. T (Jenis lainnya) - Ini termasuk Task<T> dan ValueTask<T>
Nilai hasil Perilaku Content-Type
IResult Kerangka kerja memanggil IResult.ExecuteAsync Diputuskan IResult oleh implementasi
string Kerangka kerja menulis string langsung ke respons text/plain
T (Jenis lainnya) Kerangka kerja JSON-serialisasi respons application/json

Untuk panduan yang lebih mendalam untuk merutekan nilai pengembalian handler, lihat Membuat respons di aplikasi API Minimal

Contoh nilai pengembalian

nilai pengembalian string

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

JSNILAI yang dikembalikan ON

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

Mengembalikan TypedResults

Kode berikut mengembalikan TypedResults:

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

TypedResults Mengembalikan lebih disukai untuk mengembalikan Results. Untuk informasi selengkapnya, lihat TypedResults vs Results.

Nilai pengembalian IResult

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

Contoh berikut menggunakan jenis hasil bawaan untuk mengkustomisasi respons:

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

JSPADA

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

Kode Status Kustom

app.MapGet("/405", () => Results.StatusCode(405));

Teks

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

Lihat Membuat respons di aplikasi API Minimal untuk contoh selengkapnya.

Pengalihan

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

File

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

Hasil bawaan

Pembantu hasil umum ada di Results kelas statis dan TypedResults . TypedResults Mengembalikan lebih disukai untuk mengembalikan Results. Untuk informasi selengkapnya, lihat TypedResults vs Results.

Menyesuaikan hasil

Aplikasi dapat mengontrol respons dengan menerapkan jenis kustom IResult . Kode berikut adalah contoh jenis hasil 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);
    }
}

Sebaiknya tambahkan metode ekstensi untuk Microsoft.AspNetCore.Http.IResultExtensions membuat hasil kustom ini lebih dapat ditemukan.

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

Hasil yang ditik

Antarmuka IResult dapat mewakili nilai yang dikembalikan dari API minimal yang tidak menggunakan dukungan implisit untuk JSON yang menserialisasikan objek yang dikembalikan ke respons HTTP. Kelas Hasil statis digunakan untuk membuat berbagai IResult objek yang mewakili berbagai jenis respons. Misalnya, mengatur kode status respons atau mengalihkan ke URL lain.

Jenis yang IResult diterapkan bersifat publik, memungkinkan pernyataan jenis saat pengujian. Contohnya:

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

Anda dapat melihat jenis pengembalian metode yang sesuai pada kelas TypedResults statis untuk menemukan jenis publik IResult yang benar untuk ditransmisikan.

Lihat Membuat respons di aplikasi API Minimal untuk contoh selengkapnya.

Filter

Lihat Filter di aplikasi API Minimal

Authorization

Rute dapat dilindungi menggunakan kebijakan otorisasi. Ini dapat dideklarasikan melalui [Authorize] atribut atau dengan menggunakan RequireAuthorization metode :

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

Kode sebelumnya dapat ditulis dengan RequireAuthorization:

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

Sampel berikut menggunakan otorisasi berbasis kebijakan:

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

Perbolehkan pengguna yang tidak diaauthenticated untuk mengakses titik akhir

memungkinkan [AllowAnonymous] pengguna yang tidak diaauthenticated untuk mengakses titik akhir:

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


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

CORS

Rute dapat diaktifkan CORS menggunakan kebijakan CORS. CORS dapat dideklarasikan melalui [EnableCors] atribut atau dengan menggunakan RequireCors metode . Sampel berikut mengaktifkan 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();

Untuk informasi selengkapnya, lihat Mengaktifkan Permintaan Lintas Asal (CORS) di ASP.NET Core

Lihat juga

Dokumen ini:

API minimal terdiri dari:

WebApplication

Kode berikut dihasilkan oleh templat ASP.NET Core:

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

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

app.Run();

Kode sebelumnya dapat dibuat melalui dotnet new web pada baris perintah atau memilih templat Web Kosong di Visual Studio.

Kode berikut membuat WebApplication (app) tanpa secara eksplisit membuat WebApplicationBuilder:

var app = WebApplication.Create(args);

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

app.Run();

WebApplication.Create menginisialisasi instans WebApplication baru kelas dengan default yang telah dikonfigurasi sebelumnya.

Bekerja dengan port

Saat aplikasi web dibuat dengan Visual Studio atau dotnet new, Properties/launchSettings.json file dibuat yang menentukan port yang ditanggapi aplikasi. Di sampel pengaturan port berikut, menjalankan aplikasi dari Visual Studio mengembalikan dialog Unable to connect to web server 'AppName'kesalahan . Jalankan sampel perubahan port berikut dari baris perintah.

Bagian berikut mengatur port yang direspons aplikasi.

var app = WebApplication.Create(args);

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

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

Dalam kode sebelumnya, aplikasi merespons port 3000.

Beberapa port

Dalam kode berikut, aplikasi merespons port 3000 dan 4000.

var app = WebApplication.Create(args);

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

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

app.Run();

Mengatur port dari baris perintah

Perintah berikut membuat aplikasi merespons port 7777:

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

Kestrel Jika titik akhir juga dikonfigurasi dalam appsettings.json file, appsettings.json URL yang ditentukan file akan digunakan. Untuk informasi selengkapnya, lihat Kestrel konfigurasi titik akhir

Membaca port dari lingkungan

Kode berikut membaca port dari lingkungan:

var app = WebApplication.Create(args);

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

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

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

Cara yang disukai untuk mengatur port dari lingkungan adalah dengan menggunakan ASPNETCORE_URLS variabel lingkungan, yang ditunjukkan di bagian berikut.

Mengatur port melalui variabel lingkungan ASPNETCORE_URLS

Variabel ASPNETCORE_URLS lingkungan tersedia untuk mengatur port:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS mendukung beberapa URL:

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

Dengarkan semua antarmuka

Sampel berikut menunjukkan mendengarkan di semua antarmuka

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

Dengarkan semua antarmuka menggunakan ASPNETCORE_URLS

Sampel sebelumnya dapat menggunakan ASPNETCORE_URLS

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

Tentukan HTTPS dengan sertifikat pengembangan

var app = WebApplication.Create(args);

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

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

app.Run();

Untuk informasi selengkapnya tentang sertifikat pengembangan, lihat Mempercayai sertifikat pengembangan ASP.NET Core HTTPS di Windows dan macOS.

Tentukan HTTPS menggunakan sertifikat kustom

Bagian berikut menunjukkan cara menentukan sertifikat kustom menggunakan appsettings.json file dan melalui konfigurasi.

Tentukan sertifikat kustom dengan appsettings.json

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

Tentukan sertifikat kustom melalui konfigurasi

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

Menggunakan API sertifikat

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

Membaca lingkungan

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

Untuk informasi selengkapnya menggunakan lingkungan, lihat Menggunakan beberapa lingkungan di ASP.NET Core

Konfigurasi

Kode berikut membaca dari sistem konfigurasi:

var app = WebApplication.Create(args);

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

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

app.Run();

Untuk informasi selengkapnya, lihat Konfigurasi di ASP.NET Core

Pencatatan

Kode berikut menulis pesan ke startup aplikasi masuk:

var app = WebApplication.Create(args);

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

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

app.Run();

Untuk informasi selengkapnya, lihat Pengelogan di .NET Core dan ASP.NET Core

Mengakses kontainer Injeksi Dependensi (DI)

Kode berikut menunjukkan cara mendapatkan layanan dari kontainer DI selama pengaktifan aplikasi:


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

Untuk informasi lebih lanjut, lihat Injeksi dependensi di ASP.NET Core.

WebApplicationBuilder

Bagian ini berisi kode sampel menggunakan WebApplicationBuilder.

Mengubah akar konten, nama aplikasi, dan lingkungan

Kode berikut mengatur akar konten, nama aplikasi, dan lingkungan:

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 menginisialisasi instans baru kelas WebApplicationBuilder dengan default yang telah dikonfigurasi sebelumnya.

Untuk informasi selengkapnya, lihat gambaran umum dasar-dasar ASP.NET Core

Mengubah akar konten, nama aplikasi, dan lingkungan menurut variabel lingkungan atau baris perintah

Tabel berikut ini memperlihatkan variabel lingkungan dan argumen baris perintah yang digunakan untuk mengubah akar konten, nama aplikasi, dan lingkungan:

fitur Variabel lingkungan Argumen baris perintah
Nama aplikasi ASPNETCORE_APPLICATIONNAME --applicationName
Nama lingkungan ASPNETCORE_ENVIRONMENT --Lingkungan
Akar konten ASPNETCORE_CONTENTROOT --contentRoot

Menambahkan penyedia konfigurasi

Contoh berikut menambahkan penyedia konfigurasi INI:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

Untuk informasi terperinci, lihat Penyedia konfigurasi file di Konfigurasi di ASP.NET Core.

Membaca konfigurasi

Secara default WebApplicationBuilder , konfigurasi baca dari beberapa sumber, termasuk:

  • appSettings.json dan appSettings.{environment}.json
  • Variabel lingkungan
  • Baris perintah

Untuk daftar lengkap sumber konfigurasi yang dibaca, lihat Konfigurasi default dalam Konfigurasi di ASP.NET Core

Kode berikut membaca HelloKey dari konfigurasi dan menampilkan nilai di / titik akhir. Jika nilai konfigurasi null, "Halo" ditetapkan ke message:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Membaca lingkungan

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Menambahkan penyedia pengelogan

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

Menambahkan layanan

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

Menyesuaikan IHostBuilder

Metode ekstensi yang ada di IHostBuilder dapat diakses menggunakan properti 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();

Menyesuaikan IWebHostBuilder

Metode ekstensi pada IWebHostBuilder dapat diakses menggunakan properti 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();

Mengubah akar web

Secara default, akar web relatif terhadap akar konten di wwwroot folder. Akar web adalah tempat middleware file statis mencari file statis. Akar web dapat diubah dengan WebHostOptions, baris perintah, atau dengan UseWebRoot metode :

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

var app = builder.Build();

app.Run();

Kontainer injeksi dependensi kustom (DI)

Contoh berikut menggunakan 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();

Tambahkan Middleware

Middleware ASP.NET Core yang ada dapat dikonfigurasi pada WebApplication:

var app = WebApplication.Create(args);

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

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

app.Run();

Untuk informasi selengkapnya, lihat ASP.NET Core Middleware

Halaman pengecualian pengembang

WebApplication.CreateBuilder menginisialisasi instans WebApplicationBuilder baru kelas dengan default yang telah dikonfigurasi sebelumnya. Halaman pengecualian pengembang diaktifkan dalam default yang telah dikonfigurasi sebelumnya. Saat kode berikut dijalankan di lingkungan pengembangan, navigasi untuk / merender halaman ramah yang menunjukkan pengecualian.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Run();

ASP.NET Core Middleware

Tabel berikut mencantumkan beberapa middleware yang sering digunakan dengan API minimal.

Middleware Deskripsi API
Autentikasi Menyediakan dukungan autentikasi. UseAuthentication
Otorisasi Menyediakan dukungan otorisasi. UseAuthorization
CORS Mengonfigurasi Cross-Origin Resource Sharing. UseCors
Handler Pengecualian Secara global menangani pengecualian yang dilemparkan oleh alur middleware. UseExceptionHandler
Header yang Diteruskan Meneruskan header yang diproksi ke permintaan saat ini. UseForwardedHeaders
Pengalihan HTTPS Mengalihkan semua permintaan HTTP ke HTTPS. UseHttpsRedirection
HTTP Strict Transport Security (HSTS) Middleware peningkatan keamanan yang menambahkan header respons khusus. UseHsts
Pengelogan Permintaan Menyediakan dukungan untuk mencatat permintaan dan respons HTTP. UseHttpLogging
Pengelogan Permintaan W3C Menyediakan dukungan untuk mencatat permintaan dan respons HTTP dalam format W3C. UseW3CLogging
Penembolokan Respons Menyediakan dukungan untuk respons penembolokan. UseResponseCaching
Pemadatan Respons Memberikan dukungan untuk memadatkan respons. UseResponseCompression
Sesi Menyediakan dukungan untuk mengelola sesi pengguna. UseSession
File Statis Menyediakan dukungan untuk menyajikan file statis dan penjelajahan direktori. UseStaticFiles, UseFileServer
WebSocket Mengaktifkan protokol WebSocket. UseWebSockets

Penanganan permintaan

Bagian berikut mencakup perutean, pengikatan parameter, dan respons.

Perutean

Dukungan yang dikonfigurasi WebApplicationMap{Verb} dan 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();

Penangan Rute

Penangan rute adalah metode yang dijalankan saat rute cocok. Penangan rute dapat menjadi fungsi dari bentuk apa pun, termasuk sinkron atau asinkron. Penangan rute dapat berupa ekspresi lambda, fungsi lokal, metode instans, atau metode statis.

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

Fungsi lokal

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

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

app.MapGet("/", LocalFunction);

app.Run();

Metode instans

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

Metode statis

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

Titik akhir dapat diberikan nama untuk menghasilkan URL ke titik akhir. Menggunakan titik akhir bernama menghindari harus melakukan jalur kode keras di aplikasi:

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

Kode sebelumnya ditampilkan The link to the hello endpoint is /hello dari / titik akhir.

CATATAN: Nama titik akhir peka huruf besar/kecil.

Nama titik akhir:

  • Harus unik secara global.
  • Digunakan sebagai id operasi OpenAPI saat dukungan OpenAPI diaktifkan. Untuk informasi selengkapnya, lihat OpenAPI.

Parameter Rute

Parameter rute dapat ditangkap sebagai bagian dari definisi pola rute:

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

Kode sebelumnya dikembalikan The user id is 3 and book id is 7 dari URI /users/3/books/7.

Handler rute dapat mendeklarasikan parameter yang akan diambil. Ketika permintaan dibuat rute dengan parameter yang dinyatakan untuk diambil, parameter diurai dan diteruskan ke handler. Ini memudahkan untuk menangkap nilai dengan cara yang aman. Dalam kode sebelumnya, userId dan bookId keduanya int.

Dalam kode sebelumnya, jika salah satu nilai rute tidak dapat dikonversi ke int, pengecualian akan dilemparkan. Permintaan /users/hello/books/3 GET melemparkan pengecualian berikut:

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

Kartubebas dan tangkap semua rute

Berikut ini menangkap semua rute yang Routing to hello dikembalikan dari titik akhir '/posts/hello':

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

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

app.Run();

Batasan rute

Batasan rute membatasi perilaku pencocokan rute.

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

Tabel berikut menunjukkan templat rute sebelumnya dan perilakunya:

Templat Rute Contoh URI yang Cocok
/todos/{id:int} /todos/1
/todos/{text} /todos/something
/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

Untuk informasi selengkapnya, lihat Referensi batasan rute dalam Perutean di ASP.NET Core.

Pengikatan Parameter

Pengikatan parameter adalah proses mengonversi data permintaan menjadi parameter yang diekspresikan dengan kuat yang dinyatakan oleh penangan rute. Sumber pengikatan menentukan dari mana parameter terikat. Sumber pengikatan dapat eksplisit atau disimpulkan berdasarkan metode HTTP dan jenis parameter.

Sumber pengikatan yang didukung:

  • Nilai rute
  • Untai kueri
  • Header
  • Isi (sebagai JSAKTIF)
  • Layanan yang disediakan oleh injeksi dependensi
  • Kustom

Catatan

Pengikatan dari nilai formulir tidak didukung secara asli di .NET.

Contoh handler rute GET berikut menggunakan beberapa sumber pengikatan parameter ini:

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

Tabel berikut menunjukkan hubungan antara parameter yang digunakan dalam contoh sebelumnya dan sumber pengikatan terkait.

Parameter Sumber Pengikatan
id nilai rute
page string kueri
customHeader {i>header
service Disediakan oleh injeksi dependensi

Metode HTTP , HEAD, OPTIONS, dan DELETE tidak secara implisit GETmengikat dari isi. Untuk mengikat dari isi (sebagai JSAKTIF) untuk metode HTTP ini, ikat secara eksplisit dengan [FromBody] atau baca dari HttpRequest.

Contoh handler rute POST berikut menggunakan sumber pengikatan isi (sebagai JSAKTIF) untuk person parameter :

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

record Person(string Name, int Age);

Parameter dalam contoh sebelumnya semuanya terikat dari data permintaan secara otomatis. Untuk menunjukkan kenyamanan yang disediakan pengikatan parameter, contoh penanganan rute berikut menunjukkan cara membaca data permintaan langsung dari permintaan:

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

    // ...
});

Pengikatan Parameter Eksplisit

Atribut dapat digunakan untuk secara eksplisit menyatakan dari mana parameter terikat.

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();


app.MapGet("/{id}", ([FromRoute] int id,
                     [FromQuery(Name = "p")] int page,
                     [FromServices] Service service,
                     [FromHeader(Name = "Content-Type")] string contentType) 
                     => {});

class Service { }

record Person(string Name, int Age);
Parameter Sumber Pengikatan
id nilai rute dengan nama id
page string kueri dengan nama "p"
service Disediakan oleh injeksi dependensi
contentType header dengan nama "Content-Type"

Catatan

Pengikatan dari nilai formulir tidak didukung secara asli di .NET.

Pengikatan parameter dengan DI

Pengikatan parameter untuk API minimal mengikat parameter melalui injeksi dependensi saat jenis dikonfigurasi sebagai layanan. Tidak perlu menerapkan [FromServices] atribut secara eksplisit ke parameter. Dalam kode berikut, kedua tindakan mengembalikan waktu:

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

Parameter opsional

Parameter yang dideklarasikan dalam handler rute diperlakukan sesuai kebutuhan:

  • Jika permintaan cocok dengan rute, handler rute hanya berjalan jika semua parameter yang diperlukan disediakan dalam permintaan.
  • Kegagalan untuk memberikan semua parameter yang diperlukan menghasilkan kesalahan.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");

app.Run();
URI hasil
/products?pageNumber=3 3 dikembalikan
/products BadHttpRequestException: Parameter yang diperlukan "int pageNumber" tidak disediakan dari string kueri.
/products/1 Kesalahan HTTP 404, tidak ada rute yang cocok

Untuk membuat pageNumber opsional, tentukan jenis sebagai opsional atau berikan nilai default:

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

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

string ListProducts(int pageNumber = 1) => $"Requesting page {pageNumber}";

app.MapGet("/products2", ListProducts);

app.Run();
URI hasil
/products?pageNumber=3 3 dikembalikan
/products 1 dikembalikan
/products2 1 dikembalikan

Nilai nullable dan default sebelumnya berlaku untuk semua sumber:

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

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

app.Run();

Kode sebelumnya memanggil metode dengan produk null jika tidak ada isi permintaan yang dikirim.

CATATAN: Jika data yang tidak valid disediakan dan parameter dapat diubah ke null, handler rute tidak dijalankan.

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

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

app.Run();
URI hasil
/products?pageNumber=3 3 Kembali
/products 1 Kembali
/products?pageNumber=two BadHttpRequestException: Gagal mengikat parameter "Nullable<int> pageNumber" dari "dua".
/products/two Kesalahan HTTP 404, tidak ada rute yang cocok

Lihat bagian Kegagalan Pengikatan untuk informasi selengkapnya.

Jenis khusus

Jenis berikut terikat tanpa atribut eksplisit:

  • HttpContext: Konteks yang menyimpan semua informasi tentang permintaan atau respons HTTP saat ini:

    app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
    
  • HttpRequest dan HttpResponse: Permintaan HTTP dan respons HTTP:

    app.MapGet("/", (HttpRequest request, HttpResponse response) =>
        response.WriteAsync($"Hello World {request.Query["name"]}"));
    
  • CancellationToken: Token pembatalan yang terkait dengan permintaan HTTP saat ini:

    app.MapGet("/", async (CancellationToken cancellationToken) => 
        await MakeLongRunningRequestAsync(cancellationToken));
    
  • ClaimsPrincipal: Pengguna yang terkait dengan permintaan, terikat dari HttpContext.User:

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

Pengikatan Kustom

Ada dua cara untuk menyesuaikan pengikatan parameter:

  1. Untuk sumber pengikatan rute, kueri, dan header, ikat jenis kustom dengan menambahkan metode statis TryParse untuk jenis tersebut.
  2. Kontrol proses pengikatan dengan menerapkan BindAsync metode pada jenis.

TryParse

TryParse memiliki dua API:

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

Kode berikut ditampilkan Point: 12.3, 10.1 dengan 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 memiliki API berikut:

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

Kode berikut ditampilkan SortBy:xyz, SortDirection:Desc, CurrentPage:99 dengan 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
}

Kegagalan pengikatan

Saat pengikatan gagal, kerangka kerja mencatat pesan debug dan mengembalikan berbagai kode status ke klien tergantung pada mode kegagalan.

Mode kegagalan Jenis Parameter Nullable Sumber Pengikatan Kode status
{ParameterType}.TryParse menghasilkan false yes route/query/header 400
{ParameterType}.BindAsync menghasilkan null yes custom 400
{ParameterType}.BindAsync Melempar tidak masalah custom 500
Kegagalan untuk mendeserialisasi JSisi ON tidak masalah body 400
Tipe isi salah (bukan application/json) tidak masalah body 415

Prioritas Pengikatan

Aturan untuk menentukan sumber pengikatan dari parameter:

  1. Atribut eksplisit yang ditentukan pada parameter (Dari* atribut) dalam urutan berikut:
    1. Nilai rute: [FromRoute]
    2. String kueri: [FromQuery]
    3. Header: [FromHeader]
    4. Tubuh: [FromBody]
    5. Layanan: [FromServices]
  2. Jenis khusus
    1. HttpContext
    2. HttpRequestAku akan menemuinya.HttpContext.Request
    3. HttpResponseAku akan menemuinya.HttpContext.Response
    4. ClaimsPrincipalAku akan menemuinya.HttpContext.User
    5. CancellationTokenAku akan menemuinya.HttpContext.RequestAborted
  3. Jenis parameter memiliki metode yang valid BindAsync .
  4. Jenis parameter adalah string atau memiliki metode yang valid TryParse .
    1. Jika nama parameter ada di templat rute misalnya app.Map("/todo/{id}", (int id) => {});, maka terikat dari rute.
    2. Terikat dari string kueri.
  5. Jika jenis parameter adalah layanan yang disediakan oleh injeksi dependensi, ia menggunakan layanan tersebut sebagai sumbernya.
  6. Parameternya adalah dari isinya.

Menyesuaikan JSpengikatan ON

Sumber pengikatan isi menggunakan System.Text.Json untuk de-serialisasi. Tidak dimungkinkan untuk mengubah default ini, tetapi pengikatan dapat disesuaikan menggunakan teknik lain yang dijelaskan sebelumnya. Untuk mengkustomisasi JSopsi serializer ON, gunakan kode yang mirip dengan yang berikut ini:

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

Kode sebelumnya:

  • Mengonfigurasi opsi ON default JSinput dan output.
  • Mengembalikan ON berikut JS
    {
      "id": 1,
      "name": "Joe Smith"
    }
    
    Saat memposting
    {
      "Id": 1,
      "Name": "Joe Smith"
    }
    

Membaca isi permintaan

Baca isi permintaan secara langsung menggunakan HttpContext parameter atau 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();

Kode sebelumnya:

Respons

Handler rute mendukung jenis nilai pengembalian berikut:

  1. IResult berbasis - Ini termasuk Task<IResult> dan ValueTask<IResult>
  2. string - Ini termasuk Task<string> dan ValueTask<string>
  3. T (Jenis lainnya) - Ini termasuk Task<T> dan ValueTask<T>
Nilai hasil Perilaku Content-Type
IResult Kerangka kerja memanggil IResult.ExecuteAsync Diputuskan IResult oleh implementasi
string Kerangka kerja menulis string langsung ke respons text/plain
T (Jenis lainnya) Kerangka kerja akan JSON menserialisasikan respons application/json

Contoh nilai pengembalian

nilai pengembalian string

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

JSNILAI yang dikembalikan ON

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

Nilai pengembalian IResult

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

Contoh berikut menggunakan jenis hasil bawaan untuk mengkustomisasi respons:

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);
JSPADA
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
Kode Status Kustom
app.MapGet("/405", () => Results.StatusCode(405));
Teks
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();
Pengalihan
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
File
app.MapGet("/download", () => Results.File("myfile.text"));

Hasil bawaan

Pembantu hasil umum ada di Microsoft.AspNetCore.Http.Results kelas statis.

Deskripsi Jenis respons Kode status API
JSMenulis respons ON dengan opsi tingkat lanjut application/json 200 Results.Json
JSMenulis respons ON application/json 200 Results.Ok
Menulis respons teks text/plain (default), dapat dikonfigurasi 200 Results.Text
Menulis respons sebagai byte application/octet-stream (default), dapat dikonfigurasi 200 Results.Byte
Menulis aliran byte ke respons application/octet-stream (default), dapat dikonfigurasi 200 Results.Stream
Mengalirkan file ke respons untuk diunduh dengan header disposisi konten application/octet-stream (default), dapat dikonfigurasi 200 Results.File
Atur kode status ke 404, dengan respons ON opsional JS T/A 404 Results.NotFound
Atur kode status ke 204 T/A 204 Results.NoContent
Atur kode status ke 422, dengan respons ON opsional JS T/A 422 Results.UnprocessableEntity
Atur kode status ke 400, dengan respons ON opsional JS T/A 400 Results.BadRequest
Atur kode status ke 409, dengan respons ON opsional JS T/A 409 Results.Conflict
Menulis detail JSmasalah PADA objek ke respons T/A 500 (default), dapat dikonfigurasi Results.Problem
Tulis detail JSmasalah PADA objek ke respons dengan kesalahan validasi T/A N/A, dapat dikonfigurasi Results.ValidationProblem

Menyesuaikan hasil

Aplikasi dapat mengontrol respons dengan menerapkan jenis kustom IResult . Kode berikut adalah contoh jenis hasil 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);
    }
}

Sebaiknya tambahkan metode ekstensi untuk Microsoft.AspNetCore.Http.IResultExtensions membuat hasil kustom ini lebih dapat ditemukan.

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

Authorization

Rute dapat dilindungi menggunakan kebijakan otorisasi. Ini dapat dideklarasikan melalui [Authorize] atribut atau dengan menggunakan RequireAuthorization metode :

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

Kode sebelumnya dapat ditulis dengan RequireAuthorization:

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

Sampel berikut menggunakan otorisasi berbasis kebijakan:

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

Perbolehkan pengguna yang tidak diaauthenticated untuk mengakses titik akhir

memungkinkan [AllowAnonymous] pengguna yang tidak diaauthenticated untuk mengakses titik akhir:

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


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

CORS

Rute dapat diaktifkan CORS menggunakan kebijakan CORS. CORS dapat dideklarasikan melalui [EnableCors] atribut atau dengan menggunakan RequireCors metode . Sampel berikut mengaktifkan 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();

Untuk informasi selengkapnya, lihat Mengaktifkan Permintaan Lintas Asal (CORS) di ASP.NET Core

Lihat juga

Dukungan OpenAPI dalam API minimal