Tutorial: Membuat API minimal dengan ASP.NET Core

Catatan

Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Penting

Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.

Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Oleh Rick Anderson dan Tom Dykstra

API minimal dirancang untuk membuat API HTTP dengan dependensi minimal. Mereka ideal untuk layanan mikro dan aplikasi yang hanya ingin menyertakan file, fitur, dan dependensi minimum di ASP.NET Core.

Tutorial ini mengajarkan dasar-dasar membangun API minimal dengan ASP.NET Core. Pendekatan lain untuk membuat API di ASP.NET Core adalah menggunakan pengontrol. Untuk bantuan dalam memilih antara API minimal dan API berbasis pengontrol, lihat Gambaran umum API. Untuk tutorial tentang membuat proyek API berdasarkan pengontrol yang berisi lebih banyak fitur, lihat Membuat API web.

Gambaran Umum

Tutorial ini membuat API berikut:

API Deskripsi Isi permintaan Isi respons
GET /todoitems Dapatkan semua item yang harus dilakukan Tidak Array item yang harus dilakukan
GET /todoitems/complete Selesaikan item yang harus dilakukan Tidak Array item yang harus dilakukan
GET /todoitems/{id} Mendapatkan item menurut ID Tidak Item yang harus dilakukan
POST /todoitems Menambahkan item baru Item yang harus dilakukan Item yang harus dilakukan
PUT /todoitems/{id} Memperbarui item yang sudah ada Item yang harus dilakukan Tidak
DELETE /todoitems/{id}     Menghapus item Tidak Tidak

Prasyarat

Membuat proyek API

  • Mulai Visual Studio 2022 dan pilih Buat proyek baru.

  • Dalam dialog Buat proyek baru:

    • Masukkan Empty di kotak pencarian Cari templat .
    • Pilih templat ASP.NET Core Empty dan pilih Berikutnya.

    Visual Studio Membuat proyek baru

  • Beri nama proyek TodoApi dan pilih Berikutnya.

  • Dalam dialog Informasi tambahan:

    • Pilih .NET 8.0 (Dukungan Jangka Panjang)
    • Hapus centang Jangan gunakan pernyataan tingkat atas
    • Pilih Buat

    Informasi Tambahan

Memeriksa kode

File Program.cs berisi kode berikut:

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

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

app.Run();

Kode sebelumnya:

Menjalankan aplikasi

Tekan Ctrl+F5 untuk menjalankan tanpa debugger.

Visual Studio menampilkan dialog berikut:

Proyek ini dikonfigurasi untuk menggunakan SSL. Untuk menghindari peringatan SSL di browser, Anda dapat memilih untuk mempercayai sertifikat yang ditandatangani sendiri yang telah dihasilkan IIS Express. Apakah Anda ingin mempercayai sertifikat IIS Express SSL?

Pilih Ya jika Anda mempercayai sertifikat IIS Express SSL.

Dialog berikut ditampilkan:

Dialog peringatan keamanan

Pilih Ya jika Anda setuju untuk mempercayai sertifikat pengembangan tersebut.

Untuk informasi tentang mempercayai browser Firefox, lihat Kesalahan sertifikat Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.

Visual Studio meluncurkan Kestrel server web dan membuka jendela browser.

Hello World! ditampilkan di browser. File Program.cs berisi aplikasi minimal tetapi lengkap.

Tutup jendela browser.

Menambahkan paket NuGet

Paket NuGet harus ditambahkan untuk mendukung database dan diagnostik yang digunakan dalam tutorial ini.

  • Dari menu Alat, pilih Pengelola > Paket NuGet Kelola Paket NuGet untuk Solusi.
  • Pilih tab Telusuri.
  • Masukkan Microsoft.EntityFrameworkCore.InMemory di kotak pencarian, lalu pilih Microsoft.EntityFrameworkCore.InMemory.
  • Pilih kotak centang Proyek di panel kanan lalu pilih Instal.
  • Ikuti instruksi sebelumnya untuk menambahkan Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore paket.

Kelas konteks model dan database

  • Di folder proyek, buat file bernama Todo.cs dengan kode berikut:
public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

Kode sebelumnya membuat model untuk aplikasi ini. Model adalah kelas yang mewakili data yang dikelola aplikasi.

  • Buat file bernama TodoDb.cs dengan kode berikut:
using Microsoft.EntityFrameworkCore;

class TodoDb : DbContext
{
    public TodoDb(DbContextOptions<TodoDb> options)
        : base(options) { }

    public DbSet<Todo> Todos => Set<Todo>();
}

Kode sebelumnya mendefinisikan konteks database, yang merupakan kelas utama yang mengoordinasikan fungsionalitas Kerangka Kerja Entitas untuk model data. Kelas ini berasal dari Microsoft.EntityFrameworkCore.DbContext kelas .

Menambahkan kode API

  • Ganti isi file Program.cs dengan kode berikut:
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

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

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.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.NoContent();
    }

    return Results.NotFound();
});

app.Run();

Kode yang disorot berikut menambahkan konteks database ke kontainer injeksi dependensi (DI) dan memungkinkan menampilkan pengecualian terkait database:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

Kontainer DI menyediakan akses ke konteks database dan layanan lainnya.

Tutorial ini menggunakan file Endpoints Explorer dan .http untuk menguji API.

Menguji data posting

Kode berikut dalam Program.cs membuat titik /todoitems akhir HTTP POST yang menambahkan data ke database dalam memori:

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

Jalankan aplikasi. Browser menampilkan kesalahan 404 karena tidak ada lagi / titik akhir.

Titik akhir POST akan digunakan untuk menambahkan data ke aplikasi.

  • Pilih Tampilkan>Penjelajah Titik Akhir Windows>Lainnya.

  • Klik kanan titik akhir POST dan pilih Buat permintaan.

    Menu konteks Penjelajah Titik Akhir menyoroti item menu Hasilkan Permintaan.

    File baru dibuat di folder proyek bernama TodoApi.http, dengan konten yang mirip dengan contoh berikut:

    @TodoApi_HostAddress = https://localhost:7031
    
    Post {{TodoApi_HostAddress}}/todoitems
    
    ###
    
    • Baris pertama membuat variabel yang digunakan untuk semua titik akhir.
    • Baris berikutnya menentukan permintaan POST.
    • Baris hashtag triple (###) adalah pemisah permintaan: apa yang terjadi setelahnya adalah untuk permintaan yang berbeda.
  • Permintaan POST memerlukan header dan isi. Untuk menentukan bagian permintaan tersebut, tambahkan baris berikut segera setelah baris permintaan POST:

    Content-Type: application/json
    
    {
      "name":"walk dog",
      "isComplete":true
    }
    

    Kode sebelumnya menambahkan header Jenis Konten dan JSisi permintaan ON. File TodoApi.http sekarang akan terlihat seperti contoh berikut, tetapi dengan nomor port Anda:

    @TodoApi_HostAddress = https://localhost:7057
    
    Post {{TodoApi_HostAddress}}/todoitems
    Content-Type: application/json
    
    {
      "name":"walk dog",
      "isComplete":true
    }
    
    ###
    
  • Jalankan aplikasi.

  • Pilih tautan Kirim permintaan yang berada di atas POST baris permintaan.

    Jendela file .http dengan tautan eksekusi disorot.

    Permintaan POST dikirim ke aplikasi dan respons ditampilkan di panel Respons .

    Jendela file .http dengan respons dari permintaan POST.

Memeriksa titik akhir GET

Aplikasi sampel mengimplementasikan beberapa titik akhir GET dengan memanggil MapGet:

API Deskripsi Isi permintaan Isi respons
GET /todoitems Dapatkan semua item yang harus dilakukan Tidak Array item yang harus dilakukan
GET /todoitems/complete Dapatkan semua item tugas yang sudah selesai Tidak Array item yang harus dilakukan
GET /todoitems/{id} Mendapatkan item menurut ID Tidak Item yang harus dilakukan
app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

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

Menguji titik akhir GET

Uji aplikasi dengan memanggil GET titik akhir dari browser atau dengan menggunakan Endpoints Explorer. Langkah-langkah berikut adalah untuk Penjelajah Titik Akhir.

  • Di Penjelajah Titik Akhir, klik kanan titik akhir GET pertama, dan pilih Buat permintaan.

    Konten berikut ditambahkan ke TodoApi.http file:

    Get {{TodoApi_HostAddress}}/todoitems
    
    ###
    
  • Pilih tautan Kirim permintaan yang berada di atas baris permintaan baru GET .

    Permintaan GET dikirim ke aplikasi dan respons ditampilkan di panel Respons .

  • Isi respons mirip dengan ON berikut JS:

    [
      {
        "id": 1,
        "name": "walk dog",
        "isComplete": true
      }
    ]
    
  • Di Penjelajah Titik Akhir, klik /todoitems/{id}kanan titik akhir GET dan pilih Hasilkan permintaan. Konten berikut ditambahkan ke TodoApi.http file:

    GET {{TodoApi_HostAddress}}/todoitems/{id}
    
    ###
    
  • Ganti {id} dengan 1.

  • Pilih tautan Kirim permintaan yang berada di atas baris permintaan GET baru.

    Permintaan GET dikirim ke aplikasi dan respons ditampilkan di panel Respons .

  • Isi respons mirip dengan ON berikut JS:

    {
      "id": 1,
      "name": "walk dog",
      "isComplete": true
    }
    

Aplikasi ini menggunakan database dalam memori. Jika aplikasi dimulai ulang, permintaan GET tidak mengembalikan data apa pun. Jika tidak ada data yang dikembalikan, KIRIM data ke aplikasi dan coba permintaan GET lagi.

Mengembalikan nilai

ASP.NET Core secara otomatis menserialisasikan objek ke JSAKTIF dan menulis JSON ke dalam isi pesan respons. Kode respons untuk jenis pengembalian ini adalah 200 OK, dengan asumsi tidak ada pengecualian yang tidak tertangani. Pengecualian yang tidak tertangani diterjemahkan ke dalam kesalahan 5xx.

Jenis pengembalian dapat mewakili berbagai kode status HTTP. Misalnya, GET /todoitems/{id} dapat mengembalikan dua nilai status yang berbeda:

  • Jika tidak ada item yang cocok dengan ID yang diminta, metode mengembalikan kode kesalahan statusNotFound 404.
  • Jika tidak, metode mengembalikan 200 dengan JSisi respons ON. Mengembalikan item hasil dalam respons HTTP 200.

Memeriksa titik akhir PUT

Aplikasi sampel mengimplementasikan satu titik akhir PUT menggunakan MapPut:

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

Metode ini mirip MapPost dengan metode , kecuali menggunakan HTTP PUT. Respons yang berhasil mengembalikan 204 (Tanpa Konten). Menurut spesifikasi HTTP, permintaan PUT mengharuskan klien untuk mengirim seluruh entitas yang diperbarui, bukan hanya perubahan. Untuk mendukung pembaruan parsial, gunakan HTTP PATCH.

Menguji titik akhir PUT

Sampel ini menggunakan database dalam memori yang harus diinisialisasi setiap kali aplikasi dimulai. Harus ada item dalam database sebelum Anda melakukan panggilan PUT. Panggil GET untuk memastikan ada item dalam database sebelum melakukan panggilan PUT.

Perbarui item yang harus dilakukan yang memiliki Id = 1 dan atur namanya ke "feed fish".

  • Di Penjelajah Titik Akhir, klik kanan titik akhir PUT , dan pilih Buat permintaan.

    Konten berikut ditambahkan ke TodoApi.http file:

    Put {{TodoApi_HostAddress}}/todoitems/{id}
    
    ###
    
  • Di baris permintaan PUT, ganti {id} dengan 1.

  • Tambahkan baris berikut segera setelah baris permintaan PUT:

    Content-Type: application/json
    
    {
      "id": 1,
      "name": "feed fish",
      "isComplete": false
    }
    

    Kode sebelumnya menambahkan header Jenis Konten dan JSisi permintaan ON.

  • Pilih tautan Kirim permintaan yang berada di atas baris permintaan PUT baru.

    Permintaan PUT dikirim ke aplikasi dan respons ditampilkan di panel Respons . Isi respons kosong, dan kode status adalah 204.

Memeriksa dan menguji titik akhir DELETE

Aplikasi sampel mengimplementasikan satu titik akhir DELETE menggunakan MapDelete:

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

    return Results.NotFound();
});
  • Di Penjelajah Titik Akhir, klik kanan titik akhir DELETE dan pilih Hasilkan permintaan.

    Permintaan DELETE ditambahkan ke TodoApi.http.

  • Ganti {id} di baris permintaan DELETE dengan 1. Permintaan DELETE akan terlihat seperti contoh berikut:

    DELETE {{TodoApi_HostAddress}}/todoitems/1
    
    ###
    
  • Pilih tautan Kirim permintaan untuk permintaan DELETE.

    Permintaan DELETE dikirim ke aplikasi dan respons ditampilkan di panel Respons . Isi respons kosong, dan kode status adalah 204.

Menggunakan API MapGroup

Kode aplikasi sampel mengulangi awalan todoitems URL setiap kali menyiapkan titik akhir. API sering memiliki grup titik akhir dengan awalan URL umum, dan metode ini tersedia untuk membantu mengatur grup tersebut MapGroup . Ini mengurangi kode berulang dan memungkinkan untuk menyesuaikan seluruh grup titik akhir dengan satu panggilan ke metode seperti RequireAuthorization dan WithMetadata.

Ganti isi Program.cs dengan kode berikut:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", async (TodoDb db) =>
    await db.Todos.ToListAsync());

todoItems.MapGet("/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

todoItems.MapGet("/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(todo)
            : Results.NotFound());

todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return Results.NoContent();
    }

    return Results.NotFound();
});

app.Run();

Kode sebelumnya memiliki perubahan berikut:

  • var todoItems = app.MapGroup("/todoitems"); Menambahkan untuk menyiapkan grup menggunakan awalan /todoitemsURL .
  • Mengubah semua metode menjadi app.Map<HttpVerb>todoItems.Map<HttpVerb>.
  • Menghapus awalan /todoitems URL dari Map<HttpVerb> panggilan metode.

Uji titik akhir untuk memverifikasi bahwa titik akhir berfungsi sama.

Menggunakan API TypedResults

TypedResults Mengembalikan daripada Results memiliki beberapa keuntungan, termasuk uji coba dan secara otomatis mengembalikan metadata jenis respons untuk OpenAPI untuk menjelaskan titik akhir. Untuk informasi selengkapnya, lihat TypedResults vs Results.

Metode ini Map<HttpVerb> dapat memanggil metode handler rute alih-alih menggunakan lambda. Untuk melihat contoh, perbarui Program.cs dengan kode berikut:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

app.Run();

static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)
{
    return await db.Todos.FindAsync(id)
        is Todo todo
            ? TypedResults.Ok(todo)
            : TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

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

static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return TypedResults.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return TypedResults.NoContent();
    }

    return TypedResults.NotFound();
}

Kode Map<HttpVerb> sekarang memanggil metode alih-alih lambda:

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

Metode ini mengembalikan objek yang mengimplementasikan IResult dan didefinisikan oleh TypedResults:

static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)
{
    return await db.Todos.FindAsync(id)
        is Todo todo
            ? TypedResults.Ok(todo)
            : TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

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

static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return TypedResults.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return TypedResults.NoContent();
    }

    return TypedResults.NotFound();
}

Pengujian unit dapat memanggil metode ini dan menguji bahwa mereka mengembalikan jenis yang benar. Misalnya, jika metodenya adalah GetAllTodos:

static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

Kode pengujian unit dapat memverifikasi bahwa objek jenis Ok<Todo[]> dikembalikan dari metode handler. Contohnya:

public async Task GetAllTodos_ReturnsOkOfTodosResult()
{
    // Arrange
    var db = CreateDbContext();

    // Act
    var result = await TodosApi.GetAllTodos(db);

    // Assert: Check for the correct returned type
    Assert.IsType<Ok<Todo[]>>(result);
}

Cegah postingan berlebihan

Saat ini aplikasi sampel mengekspos seluruh Todo objek. Aplikasi produksi Dalam aplikasi produksi, subset model sering digunakan untuk membatasi data yang dapat diinput dan dikembalikan. Ada beberapa alasan di balik ini dan keamanan adalah yang utama. Subset model biasanya disebut sebagai Objek Transfer Data (DTO), model input, atau model tampilan. DTO digunakan dalam artikel ini.

DTO dapat digunakan untuk:

  • Cegah postingan berlebihan.
  • Sembunyikan properti yang seharusnya tidak dilihat klien.
  • Hilangkan beberapa properti untuk mengurangi ukuran payload.
  • Meratakan grafik objek yang berisi objek berlapis. Grafik objek yang diratakan bisa lebih nyaman untuk klien.

Untuk menunjukkan pendekatan DTO, perbarui Todo kelas untuk menyertakan bidang rahasia:

public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
    public string? Secret { get; set; }
}

Bidang rahasia perlu disembunyikan dari aplikasi ini, tetapi aplikasi administratif dapat memilih untuk mengeksposnya.

Verifikasi bahwa Anda dapat memposting dan mendapatkan bidang rahasia.

Buat file bernama TodoItemDTO.cs dengan kode berikut:

public class TodoItemDTO
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }

    public TodoItemDTO() { }
    public TodoItemDTO(Todo todoItem) =>
    (Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}

Ganti konten Program.cs file dengan kode berikut untuk menggunakan model DTO ini:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

RouteGroupBuilder todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

app.Run();

static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.Select(x => new TodoItemDTO(x)).ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db) {
    return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).Select(x => new TodoItemDTO(x)).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)
{
    return await db.Todos.FindAsync(id)
        is Todo todo
            ? TypedResults.Ok(new TodoItemDTO(todo))
            : TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(TodoItemDTO todoItemDTO, TodoDb db)
{
    var todoItem = new Todo
    {
        IsComplete = todoItemDTO.IsComplete,
        Name = todoItemDTO.Name
    };

    db.Todos.Add(todoItem);
    await db.SaveChangesAsync();

    todoItemDTO = new TodoItemDTO(todoItem);

    return TypedResults.Created($"/todoitems/{todoItem.Id}", todoItemDTO);
}

static async Task<IResult> UpdateTodo(int id, TodoItemDTO todoItemDTO, TodoDb db)
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return TypedResults.NotFound();

    todo.Name = todoItemDTO.Name;
    todo.IsComplete = todoItemDTO.IsComplete;

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return TypedResults.NoContent();
    }

    return TypedResults.NotFound();
}

Verifikasi bahwa Anda bisa memposting dan mendapatkan semua bidang kecuali bidang rahasia.

Pemecahan masalah dengan sampel yang telah selesai

Jika Anda mengalami masalah yang tidak dapat Anda atasi, bandingkan kode Anda dengan proyek yang telah selesai. Lihat atau unduh proyek yang selesai (cara mengunduh).

Langkah berikutnya

Pelajari lebih lanjut

Lihat Referensi cepat API minimal

API minimal dirancang untuk membuat API HTTP dengan dependensi minimal. Mereka sangat ideal untuk layanan mikro dan aplikasi yang hanya ingin menyertakan file, fitur, dan dependensi minimum di ASP.NET Core.

Tutorial ini mengajarkan dasar-dasar membangun API minimal dengan ASP.NET Core. Pendekatan lain untuk membuat API di ASP.NET Core adalah menggunakan pengontrol. Untuk bantuan dalam memilih antara API minimal dan API berbasis pengontrol, lihat Gambaran umum API. Untuk tutorial tentang membuat proyek API berdasarkan pengontrol yang berisi lebih banyak fitur, lihat Membuat API web.

Gambaran Umum

Tutorial ini membuat API berikut:

API Deskripsi Isi permintaan Isi respons
GET /todoitems Dapatkan semua item yang harus dilakukan Tidak Array item yang harus dilakukan
GET /todoitems/complete Selesaikan item yang harus dilakukan Tidak Array item yang harus dilakukan
GET /todoitems/{id} Mendapatkan item menurut ID Tidak Item yang harus dilakukan
POST /todoitems Menambahkan item baru Item yang harus dilakukan Item yang harus dilakukan
PUT /todoitems/{id} Memperbarui item yang sudah ada Item yang harus dilakukan Tidak
DELETE /todoitems/{id}     Menghapus item Tidak Tidak

Prasyarat

Membuat proyek API

  • Mulai Visual Studio 2022 dan pilih Buat proyek baru.

  • Dalam dialog Buat proyek baru:

    • Masukkan Empty di kotak pencarian Cari templat .
    • Pilih templat ASP.NET Core Empty dan pilih Berikutnya.

    Visual Studio Membuat proyek baru

  • Beri nama proyek TodoApi dan pilih Berikutnya.

  • Dalam dialog Informasi tambahan:

    • Pilih .NET 7.0
    • Hapus centang Jangan gunakan pernyataan tingkat atas
    • Pilih Buat

    Informasi Tambahan

Memeriksa kode

File Program.cs berisi kode berikut:

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

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

app.Run();

Kode sebelumnya:

Menjalankan aplikasi

Tekan Ctrl+F5 untuk menjalankan tanpa debugger.

Visual Studio menampilkan dialog berikut:

Proyek ini dikonfigurasi untuk menggunakan SSL. Untuk menghindari peringatan SSL di browser, Anda dapat memilih untuk mempercayai sertifikat yang ditandatangani sendiri yang telah dihasilkan IIS Express. Apakah Anda ingin mempercayai sertifikat IIS Express SSL?

Pilih Ya jika Anda mempercayai sertifikat IIS Express SSL.

Dialog berikut ditampilkan:

Dialog peringatan keamanan

Pilih Ya jika Anda setuju untuk mempercayai sertifikat pengembangan tersebut.

Untuk informasi tentang mempercayai browser Firefox, lihat Kesalahan sertifikat Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.

Visual Studio meluncurkan Kestrel server web dan membuka jendela browser.

Hello World! ditampilkan di browser. File Program.cs berisi aplikasi minimal tetapi lengkap.

Menambahkan paket NuGet

Paket NuGet harus ditambahkan untuk mendukung database dan diagnostik yang digunakan dalam tutorial ini.

  • Dari menu Alat, pilih Pengelola > Paket NuGet Kelola Paket NuGet untuk Solusi.
  • Pilih tab Telusuri.
  • Masukkan Microsoft.EntityFrameworkCore.InMemory di kotak pencarian, lalu pilih Microsoft.EntityFrameworkCore.InMemory.
  • Pilih kotak centang Proyek di panel kanan.
  • Di menu drop-down Versi pilih versi terbaru 7 yang tersedia, misalnya 7.0.17, lalu pilih Instal.
  • Ikuti instruksi sebelumnya untuk menambahkan Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore paket dengan versi terbaru 7 yang tersedia.

Kelas konteks model dan database

Di folder proyek, buat file bernama Todo.cs dengan kode berikut:

public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

Kode sebelumnya membuat model untuk aplikasi ini. Model adalah kelas yang mewakili data yang dikelola aplikasi.

Buat file bernama TodoDb.cs dengan kode berikut:

using Microsoft.EntityFrameworkCore;

class TodoDb : DbContext
{
    public TodoDb(DbContextOptions<TodoDb> options)
        : base(options) { }

    public DbSet<Todo> Todos => Set<Todo>();
}

Kode sebelumnya mendefinisikan konteks database, yang merupakan kelas utama yang mengoordinasikan fungsionalitas Kerangka Kerja Entitas untuk model data. Kelas ini berasal dari Microsoft.EntityFrameworkCore.DbContext kelas .

Menambahkan kode API

Ganti isi file Program.cs dengan kode berikut:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

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

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.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.NoContent();
    }

    return Results.NotFound();
});

app.Run();

Kode yang disorot berikut menambahkan konteks database ke kontainer injeksi dependensi (DI) dan memungkinkan menampilkan pengecualian terkait database:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

Kontainer DI menyediakan akses ke konteks database dan layanan lainnya.

Membuat UI pengujian API dengan Swagger

Ada banyak alat pengujian API web yang tersedia untuk dipilih, dan Anda dapat mengikuti langkah-langkah pengujian API pengantar tutorial ini dengan alat pilihan Anda sendiri.

Tutorial ini menggunakan paket .NET NSwag.AspNetCore, yang mengintegrasikan alat Swagger untuk menghasilkan UI pengujian yang mematuhi spesifikasi OpenAPI:

  • NSwag: Pustaka .NET yang mengintegrasikan Swagger langsung ke aplikasi ASP.NET Core, menyediakan middleware dan konfigurasi.
  • Swagger: Sekumpulan alat sumber terbuka seperti OpenAPIGenerator dan SwaggerUI yang menghasilkan halaman pengujian API yang mengikuti spesifikasi OpenAPI.
  • Spesifikasi OpenAPI: Dokumen yang menjelaskan kemampuan API, berdasarkan XML dan anotasi atribut dalam pengontrol dan model.

Untuk informasi selengkapnya tentang menggunakan OpenAPI dan NSwag dengan ASP.NET, lihat dokumentasi API web ASP.NET Core dengan Swagger / OpenAPI.

Menginstal alat Swagger

  • Jalankan perintah berikut:

    dotnet add package NSwag.AspNetCore
    

Perintah sebelumnya menambahkan paket NSwag.AspNetCore , yang berisi alat untuk menghasilkan dokumen dan UI Swagger.

Mengonfigurasi middleware Swagger

  • Di Program.cs tambahkan pernyataan berikut using di bagian atas:

    using NSwag.AspNetCore;
    
  • Tambahkan kode yang disorot berikut sebelum app ditentukan sebaris var app = builder.Build();

    using NSwag.AspNetCore;
    using Microsoft.EntityFrameworkCore;
    
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
    builder.Services.AddDatabaseDeveloperPageExceptionFilter();
    
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddOpenApiDocument(config =>
    {
        config.DocumentName = "TodoAPI";
        config.Title = "TodoAPI v1";
        config.Version = "v1";
    });
    var app = builder.Build();
    

Dalam kode sebelumnya:

  • builder.Services.AddEndpointsApiExplorer();: Mengaktifkan API Explorer, yang merupakan layanan yang menyediakan metadata tentang API HTTP. API Explorer digunakan oleh Swagger untuk menghasilkan dokumen Swagger.

  • builder.Services.AddOpenApiDocument(config => {...});: Menambahkan generator dokumen Swagger OpenAPI ke layanan aplikasi dan mengonfigurasinya untuk memberikan informasi lebih lanjut tentang API, seperti judul dan versinya. Untuk informasi tentang memberikan detail API yang lebih kuat, lihat Mulai menggunakan NSwag dan ASP.NET Core

  • Tambahkan kode yang disorot berikut ke baris berikutnya setelah app ditentukan dalam baris var app = builder.Build();

    var app = builder.Build();
    if (app.Environment.IsDevelopment())
    {
        app.UseOpenApi();
        app.UseSwaggerUi(config =>
        {
            config.DocumentTitle = "TodoAPI";
            config.Path = "/swagger";
            config.DocumentPath = "/swagger/{documentName}/swagger.json";
            config.DocExpansion = "list";
        });
    }
    

    Kode sebelumnya memungkinkan middleware Swagger untuk melayani dokumen ON yang dihasilkan JSdan antarmuka pengguna Swagger. Swagger hanya diaktifkan di lingkungan pengembangan. Mengaktifkan Swagger di lingkungan produksi dapat mengekspos detail yang berpotensi sensitif tentang struktur dan implementasi API.

Menguji data posting

Kode berikut dalam Program.cs membuat titik /todoitems akhir HTTP POST yang menambahkan data ke database dalam memori:

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

Jalankan aplikasi. Browser menampilkan kesalahan 404 karena tidak ada lagi / titik akhir.

Titik akhir POST akan digunakan untuk menambahkan data ke aplikasi.

  • Dengan aplikasi yang masih berjalan, di browser, navigasikan ke https://localhost:<port>/swagger untuk menampilkan halaman pengujian API yang dihasilkan oleh Swagger.

    Halaman pengujian API yang dihasilkan Swagger

  • Pada halaman pengujian API Swagger, pilih Posting /todoitems>Cobalah.

  • Perhatikan bahwa bidang Isi permintaan berisi format contoh yang dihasilkan yang mencerminkan parameter untuk API.

  • Dalam isi permintaan masukkan JSAKTIF untuk item yang harus dilakukan, tanpa menentukan opsional id:

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • Pilih Jalankan.

    Swagger dengan Post

Swagger menyediakan panel Respons di bawah tombol Jalankan.

Swagger dengan respons Post

Perhatikan beberapa detail yang berguna:

  • cURL: Swagger menyediakan contoh perintah cURL dalam sintaks Unix/Linux, yang dapat dijalankan di baris perintah dengan shell bash apa pun yang menggunakan sintaks Unix/Linux, termasuk Git Bash dari Git untuk Windows.
  • URL Permintaan: Representasi yang disederhanakan dari permintaan HTTP yang dibuat oleh kode JavaScript UI Swagger untuk panggilan API. Permintaan aktual dapat menyertakan detail seperti header dan parameter kueri dan isi permintaan.
  • Respons server: Menyertakan isi respons dan header. Isi respons menunjukkan id diatur ke 1.
  • Kode Respons: Kode status 200 HTTP dikembalikan yang menunjukkan permintaan berhasil diproses.

Memeriksa titik akhir GET

Aplikasi sampel mengimplementasikan beberapa titik akhir GET dengan memanggil MapGet:

API Deskripsi Isi permintaan Isi respons
GET /todoitems Dapatkan semua item yang harus dilakukan Tidak Array item yang harus dilakukan
GET /todoitems/complete Dapatkan semua item tugas yang sudah selesai Tidak Array item yang harus dilakukan
GET /todoitems/{id} Mendapatkan item menurut ID Tidak Item yang harus dilakukan
app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

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

Menguji titik akhir GET

Uji aplikasi dengan memanggil titik akhir dari browser atau Swagger.

  • Di Swagger pilih GET /todoitems>Cobalah Jalankan>.

  • Atau, panggil GET /todoitems dari browser dengan memasukkan URI http://localhost:<port>/todoitems. Misalnya: http://localhost:5001/todoitems

Panggilan untuk GET /todoitems menghasilkan respons yang mirip dengan yang berikut ini:

[
  {
    "id": 1,
    "name": "walk dog",
    "isComplete": true
  }
]
  • Panggil GET /todoitems/{id} di Swagger untuk mengembalikan data dari id tertentu:

    • Pilih GET /todoitems>Cobalah.
    • Atur bidang id ke 1 dan pilih Jalankan.
  • Atau, panggil GET /todoitems dari browser dengan memasukkan URI https://localhost:<port>/todoitems/1. Misalnya: https://localhost:5001/todoitems/1

  • Responsnya mirip dengan yang berikut ini:

    {
      "id": 1,
      "name": "walk dog",
      "isComplete": true
    }
    

Aplikasi ini menggunakan database dalam memori. Jika aplikasi dimulai ulang, permintaan GET tidak mengembalikan data apa pun. Jika tidak ada data yang dikembalikan, KIRIM data ke aplikasi dan coba permintaan GET lagi.

Mengembalikan nilai

ASP.NET Core secara otomatis menserialisasikan objek ke JSAKTIF dan menulis JSON ke dalam isi pesan respons. Kode respons untuk jenis pengembalian ini adalah 200 OK, dengan asumsi tidak ada pengecualian yang tidak tertangani. Pengecualian yang tidak tertangani diterjemahkan ke dalam kesalahan 5xx.

Jenis pengembalian dapat mewakili berbagai kode status HTTP. Misalnya, GET /todoitems/{id} dapat mengembalikan dua nilai status yang berbeda:

  • Jika tidak ada item yang cocok dengan ID yang diminta, metode mengembalikan kode kesalahan statusNotFound 404.
  • Jika tidak, metode mengembalikan 200 dengan JSisi respons ON. Mengembalikan item hasil dalam respons HTTP 200.

Memeriksa titik akhir PUT

Aplikasi sampel mengimplementasikan satu titik akhir PUT menggunakan MapPut:

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

Metode ini mirip MapPost dengan metode , kecuali menggunakan HTTP PUT. Respons yang berhasil mengembalikan 204 (Tanpa Konten). Menurut spesifikasi HTTP, permintaan PUT mengharuskan klien untuk mengirim seluruh entitas yang diperbarui, bukan hanya perubahan. Untuk mendukung pembaruan parsial, gunakan HTTP PATCH.

Menguji titik akhir PUT

Sampel ini menggunakan database dalam memori yang harus diinisialisasi setiap kali aplikasi dimulai. Harus ada item dalam database sebelum Anda melakukan panggilan PUT. Panggil GET untuk memastikan ada item dalam database sebelum melakukan panggilan PUT.

Perbarui item yang harus dilakukan yang memiliki Id = 1 dan atur namanya ke "feed fish".

Gunakan Swagger untuk mengirim permintaan PUT:

  • Pilih Letakkan /todoitems/{id}>Cobalah.

  • Atur bidang id ke 1.

  • Atur isi permintaan ke ON berikut JS:

    {
      "name": "feed fish",
      "isComplete": false
    }
    
  • Pilih Jalankan.

Memeriksa dan menguji titik akhir DELETE

Aplikasi sampel mengimplementasikan satu titik akhir DELETE menggunakan MapDelete:

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

    return Results.NotFound();
});

Gunakan Swagger untuk mengirim permintaan DELETE:

  • Pilih HAPUS /todoitems/{id}>Cobalah.

  • Atur bidang ID ke 1 dan pilih Jalankan.

    Permintaan DELETE dikirim ke aplikasi dan respons ditampilkan di panel Respons . Isi respons kosong, dan kode status respons Server adalah 204.

Menggunakan API MapGroup

Kode aplikasi sampel mengulangi awalan todoitems URL setiap kali menyiapkan titik akhir. API sering memiliki grup titik akhir dengan awalan URL umum, dan metode ini tersedia untuk membantu mengatur grup tersebut MapGroup . Ini mengurangi kode berulang dan memungkinkan untuk menyesuaikan seluruh grup titik akhir dengan satu panggilan ke metode seperti RequireAuthorization dan WithMetadata.

Ganti isi Program.cs dengan kode berikut:

using NSwag.AspNetCore;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(config =>
{
    config.DocumentName = "TodoAPI";
    config.Title = "TodoAPI v1";
    config.Version = "v1";
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseOpenApi();
    app.UseSwaggerUi(config =>
    {
        config.DocumentTitle = "TodoAPI";
        config.Path = "/swagger";
        config.DocumentPath = "/swagger/{documentName}/swagger.json";
        config.DocExpansion = "list";
    });
}

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", async (TodoDb db) =>
    await db.Todos.ToListAsync());

todoItems.MapGet("/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

todoItems.MapGet("/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(todo)
            : Results.NotFound());

todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return Results.NoContent();
    }

    return Results.NotFound();
});

app.Run();

Kode sebelumnya memiliki perubahan berikut:

  • var todoItems = app.MapGroup("/todoitems"); Menambahkan untuk menyiapkan grup menggunakan awalan /todoitemsURL .
  • Mengubah semua metode menjadi app.Map<HttpVerb>todoItems.Map<HttpVerb>.
  • Menghapus awalan /todoitems URL dari Map<HttpVerb> panggilan metode.

Uji titik akhir untuk memverifikasi bahwa titik akhir berfungsi sama.

Menggunakan API TypedResults

TypedResults Mengembalikan daripada Results memiliki beberapa keuntungan, termasuk uji coba dan secara otomatis mengembalikan metadata jenis respons untuk OpenAPI untuk menjelaskan titik akhir. Untuk informasi selengkapnya, lihat TypedResults vs Results.

Metode ini Map<HttpVerb> dapat memanggil metode handler rute alih-alih menggunakan lambda. Untuk melihat contoh, perbarui Program.cs dengan kode berikut:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

app.Run();

static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)
{
    return await db.Todos.FindAsync(id)
        is Todo todo
            ? TypedResults.Ok(todo)
            : TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

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

static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return TypedResults.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return TypedResults.NoContent();
    }

    return TypedResults.NotFound();
}

Kode Map<HttpVerb> sekarang memanggil metode alih-alih lambda:

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

Metode ini mengembalikan objek yang mengimplementasikan IResult dan didefinisikan oleh TypedResults:

static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)
{
    return await db.Todos.FindAsync(id)
        is Todo todo
            ? TypedResults.Ok(todo)
            : TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

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

static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return TypedResults.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return TypedResults.NoContent();
    }

    return TypedResults.NotFound();
}

Pengujian unit dapat memanggil metode ini dan menguji bahwa mereka mengembalikan jenis yang benar. Misalnya, jika metodenya adalah GetAllTodos:

static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

Kode pengujian unit dapat memverifikasi bahwa objek jenis Ok<Todo[]> dikembalikan dari metode handler. Contohnya:

public async Task GetAllTodos_ReturnsOkOfTodosResult()
{
    // Arrange
    var db = CreateDbContext();

    // Act
    var result = await TodosApi.GetAllTodos(db);

    // Assert: Check for the correct returned type
    Assert.IsType<Ok<Todo[]>>(result);
}

Cegah postingan berlebihan

Saat ini aplikasi sampel mengekspos seluruh Todo objek. Aplikasi produksi Dalam aplikasi produksi, subset model sering digunakan untuk membatasi data yang dapat diinput dan dikembalikan. Ada beberapa alasan di balik ini dan keamanan adalah yang utama. Subset model biasanya disebut sebagai Objek Transfer Data (DTO), model input, atau model tampilan. DTO digunakan dalam artikel ini.

DTO dapat digunakan untuk:

  • Cegah postingan berlebihan.
  • Sembunyikan properti yang seharusnya tidak dilihat klien.
  • Hilangkan beberapa properti untuk mengurangi ukuran payload.
  • Meratakan grafik objek yang berisi objek berlapis. Grafik objek yang diratakan bisa lebih nyaman untuk klien.

Untuk menunjukkan pendekatan DTO, perbarui Todo kelas untuk menyertakan bidang rahasia:

public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
    public string? Secret { get; set; }
}

Bidang rahasia perlu disembunyikan dari aplikasi ini, tetapi aplikasi administratif dapat memilih untuk mengeksposnya.

Verifikasi bahwa Anda dapat memposting dan mendapatkan bidang rahasia.

Buat file bernama TodoItemDTO.cs dengan kode berikut:

public class TodoItemDTO
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }

    public TodoItemDTO() { }
    public TodoItemDTO(Todo todoItem) =>
    (Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}

Ganti konten Program.cs file dengan kode berikut untuk menggunakan model DTO ini:

using Microsoft.EntityFrameworkCore;

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

app.MapPost("/todoitems", async (TodoItemDTO todoItemDTO, TodoDb db) =>
{
    var todoItem = new Todo
    {
        IsComplete = todoItemDTO.IsComplete,
        Name = todoItemDTO.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 todoItemDTO, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = todoItemDTO.Name;
    todo.IsComplete = todoItemDTO.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.NoContent();
    }

    return Results.NotFound();
});

app.Run();

public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
    public string? Secret { get; set; }
}

public class TodoItemDTO
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }

    public TodoItemDTO() { }
    public TodoItemDTO(Todo todoItem) =>
    (Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}


class TodoDb : DbContext
{
    public TodoDb(DbContextOptions<TodoDb> options)
        : base(options) { }

    public DbSet<Todo> Todos => Set<Todo>();
}

Verifikasi bahwa Anda bisa memposting dan mendapatkan semua bidang kecuali bidang rahasia.

Pemecahan masalah dengan sampel yang telah selesai

Jika Anda mengalami masalah yang tidak dapat Anda atasi, bandingkan kode Anda dengan proyek yang telah selesai. Lihat atau unduh proyek yang selesai (cara mengunduh).

Langkah berikutnya

Pelajari lebih lanjut

Lihat Referensi cepat API minimal

API minimal dirancang untuk membuat API HTTP dengan dependensi minimal. Mereka sangat ideal untuk layanan mikro dan aplikasi yang hanya ingin menyertakan file, fitur, dan dependensi minimum di ASP.NET Core.

Tutorial ini mengajarkan dasar-dasar membangun API minimal dengan ASP.NET Core. Pendekatan lain untuk membuat API di ASP.NET Core adalah menggunakan pengontrol. Untuk bantuan dalam memilih antara API minimal dan API berbasis pengontrol, lihat Gambaran umum API. Untuk tutorial tentang membuat proyek API berdasarkan pengontrol yang berisi lebih banyak fitur, lihat Membuat API web.

Gambaran Umum

Tutorial ini membuat API berikut:

API Deskripsi Isi permintaan Isi respons
GET /todoitems Dapatkan semua item yang harus dilakukan Tidak Array item yang harus dilakukan
GET /todoitems/complete Selesaikan item yang harus dilakukan Tidak Array item yang harus dilakukan
GET /todoitems/{id} Mendapatkan item menurut ID Tidak Item yang harus dilakukan
POST /todoitems Menambahkan item baru Item yang harus dilakukan Item yang harus dilakukan
PUT /todoitems/{id} Memperbarui item yang sudah ada Item yang harus dilakukan Tidak
DELETE /todoitems/{id}     Menghapus item Tidak Tidak

Prasyarat

Membuat proyek API

  • Mulai Visual Studio 2022 dan pilih Buat proyek baru.

  • Dalam dialog Buat proyek baru:

    • Masukkan Empty di kotak pencarian Cari templat .
    • Pilih templat ASP.NET Core Empty dan pilih Berikutnya.

    Visual Studio Membuat proyek baru

  • Beri nama proyek TodoApi dan pilih Berikutnya.

  • Dalam dialog Informasi tambahan:

    • Pilih .NET 6.0
    • Hapus centang Jangan gunakan pernyataan tingkat atas
    • Pilih Buat

Memeriksa kode

File Program.cs berisi kode berikut:

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

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

app.Run();

Kode sebelumnya:

Menjalankan aplikasi

Tekan Ctrl+F5 untuk menjalankan tanpa debugger.

Visual Studio menampilkan dialog berikut:

Proyek ini dikonfigurasi untuk menggunakan SSL. Untuk menghindari peringatan SSL di browser, Anda dapat memilih untuk mempercayai sertifikat yang ditandatangani sendiri yang telah dihasilkan IIS Express. Apakah Anda ingin mempercayai sertifikat IIS Express SSL?

Pilih Ya jika Anda mempercayai sertifikat IIS Express SSL.

Dialog berikut ditampilkan:

Dialog peringatan keamanan

Pilih Ya jika Anda setuju untuk mempercayai sertifikat pengembangan tersebut.

Untuk informasi tentang mempercayai browser Firefox, lihat Kesalahan sertifikat Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.

Visual Studio meluncurkan Kestrel server web dan membuka jendela browser.

Hello World! ditampilkan di browser. File Program.cs berisi aplikasi minimal tetapi lengkap.

Menambahkan paket NuGet

Paket NuGet harus ditambahkan untuk mendukung database dan diagnostik yang digunakan dalam tutorial ini.

  • Dari menu Alat, pilih Pengelola > Paket NuGet Kelola Paket NuGet untuk Solusi.
  • Pilih tab Telusuri.
  • Masukkan Microsoft.EntityFrameworkCore.InMemory di kotak pencarian, lalu pilih Microsoft.EntityFrameworkCore.InMemory.
  • Pilih kotak centang Proyek di panel kanan.
  • Di menu drop-down Versi pilih versi terbaru 7 yang tersedia, misalnya 6.0.28, lalu pilih Instal.
  • Ikuti instruksi sebelumnya untuk menambahkan Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore paket dengan versi terbaru 7 yang tersedia.

Kelas konteks model dan database

Di folder proyek, buat file bernama Todo.cs dengan kode berikut:

public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

Kode sebelumnya membuat model untuk aplikasi ini. Model adalah kelas yang mewakili data yang dikelola aplikasi.

Buat file bernama TodoDb.cs dengan kode berikut:

using Microsoft.EntityFrameworkCore;

class TodoDb : DbContext
{
    public TodoDb(DbContextOptions<TodoDb> options)
        : base(options) { }

    public DbSet<Todo> Todos => Set<Todo>();
}

Kode sebelumnya mendefinisikan konteks database, yang merupakan kelas utama yang mengoordinasikan fungsionalitas Kerangka Kerja Entitas untuk model data. Kelas ini berasal dari Microsoft.EntityFrameworkCore.DbContext kelas .

Menambahkan kode API

Ganti isi file Program.cs dengan kode berikut:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

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

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

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

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.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.NoContent();
    }

    return Results.NotFound();
});

app.Run();

Kode yang disorot berikut menambahkan konteks database ke kontainer injeksi dependensi (DI) dan memungkinkan menampilkan pengecualian terkait database:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

Kontainer DI menyediakan akses ke konteks database dan layanan lainnya.

Membuat UI pengujian API dengan Swagger

Ada banyak alat pengujian API web yang tersedia untuk dipilih, dan Anda dapat mengikuti langkah-langkah pengujian API pengantar tutorial ini dengan alat pilihan Anda sendiri.

Tutorial ini menggunakan paket .NET NSwag.AspNetCore, yang mengintegrasikan alat Swagger untuk menghasilkan UI pengujian yang mematuhi spesifikasi OpenAPI:

  • NSwag: Pustaka .NET yang mengintegrasikan Swagger langsung ke aplikasi ASP.NET Core, menyediakan middleware dan konfigurasi.
  • Swagger: Sekumpulan alat sumber terbuka seperti OpenAPIGenerator dan SwaggerUI yang menghasilkan halaman pengujian API yang mengikuti spesifikasi OpenAPI.
  • Spesifikasi OpenAPI: Dokumen yang menjelaskan kemampuan API, berdasarkan XML dan anotasi atribut dalam pengontrol dan model.

Untuk informasi selengkapnya tentang menggunakan OpenAPI dan NSwag dengan ASP.NET, lihat dokumentasi API web ASP.NET Core dengan Swagger / OpenAPI.

Menginstal alat Swagger

  • Jalankan perintah berikut:

    dotnet add package NSwag.AspNetCore
    

Perintah sebelumnya menambahkan paket NSwag.AspNetCore , yang berisi alat untuk menghasilkan dokumen dan UI Swagger.

Mengonfigurasi middleware Swagger

  • Di Program.cs tambahkan pernyataan berikut using di bagian atas:

    using NSwag.AspNetCore;
    
  • Tambahkan kode yang disorot berikut sebelum app ditentukan sebaris var app = builder.Build();

    using NSwag.AspNetCore;
    using Microsoft.EntityFrameworkCore;
    
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
    builder.Services.AddDatabaseDeveloperPageExceptionFilter();
    
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddOpenApiDocument(config =>
    {
        config.DocumentName = "TodoAPI";
        config.Title = "TodoAPI v1";
        config.Version = "v1";
    });
    
    var app = builder.Build();
    

Dalam kode sebelumnya:

  • builder.Services.AddEndpointsApiExplorer();: Mengaktifkan API Explorer, yang merupakan layanan yang menyediakan metadata tentang API HTTP. API Explorer digunakan oleh Swagger untuk menghasilkan dokumen Swagger.

  • builder.Services.AddOpenApiDocument(config => {...});: Menambahkan generator dokumen Swagger OpenAPI ke layanan aplikasi dan mengonfigurasinya untuk memberikan informasi lebih lanjut tentang API, seperti judul dan versinya. Untuk informasi tentang memberikan detail API yang lebih kuat, lihat Mulai menggunakan NSwag dan ASP.NET Core

  • Tambahkan kode yang disorot berikut ke baris berikutnya setelah app ditentukan dalam baris var app = builder.Build();

    
    var app = builder.Build();
    
    if (app.Environment.IsDevelopment())
    {
        app.UseOpenApi();
        app.UseSwaggerUi(config =>
        {
            config.DocumentTitle = "TodoAPI";
            config.Path = "/swagger";
            config.DocumentPath = "/swagger/{documentName}/swagger.json";
            config.DocExpansion = "list";
        });
    }
    
    

    Kode sebelumnya memungkinkan middleware Swagger untuk melayani dokumen ON yang dihasilkan JSdan antarmuka pengguna Swagger. Swagger hanya diaktifkan di lingkungan pengembangan. Mengaktifkan Swagger di lingkungan produksi dapat mengekspos detail yang berpotensi sensitif tentang struktur dan implementasi API.

Menguji data posting

Kode berikut dalam Program.cs membuat titik /todoitems akhir HTTP POST yang menambahkan data ke database dalam memori:

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

Jalankan aplikasi. Browser menampilkan kesalahan 404 karena tidak ada lagi / titik akhir.

Titik akhir POST akan digunakan untuk menambahkan data ke aplikasi.

  • Dengan aplikasi yang masih berjalan, di browser, navigasikan ke https://localhost:<port>/swagger untuk menampilkan halaman pengujian API yang dihasilkan oleh Swagger.

    Halaman pengujian API yang dihasilkan Swagger

  • Pada halaman pengujian API Swagger, pilih Posting /todoitems>Cobalah.

  • Perhatikan bahwa bidang Isi permintaan berisi format contoh yang dihasilkan yang mencerminkan parameter untuk API.

  • Dalam isi permintaan masukkan JSAKTIF untuk item yang harus dilakukan, tanpa menentukan opsional id:

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • Pilih Jalankan.

    Swagger dengan Post data

Swagger menyediakan panel Respons di bawah tombol Jalankan.

Swagger dengan panel Post resonse

Perhatikan beberapa detail yang berguna:

  • cURL: Swagger menyediakan contoh perintah cURL dalam sintaks Unix/Linux, yang dapat dijalankan di baris perintah dengan shell bash apa pun yang menggunakan sintaks Unix/Linux, termasuk Git Bash dari Git untuk Windows.
  • URL Permintaan: Representasi yang disederhanakan dari permintaan HTTP yang dibuat oleh kode JavaScript UI Swagger untuk panggilan API. Permintaan aktual dapat menyertakan detail seperti header dan parameter kueri dan isi permintaan.
  • Respons server: Menyertakan isi respons dan header. Isi respons menunjukkan id diatur ke 1.
  • Kode Respons: Kode status 200 HTTP dikembalikan yang menunjukkan permintaan berhasil diproses.

Memeriksa titik akhir GET

Aplikasi sampel mengimplementasikan beberapa titik akhir GET dengan memanggil MapGet:

API Deskripsi Isi permintaan Isi respons
GET /todoitems Dapatkan semua item yang harus dilakukan Tidak Array item yang harus dilakukan
GET /todoitems/complete Dapatkan semua item tugas yang sudah selesai Tidak Array item yang harus dilakukan
GET /todoitems/{id} Mendapatkan item menurut ID Tidak Item yang harus dilakukan
app.MapGet("/", () => "Hello World!");

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

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

Menguji titik akhir GET

Uji aplikasi dengan memanggil titik akhir dari browser atau Swagger.

  • Di Swagger pilih GET /todoitems>Cobalah Jalankan>.

  • Atau, panggil GET /todoitems dari browser dengan memasukkan URI http://localhost:<port>/todoitems. Misalnya: http://localhost:5001/todoitems

Panggilan untuk GET /todoitems menghasilkan respons yang mirip dengan yang berikut ini:

[
  {
    "id": 1,
    "name": "walk dog",
    "isComplete": true
  }
]
  • Panggil GET /todoitems/{id} di Swagger untuk mengembalikan data dari id tertentu:

    • Pilih GET /todoitems>Cobalah.
    • Atur bidang id ke 1 dan pilih Jalankan.
  • Atau, panggil GET /todoitems dari browser dengan memasukkan URI https://localhost:<port>/todoitems/1. Misalnya, Misalnya, https://localhost:5001/todoitems/1

  • Responsnya mirip dengan yang berikut ini:

    {
      "id": 1,
      "name": "walk dog",
      "isComplete": true
    }
    

Aplikasi ini menggunakan database dalam memori. Jika aplikasi dimulai ulang, permintaan GET tidak mengembalikan data apa pun. Jika tidak ada data yang dikembalikan, KIRIM data ke aplikasi dan coba permintaan GET lagi.

Mengembalikan nilai

ASP.NET Core secara otomatis menserialisasikan objek ke JSAKTIF dan menulis JSON ke dalam isi pesan respons. Kode respons untuk jenis pengembalian ini adalah 200 OK, dengan asumsi tidak ada pengecualian yang tidak tertangani. Pengecualian yang tidak tertangani diterjemahkan ke dalam kesalahan 5xx.

Jenis pengembalian dapat mewakili berbagai kode status HTTP. Misalnya, GET /todoitems/{id} dapat mengembalikan dua nilai status yang berbeda:

  • Jika tidak ada item yang cocok dengan ID yang diminta, metode mengembalikan kode kesalahan statusNotFound 404.
  • Jika tidak, metode mengembalikan 200 dengan JSisi respons ON. Mengembalikan item hasil dalam respons HTTP 200.

Memeriksa titik akhir PUT

Aplikasi sampel mengimplementasikan satu titik akhir PUT menggunakan MapPut:

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

Metode ini mirip MapPost dengan metode , kecuali menggunakan HTTP PUT. Respons yang berhasil mengembalikan 204 (Tanpa Konten). Menurut spesifikasi HTTP, permintaan PUT mengharuskan klien untuk mengirim seluruh entitas yang diperbarui, bukan hanya perubahan. Untuk mendukung pembaruan parsial, gunakan HTTP PATCH.

Menguji titik akhir PUT

Sampel ini menggunakan database dalam memori yang harus diinisialisasi setiap kali aplikasi dimulai. Harus ada item dalam database sebelum Anda melakukan panggilan PUT. Panggil GET untuk memastikan ada item dalam database sebelum melakukan panggilan PUT.

Perbarui item yang harus dilakukan yang memiliki Id = 1 dan atur namanya ke "feed fish".

Gunakan Swagger untuk mengirim permintaan PUT:

  • Pilih Letakkan /todoitems/{id}>Cobalah.

  • Atur bidang id ke 1.

  • Atur isi permintaan ke ON berikut JS:

    {
      "name": "feed fish",
      "isComplete": false
    }
    
  • Pilih Jalankan.

Memeriksa dan menguji titik akhir DELETE

Aplikasi sampel mengimplementasikan satu titik akhir DELETE menggunakan MapDelete:

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

    return Results.NotFound();
});

Gunakan Swagger untuk mengirim permintaan DELETE:

  • Pilih HAPUS /todoitems/{id}>Cobalah.

  • Atur bidang ID ke 1 dan pilih Jalankan.

    Permintaan DELETE dikirim ke aplikasi dan respons ditampilkan di panel Respons . Isi respons kosong, dan kode status respons Server adalah 204.

Cegah postingan berlebihan

Saat ini aplikasi sampel mengekspos seluruh Todo objek. Aplikasi produksi Dalam aplikasi produksi, subset model sering digunakan untuk membatasi data yang dapat diinput dan dikembalikan. Ada beberapa alasan di balik ini dan keamanan adalah yang utama. Subset model biasanya disebut sebagai Objek Transfer Data (DTO), model input, atau model tampilan. DTO digunakan dalam artikel ini.

DTO dapat digunakan untuk:

  • Cegah postingan berlebihan.
  • Sembunyikan properti yang seharusnya tidak dilihat klien.
  • Hilangkan beberapa properti untuk mengurangi ukuran payload.
  • Meratakan grafik objek yang berisi objek berlapis. Grafik objek yang diratakan bisa lebih nyaman untuk klien.

Untuk menunjukkan pendekatan DTO, perbarui Todo kelas untuk menyertakan bidang rahasia:

public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
    public string? Secret { get; set; }
}

Bidang rahasia perlu disembunyikan dari aplikasi ini, tetapi aplikasi administratif dapat memilih untuk mengeksposnya.

Verifikasi bahwa Anda dapat memposting dan mendapatkan bidang rahasia.

Buat file bernama TodoItemDTO.cs dengan kode berikut:

public class TodoItemDTO
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }

    public TodoItemDTO() { }
    public TodoItemDTO(Todo todoItem) =>
    (Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}

Ganti konten Program.cs file dengan kode berikut untuk menggunakan model DTO ini:

using Microsoft.EntityFrameworkCore;

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

app.MapPost("/todoitems", async (TodoItemDTO todoItemDTO, TodoDb db) =>
{
    var todoItem = new Todo
    {
        IsComplete = todoItemDTO.IsComplete,
        Name = todoItemDTO.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 todoItemDTO, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = todoItemDTO.Name;
    todo.IsComplete = todoItemDTO.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.NoContent();
    }

    return Results.NotFound();
});

app.Run();

public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
    public string? Secret { get; set; }
}

public class TodoItemDTO
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }

    public TodoItemDTO() { }
    public TodoItemDTO(Todo todoItem) =>
    (Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}


class TodoDb : DbContext
{
    public TodoDb(DbContextOptions<TodoDb> options)
        : base(options) { }

    public DbSet<Todo> Todos => Set<Todo>();
}

Verifikasi bahwa Anda bisa memposting dan mendapatkan semua bidang kecuali bidang rahasia.

Menguji API minimal

Untuk contoh pengujian aplikasi API minimal, lihat sampel GitHub ini.

Menerbitkan ke Azure

Untuk informasi tentang penyebaran ke Azure, lihat Mulai Cepat: Menyebarkan aplikasi web ASP.NET.

Sumber Daya Tambahan: