Självstudie: Skapa ett minimalt API med ASP.NET Core
Obs
Det här är inte den senaste versionen av den här artikeln. För den aktuella utgåvan, se .NET 9-versionen av den här artikeln.
Varning
Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i .NET och .NET Core Support Policy. Den aktuella versionen finns i den .NET 9-versionen av den här artikeln.
Viktig
Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.
Den aktuella versionen finns i den .NET 9-versionen av den här artikeln.
Av Rick Anderson och Tom Dykstra
Minimala API:er har skapats för att skapa HTTP-API:er med minimala beroenden. De är idealiska för mikrotjänster och appar som bara vill inkludera de minsta filerna, funktionerna och beroendena i ASP.NET Core.
I den här självstudien lär du dig grunderna i att skapa ett minimalt API med ASP.NET Core. En annan metod för att skapa API:er i ASP.NET Core är att använda styrenheter. Hjälp med att välja mellan minimala API:er och kontrollantbaserade API:er finns i översikten över API:er. En självstudiekurs om hur du skapar ett API-projekt baserat på kontrollanter som innehåller fler funktioner finns i Skapa ett webb-API.
Överblick
I den här handledningen skapas följande API:
API | Beskrivning | Begärandetext | Svarsinnehåll |
---|---|---|---|
GET /todoitems |
Hämta alla to-do objekt | Ingen | Matris med to-do objekt |
GET /todoitems/complete |
Hämta slutförda to-do objekt | Ingen | Matris med to-do objekt |
GET /todoitems/{id} |
Hämta ett objekt efter ID | Ingen | Att göra-objekt |
POST /todoitems |
Lägga till ett nytt objekt | Att göra-objekt | Att göra-objekt |
PUT /todoitems/{id} |
Uppdatera ett befintligt objekt | Att göra-objekt | Ingen |
DELETE /todoitems/{id} |
Ta bort ett objekt | Ingen | Ingen |
Förutsättningar
Visual Studio 2022 med arbetsbelastningen ASP.NET och webbutveckling.
Skapa ett API-projekt
Starta Visual Studio 2022 och välj Skapa ett nytt projekt.
I dialogrutan Skapa ett nytt projekt:
- Ange
Empty
i sökrutan Sök efter mallar. - Välj mallen ASP.NET Core Empty och välj Nästa.
- Ange
Ge projektet namnet TodoApi och välj Nästa.
I dialogen Tilläggsinformation:
- Välj .NET 9.0
- Avmarkera Använd inte toppnivåinstruktioner
- Välj Skapa
Granska koden
Filen Program.cs
innehåller följande kod:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Föregående kod:
- Skapar en WebApplicationBuilder och en WebApplication med förkonfigurerade standardvärden.
- Skapar en HTTP GET-slutpunkt
/
som returnerarHello World!
:
Kör appen
Tryck på Ctrl+F5 för att köra utan felsökningsprogrammet.
Visual Studio visar följande dialogruta:
Välj Ja om du litar på IIS Express SSL-certifikatet.
Följande dialogruta visas:
Säkerhetsvarning dialogruta
Välj Ja om du samtycker till att lita på utvecklingscertifikatet.
För information om hur du kan lita på Firefox-webbläsaren, se Firefox SEC_ERROR_INADEQUATE_KEY_USAGE certifikatfel.
Visual Studio startar Kestrel-webbservern och öppnar ett webbläsarfönster.
Hello World!
visas i webbläsaren. Filen Program.cs
innehåller en minimal men fullständig app.
Stäng webbläsarfönstret.
Lägga till NuGet-paket
NuGet-paketen måste läggas till för att stödja databasen och diagnostiken som används i den här handledningen.
- På menyn Verktyg väljer du NuGet Package Manager > Manage NuGet Packages for Solution.
- Välj fliken Bläddra.
- Välj Inkludera Prerelease.
- Ange Microsoft.EntityFrameworkCore.InMemory i sökrutan och välj sedan
Microsoft.EntityFrameworkCore.InMemory
. - Markera kryssrutan Project i den högra panelen och välj sedan Installera.
- Följ anvisningarna ovan för att lägga till
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
-paketet.
Modell- och databaskontextklasserna
- I projektmappen skapar du en fil med namnet
Todo.cs
med följande kod:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Föregående kod skapar modellen för den här appen. En modell är en klass som representerar data som appen hanterar.
- Skapa en fil med namnet
TodoDb.cs
med följande kod:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Föregående kod definierar databaskontexten, som är huvudklassen som samordnar Entity Framework- funktioner för en datamodell. Den här klassen härleds från klassen Microsoft.EntityFrameworkCore.DbContext.
Lägg till API-koden
- Ersätt innehållet i
Program.cs
-filen med följande kod:
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();
Följande markerade kod lägger till databaskontexten i beroendeinmatning (DI) container och aktiverar visning av databasrelaterade undantag:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
DI-containern ger åtkomst till databaskontexten och andra tjänster.
I den här handledningen används Endpoints Explorer och .http-filer för att testa API:t.
Testdata för publicering
Följande kod i Program.cs
skapar en HTTP POST-slutpunkt /todoitems
som lägger till data i den minnesinterna databasen:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Kör appen. Webbläsaren visar ett 404-fel eftersom det inte längre finns någon /
slutpunkt.
POST-slutpunkten används för att lägga till data i appen.
Välj Visa>Andra Fönster>Slutpunkter Utforskare.
Högerklicka på slutpunkten POST och välj Generera begäran.
En ny fil skapas i projektmappen med namnet
TodoApi.http
, med innehåll som liknar följande exempel:@TodoApi_HostAddress = https://localhost:7031 Post {{TodoApi_HostAddress}}/todoitems ###
- Den första raden skapar en variabel som används för alla slutpunkter.
- Nästa rad definierar en POST-begäran.
- Trippelhashtag-raden (
###
) är en begäranavgränsare: vad som följer efter är för en annan begäran.
POST-begäran behöver rubriker och en brödtext. Om du vill definiera dessa delar av begäran lägger du till följande rader omedelbart efter POST-begäranderaden:
Content-Type: application/json { "name":"walk dog", "isComplete":true }
Föregående kod lägger till ett content-type-huvud och en JSON-begärandetext. Filen TodoApi.http bör nu se ut som i följande exempel, men med portnumret:
@TodoApi_HostAddress = https://localhost:7057 Post {{TodoApi_HostAddress}}/todoitems Content-Type: application/json { "name":"walk dog", "isComplete":true } ###
Kör appen.
Välj länken Skicka begäran som ligger ovanför
POST
begäranderaden.POST-begäran skickas till appen och svaret visas i fönstret Svar.
Granska GET-slutpunkterna
Exempelappen implementerar flera GET-slutpunkter genom att anropa MapGet
:
API | Beskrivning | Begärandetext | Svarstext |
---|---|---|---|
GET /todoitems |
Hämta alla to-do objekt | Ingen | Matris med to-do objekt |
GET /todoitems/complete |
Hämta alla slutförda to-do objekt | Ingen | Matris med to-do objekt |
GET /todoitems/{id} |
Hämta ett objekt efter ID | Ingen | Att göra-objekt |
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());
Testa GET-slutpunkterna
Testa appen genom att anropa GET
slutpunkter från en webbläsare eller med hjälp av Endpoints Explorer. Följande steg gäller för Endpoints Explorer.
I Endpoints Explorerhögerklickar du på den första GET-slutpunkten och väljer Generera begäran.
Följande innehåll läggs till i filen
TodoApi.http
:Get {{TodoApi_HostAddress}}/todoitems ###
Välj länken Skicka begäran som ligger ovanför den nya
GET
begäranderaden.GET-begäran skickas till appen och svaret visas i fönstret Svar.
Svarstexten liknar följande JSON:
[ { "id": 1, "name": "walk dog", "isComplete": true } ]
I Endpoints Explorerhögerklickar du på slutpunkten
/todoitems/{id}
GET och väljer Generera begäran. Följande innehåll läggs till i filenTodoApi.http
:GET {{TodoApi_HostAddress}}/todoitems/{id} ###
Ersätt
{id}
med1
.Välj länken Skicka begäran som ligger ovanför den nya GET-begäranderaden.
GET-begäran skickas till appen och svaret visas i fönstret Svar.
Svarstexten liknar följande JSON:
{ "id": 1, "name": "walk dog", "isComplete": true }
Den här appen använder en minnesintern databas. Om appen startas om returnerar GET-begäran inte några data. Om inga data returneras, POST data till appen och försök get-begäran igen.
Returnera värden
ASP.NET Core serialiserar automatiskt objektet till JSON- och skriver JSON i brödtexten i svarsmeddelandet. Svarskoden för den här returtypen är 200 OK, förutsatt att det inte finns några ohanterade undantag. Ohanterade undantag översätts till 5xx-fel.
Returtyperna kan representera ett brett utbud av HTTP-statuskoder. Till exempel kan GET /todoitems/{id}
returnera två olika statusvärden:
- Om inget objekt matchar det begärda ID:t returnerar metoden en 404-statusNotFound felkod.
- Annars returnerar metoden 200 med en JSON-svarstext. Om du returnerar
item
resulterar det i ett HTTP 200-svar.
Granska PUT-slutpunkten
Exempelappen implementerar en enda PUT-slutpunkt med hjälp av 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();
});
Den här metoden liknar metoden MapPost
, förutom att den använder HTTP PUT. Ett lyckat svar returnerar 204 (inget innehåll). Enligt HTTP-specifikationen kräver en PUT-begäran att klienten skickar hela den uppdaterade entiteten, inte bara ändringarna. Om du vill ha stöd för partiella uppdateringar använder du HTTP PATCH-.
Testa PUT-slutpunkten
Det här exemplet använder en minnesintern databas som måste initieras varje gång appen startas. Det måste finnas ett objekt i databasen innan du gör ett PUT-anrop. Anropa GET för att se till att det finns ett objekt i databasen innan du gör ett PUT-anrop.
Uppdatera det to-do objekt som har Id = 1
och ange dess namn till "feed fish"
.
I Endpoints Explorerhögerklickar du på slutpunkten PUT och väljer Generera begäran.
Följande innehåll läggs till i filen
TodoApi.http
:Put {{TodoApi_HostAddress}}/todoitems/{id} ###
Ersätt
{id}
med1
på PUT-begäranderaden.Lägg till följande rader omedelbart efter PUT-begäranderaden:
Content-Type: application/json { "name": "feed fish", "isComplete": false }
Föregående kod lägger till ett content-type-huvud och en JSON-begärandetext.
Välj länken Skicka begäran som ligger ovanför den nya PUT-begäranderaden.
PUT-begäran skickas till appen och svaret visas i fönstret Svar. Svarstexten är tom och statuskoden är 204.
Granska och testa DELETE-slutpunkten
Exempelappen implementerar en enskild DELETE-slutpunkt med 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();
});
I Endpoints Explorerhögerklickar du på slutpunkten DELETE och väljer Generera begäran.
En DELETE-begäran läggs till i
TodoApi.http
.Byt ut
{id}
i raden för DELETE-begäran mot1
. DELETE-begäran bör se ut som i följande exempel:DELETE {{TodoApi_HostAddress}}/todoitems/1 ###
Välj länken Skicka begäran för DELETE-begäran.
DELETE-begäran skickas till appen och svaret visas i fönstret Svar. Svarstexten är tom och statuskoden är 204.
Använda MapGroup-API:et
Exempelappkoden upprepar todoitems
URL-prefixet varje gång den konfigurerar en slutpunkt. API:er har ofta grupper av slutpunkter med ett vanligt URL-prefix, och metoden MapGroup är tillgänglig för att organisera sådana grupper. Det minskar repetitiv kod och gör det möjligt att anpassa hela grupper av slutpunkter med ett enda anrop till metoder som RequireAuthorization och WithMetadata.
Ersätt innehållet i Program.cs
med följande kod:
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();
Föregående kod har följande ändringar:
- Lägger till
var todoItems = app.MapGroup("/todoitems");
för att konfigurera gruppen med hjälp av URL-prefixet/todoitems
. - Ändrar alla
app.Map<HttpVerb>
metoder tilltodoItems.Map<HttpVerb>
. - Tar bort URL-prefixet
/todoitems
frånMap<HttpVerb>
-metodanrop.
Testa slutpunkterna för att kontrollera att de fungerar på samma sätt.
Använda Api:et TypedResults
Att returnera TypedResults i stället för Results har flera fördelar, inklusive testbarhet och att automatiskt returnera svarstypmetadata för OpenAPI för att beskriva slutpunkten. Mer information finns i TypedResults vs Results.
De Map<HttpVerb>
metoderna kan anropa routningshanterarmetoder i stället för att använda lambdas. Om du vill se ett exempel uppdaterar du Program.cs med följande kod:
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();
}
Den Map<HttpVerb>
koden anropar nu metoder i stället för lambdas:
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);
Dessa metoder returnerar objekt som implementerar IResult och definieras av 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();
}
Enhetstester kan anropa dessa metoder och testa att de returnerar rätt typ. Om metoden till exempel är GetAllTodos
:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
Enhetstestkoden kan verifiera att ett objekt av typen Ok<Todo[]> returneras från hanteringsmetoden. Till exempel:
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);
}
Förhindra överpublicering
För närvarande exponerar exempelappen hela Todo
objektet. Produktionsappar I produktionsprogram används ofta en delmängd av modellen för att begränsa de data som kan matas in och returneras. Det finns flera orsaker till detta och säkerheten är viktig. Delmängden av en modell kallas vanligtvis för ett dataöverföringsobjekt (DTO), indatamodell eller vymodell.
DTO- används i den här artikeln.
En DTO kan användas för att:
- Förhindra överpublicering.
- Dölj egenskaper som klienter inte ska visa.
- Utelämna vissa egenskaper för att minska nyttolaststorleken.
- Platta ut objektdiagram som innehåller kapslade objekt. Utplattade objektdiagram kan vara enklare för klienter.
Om du vill demonstrera DTO-metoden uppdaterar du klassen Todo
så att den innehåller ett hemligt fält:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Det hemliga fältet måste vara dolt från den här appen, men en administrativ app kan välja att exponera det.
Kontrollera att du kan skicka och hämta det hemliga fältet.
Skapa en fil med namnet TodoItemDTO.cs
med följande kod:
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);
}
Ersätt innehållet i Program.cs
-filen med följande kod för att använda den här DTO-modellen:
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();
}
Kontrollera att du kan publicera och hämta alla fält utom det hemliga fältet.
Felsökning med det slutförda exemplet
Om du stöter på ett problem som du inte kan lösa kan du jämföra koden med det slutförda projektet. Visa eller ladda ned slutfört projekt (hur du laddar ned).
Nästa steg
- Konfigurera JSON-serialiseringsalternativ.
- Hantera fel och undantag: Sidan undantag för utvecklare är aktiverad som standard i utvecklingsmiljön för minimala API-appar. Information om hur du hanterar fel och undantag finns i Hantera fel i ASP.NET Core-API:er.
- Ett exempel på hur du testar en minimal API-app finns i det här GitHub-exemplet.
- OpenAPI-stöd i minimala API:er.
- Snabbstart: Publicera till Azure.
- Organisera ASP.NET Grundläggande minimala API:er.
Lära sig mer
Minimala API:er har skapats för att skapa HTTP-API:er med minimala beroenden. De är idealiska för mikrotjänster och appar som bara vill inkludera de minsta filerna, funktionerna och beroendena i ASP.NET Core.
I den här självstudien lär du dig grunderna i att skapa ett minimalt API med ASP.NET Core. En annan metod för att skapa API:er i ASP.NET Core är att använda styrenheter. Hjälp med att välja mellan minimala API:er och kontrollantbaserade API:er finns i översikten över API:er. En självstudiekurs om hur du skapar ett API-projekt baserat på kontrollanter som innehåller fler funktioner finns i Skapa ett webb-API.
Överblick
I den här guiden skapas följande API:
API | Beskrivning | Begärandetext | Svarskropp |
---|---|---|---|
GET /todoitems |
Hämta alla to-do objekt | Ingen | Matris med to-do objekt |
GET /todoitems/complete |
Hämta slutförda to-do objekt | Ingen | Matris med to-do objekt |
GET /todoitems/{id} |
Hämta ett objekt efter ID | Ingen | Att göra-objekt |
POST /todoitems |
Lägga till ett nytt objekt | Att göra-objekt | Att göra-objekt |
PUT /todoitems/{id} |
Uppdatera ett befintligt objekt | Att göra-objekt | Ingen |
DELETE /todoitems/{id} |
Ta bort ett objekt | Ingen | Ingen |
Förutsättningar
Visual Studio 2022 med arbetsbelastningen ASP.NET och webbutveckling.
Skapa ett API-projekt
Starta Visual Studio 2022 och välj Skapa ett nytt projekt.
I dialogrutan Skapa ett nytt projekt:
- Ange
Empty
i sökrutan Sök efter mallar. - Välj mallen ASP.NET Core Empty och välj Nästa.
- Ange
Ge projektet namnet TodoApi och välj Nästa.
I dialogrutan Ytterligare information:
- Välj .NET 7.0
- Avmarkera Använd inte toppnivåinstruktioner
- Välj Skapa
Granska koden
Filen Program.cs
innehåller följande kod:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Föregående kod:
- Skapar en WebApplicationBuilder och en WebApplication med förkonfigurerade standardvärden.
- Skapar en HTTP GET-slutpunkt
/
som returnerarHello World!
:
Kör appen
Tryck på Ctrl+F5 för att köra utan felsökningsprogrammet.
Visual Studio visar följande dialogruta:
Välj Ja om du litar på IIS Express SSL-certifikatet.
Följande dialogruta visas:
Välj Ja om du samtycker till att lita på utvecklingscertifikatet.
Information om att lita på webbläsaren Firefox finns i Firefox SEC_ERROR_INADEQUATE_KEY_USAGE certifikatfel.
Visual Studio startar Kestrel-webbservern och öppnar ett webbläsarfönster.
Hello World!
visas i webbläsaren. Filen Program.cs
innehåller en minimal men fullständig app.
Lägga till NuGet-paket
NuGet-paket måste läggas till för att stödja databasen och diagnostiken som används i denna självstudie.
- På menyn Verktyg väljer du NuGet Package Manager > Manage NuGet Packages for Solution.
- Välj fliken Bläddra.
- Ange Microsoft.EntityFrameworkCore.InMemory i sökrutan och välj sedan
Microsoft.EntityFrameworkCore.InMemory
. - Markera kryssrutan Project i den högra rutan.
- I listrutan Version väljer du den senaste version 7 som är tillgänglig, till exempel
7.0.17
och väljer sedan Installera. - Följ anvisningarna ovan för att lägga till
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
-paketet med den senaste versionen 7 tillgänglig.
Modell- och databaskontextklasserna
I projektmappen skapar du en fil med namnet Todo.cs
med följande kod:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Föregående kod skapar modellen för den här appen. En modell är en klass som representerar data som appen hanterar.
Skapa en fil med namnet TodoDb.cs
med följande kod:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Föregående kod definierar databaskontexten, som är huvudklassen som samordnar Entity Framework- funktioner för en datamodell. Den här klassen härleds från klassen Microsoft.EntityFrameworkCore.DbContext.
Lägg till API-koden
Ersätt innehållet i Program.cs
-filen med följande kod:
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();
Följande markerade kod lägger till databaskontexten i beroendeinmatning (DI) container och aktiverar visning av databasrelaterade undantag:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
DI-containern ger åtkomst till databaskontexten och andra tjänster.
Skapa API-testgränssnitt med Swagger
Det finns många tillgängliga verktyg för webb-API-testning att välja mellan, och du kan följa den här självstudiekursens inledande API-teststeg med ditt eget önskade verktyg.
I den här självstudien används .NET-paketet NSwag.AspNetCore, som integrerar Swagger-verktyg för att generera ett testgränssnitt som följer OpenAPI-specifikationen:
- NSwag: Ett .NET-bibliotek som integrerar Swagger direkt i ASP.NET Core-program, vilket ger mellanprogram och konfiguration.
- Swagger: En uppsättning verktyg med öppen källkod som OpenAPIGenerator och SwaggerUI som genererar API-testsidor som följer OpenAPI-specifikationen.
- OpenAPI-specifikation: Ett dokument som beskriver funktionerna i API:et, baserat på XML- och attributanteckningarna i kontrollanterna och modellerna.
Mer information om hur du använder OpenAPI och NSwag med ASP.NET finns i ASP.NET Core web API-dokumentation med Swagger/OpenAPI.
Installera Swagger-verktyg
Kör följande kommando:
dotnet add package NSwag.AspNetCore
Föregående kommando lägger till paketet NSwag.AspNetCore, som innehåller verktyg för att generera Swagger-dokument och användargränssnitt.
Konfigurera Swagger-mellanprogram
Lägg till följande markerade kod innan
app
definieras i radvar app = builder.Build();
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();
I föregående kod:
builder.Services.AddEndpointsApiExplorer();
: Aktiverar API Explorer, som är en tjänst som tillhandahåller metadata om HTTP-API:et. API Explorer används av Swagger för att generera Swagger-dokumentet.builder.Services.AddOpenApiDocument(config => {...});
: Lägger till Swagger OpenAPI-dokumentgeneratorn i programtjänsterna och konfigurerar den för att ge mer information om API:et, till exempel dess titel och version. Information om hur du tillhandahåller mer robust API-information finns i Komma igång med NSwag och ASP.NET CoreLägg till följande markerade kod på nästa rad när
app
har definierats i radvar 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"; }); }
Den tidigare koden aktiverar Swagger-mellanprogrammet för att hantera det genererade JSON-dokumentet och Swagger-användargränssnittet. Swagger är endast aktiverat i en utvecklingsmiljö. Om du aktiverar Swagger i en produktionsmiljö kan potentiellt känslig information om API:ets struktur och implementering exponeras.
Testpubliceringsdata
Följande kod i Program.cs
skapar en HTTP POST-slutpunkt /todoitems
som lägger till data i den minnesinterna databasen:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Kör appen. Webbläsaren visar ett 404-fel eftersom det inte längre finns någon /
slutpunkt.
POST-slutpunkten används för att lägga till data i appen.
När appen fortfarande körs går du till
https://localhost:<port>/swagger
i webbläsaren för att visa den API-testsida som genererats av Swagger.På testsidan för Swagger API väljer du Publicera /todoitems>Prova.
Observera att fältet Begärandetext innehåller ett genererat exempelformat som återspeglar parametrarna för API:et.
I begärandetexten anger du JSON för ett to-do objekt, utan att ange den valfria
id
:{ "name":"walk dog", "isComplete":true }
Välj Utför.
Swagger innehåller ett svar fönster nedanför knappen Kör.
Observera några av de användbara detaljerna:
- cURL: Swagger innehåller ett exempel på ett cURL-kommando i Unix/Linux-syntaxen, som kan köras på kommandoraden med alla bash-gränssnitt som använder Unix/Linux-syntax, inklusive Git Bash från Git för Windows.
- Begärande-URL: En förenklad representation av HTTP-begäran som görs av Swagger UI:s JavaScript-kod för API-anropet. Faktiska begäranden kan innehålla information som rubriker och frågeparametrar och en begärandetext.
- Serversvar: Innehåller svarstexten och rubrikerna. Svarstexten visar att
id
har tilldelats1
. - Svarskod: En statuskod för 201
HTTP
returnerades, vilket indikerar att begäran har bearbetats och resulterat i skapandet av en ny resurs.
Granska GET-slutpunkterna
Exempelappen implementerar flera GET-slutpunkter genom att anropa MapGet
:
API | Beskrivning | Begärandetext | Svarskropp |
---|---|---|---|
GET /todoitems |
Hämta alla to-do objekt | Ingen | Matris med to-do objekt |
GET /todoitems/complete |
Hämta alla slutförda to-do objekt | Ingen | Matris med to-do objekt |
GET /todoitems/{id} |
Hämta ett objekt efter ID | Ingen | Att göra-objekt |
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());
Testa GET-slutpunkterna
Testa appen genom att anropa slutpunkterna från en webbläsare eller Swagger.
I Swagger väljer du GET /todoitems>Prova>Kör.
Du kan också anropa GET /todoitems från en webbläsare genom att ange URI-
http://localhost:<port>/todoitems
. Till exempelhttp://localhost:5001/todoitems
Anropet till GET /todoitems
genererar ett svar som liknar följande:
[
{
"id": 1,
"name": "walk dog",
"isComplete": true
}
]
Anropa GET /todoitems/{id} i Swagger för att returnera data från ett specifikt ID:
- Välj GET /todoitems>Prova.
- Ställ in id-fältet till
1
och välj Kör.
Du kan också anropa GET /todoitems från en webbläsare genom att ange URI-
https://localhost:<port>/todoitems/1
. Till exempelhttps://localhost:5001/todoitems/1
Svaret liknar följande:
{ "id": 1, "name": "walk dog", "isComplete": true }
Den här appen använder en minnesintern databas. Om appen startas om returnerar GET-begäran inte några data. Om inga data returneras POST data till appen och prova GET-begäran igen.
Returnera värden
ASP.NET Core serialiserar automatiskt objektet till JSON- och skriver JSON i brödtexten i svarsmeddelandet. Svarskoden för den här returtypen är 200 OK, förutsatt att det inte finns några ohanterade undantag. Ohanterade undantag översätts till 5xx-fel.
Returtyperna kan representera ett brett utbud av HTTP-statuskoder. Till exempel kan GET /todoitems/{id}
returnera två olika statusvärden:
- Om inget objekt matchar det begärda ID:t returnerar metoden en 404-statusNotFound felkod.
- Annars returnerar metoden 200 med en JSON-svarstext. Om du returnerar
item
resulterar det i ett HTTP 200-svar.
Granska PUT-slutpunkten
Exempelappen implementerar en enda PUT-slutpunkt med hjälp av 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();
});
Den här metoden liknar metoden MapPost
, förutom att den använder HTTP PUT. Ett lyckat svar returnerar 204 (inget innehåll). Enligt HTTP-specifikationen kräver en PUT-begäran att klienten skickar hela den uppdaterade entiteten, inte bara ändringarna. Om du vill ha stöd för partiella uppdateringar använder du HTTP PATCH-.
Testa PUT-slutpunkten
Det här exemplet använder en minnesintern databas som måste initieras varje gång appen startas. Det måste finnas ett objekt i databasen innan du gör ett PUT-anrop. Anropa GET för att se till att det finns ett objekt i databasen innan du gör ett PUT-anrop.
Uppdatera det to-do objekt som har Id = 1
och ange dess namn till "feed fish"
.
Använd Swagger för att skicka en PUT-begäran:
Välj Placera /todoitems/{id}>Prova.
Ställ in id-fältet på
1
.Ange begäranens brödtext till följande JSON:
{ "name": "feed fish", "isComplete": false }
Välj Kör.
Granska och testa DELETE-slutpunkten
Exempelappen implementerar en enskild DELETE-slutpunkt med 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();
});
Använd Swagger för att skicka en DELETE-begäran:
Välj DELETE /todoitems/{id}>Prova.
Ange fältet ID till
1
och välj Kör.DELETE-begäran skickas till appen och svaret visas i fönstret Svar. Svarstexten är tom och Server-svaret statuskod är 204.
Använda MapGroup-API:et
Exempelappkoden upprepar todoitems
URL-prefixet varje gång den konfigurerar en slutpunkt. API:er har ofta grupper av slutpunkter med ett vanligt URL-prefix, och metoden MapGroup är tillgänglig för att organisera sådana grupper. Det minskar repetitiv kod och gör det möjligt att anpassa hela grupper av slutpunkter med ett enda anrop till metoder som RequireAuthorization och WithMetadata.
Ersätt innehållet i Program.cs
med följande kod:
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();
Föregående kod har följande ändringar:
- Lägger till
var todoItems = app.MapGroup("/todoitems");
för att konfigurera gruppen med hjälp av URL-prefixet/todoitems
. - Ändrar alla
app.Map<HttpVerb>
metoder tilltodoItems.Map<HttpVerb>
. - Tar bort URL-prefixet
/todoitems
frånMap<HttpVerb>
-metodanrop.
Testa slutpunkterna för att kontrollera att de fungerar på samma sätt.
Använda Api:et TypedResults
Att returnera TypedResults i stället för Results har flera fördelar, inklusive testbarhet och att automatiskt returnera svarstypmetadata för OpenAPI för att beskriva slutpunkten. Mer information finns i TypedResults vs Results.
De Map<HttpVerb>
metoderna kan anropa routningshanterarmetoder i stället för att använda lambdas. Om du vill se ett exempel uppdaterar du Program.cs med följande kod:
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();
}
Den Map<HttpVerb>
koden anropar nu metoder i stället för lambdas:
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);
Dessa metoder returnerar objekt som implementerar IResult och definieras av 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();
}
Enhetstester kan anropa dessa metoder och testa att de returnerar rätt typ. Om metoden till exempel är GetAllTodos
:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
Enhetstestkoden kan verifiera att ett objekt av typen Ok<Todo[]> returneras från hanteringsmetoden. Till exempel:
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);
}
Förhindra överpublicering
För närvarande exponerar exempelappen hela Todo
objektet. Produktionsappar I produktionsprogram används ofta en delmängd av modellen för att begränsa de data som kan matas in och returneras. Det finns flera orsaker till detta och säkerheten är viktig. Delmängden av en modell kallas vanligtvis för ett dataöverföringsobjekt (DTO), indatamodell eller vymodell.
DTO- används i den här artikeln.
En DTO kan användas för att:
- Förhindra överpublicering.
- Dölj egenskaper som klienter inte ska visa.
- Utelämna vissa egenskaper för att minska nyttolaststorleken.
- Platta ut objektdiagram som innehåller kapslade objekt. Utplattade objektdiagram kan vara enklare för klienter.
Om du vill demonstrera DTO-metoden uppdaterar du klassen Todo
så att den innehåller ett hemligt fält:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Det hemliga fältet måste vara dolt från den här appen, men en administrativ app kan välja att exponera det.
Kontrollera att du kan publicera och hämta det hemliga fältet.
Skapa en fil med namnet TodoItemDTO.cs
med följande kod:
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);
}
Ersätt innehållet i Program.cs
-filen med följande kod för att använda den här DTO-modellen:
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>();
}
Kontrollera att du kan publicera och hämta alla fält utom det hemliga fältet.
Felsökning med det slutförda exemplet
Om du stöter på ett problem som du inte kan lösa kan du jämföra koden med det slutförda projektet. Visa eller ladda ned slutfört projekt (hur du laddar ned).
Nästa steg
- Konfigurera JSON-serialiseringsalternativ.
- Hantera fel och undantag: Sidan undantag för utvecklare är aktiverad som standard i utvecklingsmiljön för minimala API-appar. Information om hur du hanterar fel och undantag finns i Hantera fel i ASP.NET Core-API:er.
- Ett exempel på hur du testar en minimal API-app finns i det här GitHub-exemplet.
- OpenAPI-stöd i minimala API:er.
- Snabbstart: Publicera till Azure.
- Organisera ASP.NET Grundläggande minimala API:er.
Lära sig mer
Se snabbreferensen minimala API:er
Minimala API:er har skapats för att skapa HTTP-API:er med minimala beroenden. De är idealiska för mikrotjänster och appar som bara vill inkludera de minsta filerna, funktionerna och beroendena i ASP.NET Core.
I den här självstudien lär du dig grunderna i att skapa ett minimalt API med ASP.NET Core. En annan metod för att skapa API:er i ASP.NET Core är att använda styrenheter. Hjälp med att välja mellan minimala API:er och kontrollantbaserade API:er finns i översikten över API:er. En självstudiekurs om hur du skapar ett API-projekt baserat på kontrollanter som innehåller fler funktioner finns i Skapa ett webb-API.
Överblick
I den här handledningen skapas följande API:
API | Beskrivning | Begäransinnehåll | Svarstext |
---|---|---|---|
GET /todoitems |
Hämta alla to-do objekt | Ingen | Matris med to-do objekt |
GET /todoitems/complete |
Hämta slutförda to-do objekt | Ingen | Matris med to-do objekt |
GET /todoitems/{id} |
Hämta ett objekt efter ID | Ingen | Att göra-objekt |
POST /todoitems |
Lägga till ett nytt objekt | Att göra-objekt | Att göra-objekt |
PUT /todoitems/{id} |
Uppdatera ett befintligt objekt | Att göra-objekt | Ingen |
DELETE /todoitems/{id} |
Ta bort ett objekt | Ingen | Ingen |
Förutsättningar
- Visual Studio 2022 med arbetsbelastningen ASP.NET och webbutveckling.
- .NET 6.0 SDK
Skapa ett API-projekt
Starta Visual Studio 2022 och välj Skapa ett nytt projekt.
I dialogrutan Skapa ett nytt projekt:
- Ange
Empty
i sökrutan Sök efter mallar. - Välj mallen ASP.NET Core Empty och välj Nästa.
- Ange
Ge projektet namnet TodoApi och välj Nästa.
I dialogrutan Ytterligare information:
- Välj .NET 6.0
- Avmarkera Använd inte toppnivåinstruktioner
- Välj Skapa
Granska koden
Filen Program.cs
innehåller följande kod:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Föregående kod:
- Skapar en WebApplicationBuilder och en WebApplication med förkonfigurerade standardvärden.
- Skapar en HTTP GET-slutpunkt
/
som returnerarHello World!
:
Kör appen
Tryck på Ctrl+F5 för att köra utan felsökningsprogrammet.
Visual Studio visar följande dialogruta:
Välj Ja om du litar på IIS Express SSL-certifikatet.
Följande dialogruta visas:
dialogrutan
Välj Ja om du samtycker till att lita på utvecklingscertifikatet.
Information om hur du litar på Webbläsaren Firefox finns i Firefox SEC_ERROR_INADEQUATE_KEY_USAGE certifikatfel.
Visual Studio startar Kestrel-webbservern och öppnar ett webbläsarfönster.
Hello World!
visas i webbläsaren. Filen Program.cs
innehåller en minimal men fullständig app.
Lägga till NuGet-paket
NuGet-paket måste läggas till för att stödja den databas och diagnostik som används i denna handledning.
- På menyn Verktyg väljer du NuGet Package Manager > Manage NuGet Packages for Solution.
- Välj fliken Bläddra.
- Ange Microsoft.EntityFrameworkCore.InMemory i sökrutan och välj sedan
Microsoft.EntityFrameworkCore.InMemory
. - Markera kryssrutan Project i den högra rutan.
- I listrutan Version väljer du den senaste version 7 som är tillgänglig, till exempel
6.0.28
och väljer sedan Installera. - Följ anvisningarna ovan för att lägga till
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
-paketet med den senaste versionen 7 tillgänglig.
Modell- och databaskontextklasserna
I projektmappen skapar du en fil med namnet Todo.cs
med följande kod:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Föregående kod skapar modellen för den här appen. En modell är en klass som representerar data som appen hanterar.
Skapa en fil med namnet TodoDb.cs
med följande kod:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Föregående kod definierar databaskontexten, som är huvudklassen som samordnar Entity Framework- funktioner för en datamodell. Den här klassen härleds från klassen Microsoft.EntityFrameworkCore.DbContext.
Lägg till API-koden
Ersätt innehållet i Program.cs
-filen med följande kod:
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();
Följande markerade kod lägger till databaskontexten i beroendeinmatning (DI) container och aktiverar visning av databasrelaterade undantag:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
DI-containern ger åtkomst till databaskontexten och andra tjänster.
Skapa API-testgränssnitt med Swagger
Det finns många tillgängliga verktyg för webb-API-testning att välja mellan, och du kan följa den här självstudiekursens inledande API-teststeg med ditt eget önskade verktyg.
I den här självstudien används .NET-paketet NSwag.AspNetCore, som integrerar Swagger-verktyg för att generera ett testgränssnitt som följer OpenAPI-specifikationen:
- NSwag: Ett .NET-bibliotek som integrerar Swagger direkt i ASP.NET Core-program, vilket ger mellanprogram och konfiguration.
- Swagger: En uppsättning verktyg med öppen källkod som OpenAPIGenerator och SwaggerUI som genererar API-testsidor som följer OpenAPI-specifikationen.
- OpenAPI-specifikation: Ett dokument som beskriver funktionerna i API:et, baserat på XML- och attributanteckningarna i kontrollanterna och modellerna.
Mer information om hur du använder OpenAPI och NSwag med ASP.NET finns i ASP.NET Core web API-dokumentation med Swagger/OpenAPI.
Installera Swagger-verktyg
Kör följande kommando:
dotnet add package NSwag.AspNetCore
Föregående kommando lägger till paketet NSwag.AspNetCore, som innehåller verktyg för att generera Swagger-dokument och användargränssnitt.
Konfigurera Swagger-mellanprogram
Lägg till följande
using
-instruktioner överst i Program.cs:using NSwag.AspNetCore;
Lägg till följande markerade kod innan
app
definieras i radvar 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();
I föregående kod:
builder.Services.AddEndpointsApiExplorer();
: Aktiverar API Explorer, som är en tjänst som tillhandahåller metadata om HTTP-API:et. API Explorer används av Swagger för att generera Swagger-dokumentet.builder.Services.AddOpenApiDocument(config => {...});
: Lägger till Swagger OpenAPI-dokumentgeneratorn i programtjänsterna och konfigurerar den för att ge mer information om API:et, till exempel dess titel och version. Information om hur du tillhandahåller mer robust API-information finns i Komma igång med NSwag och ASP.NET CoreLägg till följande markerade kod på nästa rad när
app
har definierats i radvar 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"; }); }
Den tidigare koden aktiverar Swagger-mellanprogrammet för att hantera det genererade JSON-dokumentet och Swagger-användargränssnittet. Swagger är endast aktiverat i en utvecklingsmiljö. Om du aktiverar Swagger i en produktionsmiljö kan potentiellt känslig information om API:ets struktur och implementering exponeras.
Testdata för publicering
Följande kod i Program.cs
skapar en HTTP POST-slutpunkt /todoitems
som lägger till data i den minnesinterna databasen:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Kör appen. Webbläsaren visar ett 404-fel eftersom det inte längre finns någon /
slutpunkt.
POST-slutpunkten används för att lägga till data i appen.
När appen fortfarande körs går du till
https://localhost:<port>/swagger
i webbläsaren för att visa den API-testsida som genererats av Swagger.På testsidan för Swagger API väljer du Publicera /todoitems>Prova.
Observera att fältet Begärandetext innehåller ett genererat exempelformat som återspeglar parametrarna för API:et.
I begärandetexten anger du JSON för ett to-do objekt, utan att ange den valfria
id
:{ "name":"walk dog", "isComplete":true }
Välj Kör.
Swagger innehåller ett svar fönster nedanför knappen Kör.
Observera några av de användbara detaljerna:
- cURL: Swagger innehåller ett exempel på ett cURL-kommando i Unix/Linux-syntaxen, som kan köras på kommandoraden med alla bash-gränssnitt som använder Unix/Linux-syntax, inklusive Git Bash från Git för Windows.
- Begärande-URL: En förenklad representation av HTTP-begäran som görs av Swagger UI:s JavaScript-kod för API-anropet. Faktiska begäranden kan innehålla information som rubriker och frågeparametrar och en begärandetext.
- Serversvar: Innehåller svarstexten och rubrikerna. Svarstexten visar att
id
har ställts in på1
. - Svarskod: En statuskod för 201
HTTP
returnerades, vilket indikerar att begäran har bearbetats och resulterat i skapandet av en ny resurs.
Granska GET-slutpunkterna
Exempelappen implementerar flera GET-slutpunkter genom att anropa MapGet
:
API | Beskrivning | Begärandeinnehåll | Svarsdel |
---|---|---|---|
GET /todoitems |
Hämta alla to-do objekt | Ingen | Matris med to-do objekt |
GET /todoitems/complete |
Hämta alla slutförda to-do objekt | Ingen | Matris med to-do objekt |
GET /todoitems/{id} |
Hämta ett objekt efter ID | Ingen | Att göra-objekt |
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());
Testa GET-slutpunkterna
Testa appen genom att anropa slutpunkterna från en webbläsare eller Swagger.
I Swagger väljer du GET /todoitems>Prova>Kör.
Du kan också anropa GET /todoitems från en webbläsare genom att ange URI-
http://localhost:<port>/todoitems
. Till exempelhttp://localhost:5001/todoitems
Anropet till GET /todoitems
genererar ett svar som liknar följande:
[
{
"id": 1,
"name": "walk dog",
"isComplete": true
}
]
Anropa GET /todoitems/{id} i Swagger för att returnera data från ett specifikt ID:
- Välj GET /todoitems>Prova.
- Ställ in fältet id till
1
och välj Utför.
Du kan också anropa GET /todoitems från en webbläsare genom att ange URI-
https://localhost:<port>/todoitems/1
. Till exempelhttps://localhost:5001/todoitems/1
Svaret liknar följande:
{ "id": 1, "name": "walk dog", "isComplete": true }
Den här appen använder en minnesintern databas. Om appen startas om returnerar GET-begäran inte några data. Om inga data returneras, POST data till appen och försök med GET-begäran igen.
Returnera värden
ASP.NET Core serialiserar automatiskt objektet till JSON- och skriver JSON i brödtexten i svarsmeddelandet. Svarskoden för den här returtypen är 200 OK, förutsatt att det inte finns några ohanterade undantag. Ohanterade undantag översätts till 5xx-fel.
Returtyperna kan representera ett brett utbud av HTTP-statuskoder. Till exempel kan GET /todoitems/{id}
returnera två olika statusvärden:
- Om inget objekt matchar det begärda ID:t returnerar metoden en 404-statusNotFound felkod.
- Annars returnerar metoden 200 med en JSON-svarstext. Om du returnerar
item
resulterar det i ett HTTP 200-svar.
Granska PUT-slutpunkten
Exempelappen implementerar en enda PUT-slutpunkt med hjälp av 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();
});
Den här metoden liknar metoden MapPost
, förutom att den använder HTTP PUT. Ett lyckat svar returnerar 204 (inget innehåll). Enligt HTTP-specifikationen kräver en PUT-begäran att klienten skickar hela den uppdaterade entiteten, inte bara ändringarna. Om du vill ha stöd för partiella uppdateringar använder du HTTP PATCH-.
Testa PUT-slutpunkten
Det här exemplet använder en minnesintern databas som måste initieras varje gång appen startas. Det måste finnas ett objekt i databasen innan du gör ett PUT-anrop. Anropa GET för att se till att det finns ett objekt i databasen innan du gör ett PUT-anrop.
Uppdatera det to-do objekt som har Id = 1
och ange dess namn till "feed fish"
.
Använd Swagger för att skicka en PUT-begäran:
Välj Placera /todoitems/{id}>Prova.
Ställ in id fält till
1
.Ange begärandekroppen till följande JSON:
{ "name": "feed fish", "isComplete": false }
Välj Kör.
Granska och testa DELETE-slutpunkten
Exempelappen implementerar en enskild DELETE-slutpunkt med 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();
});
Använd Swagger för att skicka en DELETE-begäran:
Välj DELETE /todoitems/{id}>Prova.
Ange fältet ID till
1
och välj Kör.DELETE-begäran skickas till appen och svaret visas i fönstret Svar. Svarstexten är tom och Server-svaret statuskod är 204.
Förhindra överpublicering
För närvarande exponerar exempelappen hela Todo
objektet. Produktionsappar I produktionsprogram används ofta en delmängd av modellen för att begränsa de data som kan matas in och returneras. Det finns flera orsaker till detta och säkerheten är viktig. Delmängden av en modell kallas vanligtvis för ett dataöverföringsobjekt (DTO), indatamodell eller vymodell.
DTO- används i den här artikeln.
En DTO kan användas för att:
- Förhindra överpublicering.
- Dölj egenskaper som klienter inte ska visa.
- Utelämna vissa egenskaper för att minska nyttolaststorleken.
- Platta ut objektdiagram som innehåller kapslade objekt. Utplattade objektdiagram kan vara enklare för klienter.
Om du vill demonstrera DTO-metoden uppdaterar du klassen Todo
så att den innehåller ett hemligt fält:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Det hemliga fältet måste vara dolt från den här appen, men en administrativ app kan välja att exponera det.
Kontrollera att du kan publicera och hämta det hemliga fältet.
Skapa en fil med namnet TodoItemDTO.cs
med följande kod:
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);
}
Ersätt innehållet i Program.cs
-filen med följande kod för att använda den här DTO-modellen:
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>();
}
Kontrollera att du kan publicera och hämta alla fält utom det hemliga fältet.
Testa minimalt API
Ett exempel på hur du testar en minimal API-app finns i det här GitHub-exemplet.
Publicera till Azure
Information om hur du distribuerar till Azure finns i Snabbstart: Distribuera en ASP.NET webbapp.
Ytterligare resurser
- Snabbreferens Minimala API:er
Minimala API:er har skapats för att skapa HTTP-API:er med minimala beroenden. De är idealiska för mikrotjänster och appar som bara vill inkludera de minsta filerna, funktionerna och beroendena i ASP.NET Core.
I den här självstudien lär du dig grunderna i att skapa ett minimalt API med ASP.NET Core. En annan metod för att skapa API:er i ASP.NET Core är att använda styrenheter. Hjälp med att välja mellan minimala API:er och kontrollantbaserade API:er finns i översikten över API:er. En självstudiekurs om hur du skapar ett API-projekt baserat på kontrollanter som innehåller fler funktioner finns i Skapa ett webb-API.
Överblick
I den här självstudien skapas följande API:
API | Beskrivning | Begäransinnehåll | Svarskropp |
---|---|---|---|
GET /todoitems |
Hämta alla to-do objekt | Ingen | Matris med to-do objekt |
GET /todoitems/complete |
Hämta slutförda to-do objekt | Ingen | Matris med to-do objekt |
GET /todoitems/{id} |
Hämta ett objekt efter ID | Ingen | Att göra-objekt |
POST /todoitems |
Lägga till ett nytt objekt | Att göra-objekt | Att göra-objekt |
PUT /todoitems/{id} |
Uppdatera ett befintligt objekt | Att göra-objekt | Ingen |
DELETE /todoitems/{id} |
Ta bort ett objekt | Ingen | Ingen |
Förutsättningar
Visual Studio 2022 med arbetsflödet ASP.NET och webbutveckling.
Skapa ett API-projekt
Starta Visual Studio 2022 och välj Skapa ett nytt projekt.
I dialogrutan Skapa ett nytt projekt:
- Ange
Empty
i sökrutan Sök efter mallar. - Välj mallen ASP.NET Core Empty och välj Nästa.
- Ange
Ge projektet namnet TodoApi och välj Nästa.
I dialogrutan Ytterligare information:
- Välj .NET 8.0 (långsiktig support)
- Avmarkera Använd inte toppnivåinstruktioner
- Välj Skapa
Granska koden
Filen Program.cs
innehåller följande kod:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Föregående kod:
- Skapar en WebApplicationBuilder och en WebApplication med förkonfigurerade standardvärden.
- Skapar en HTTP GET-slutpunkt
/
som returnerarHello World!
:
Kör appen
Tryck på Ctrl+F5 för att köra utan felsökningsprogrammet.
Visual Studio visar följande dialogruta:
Välj Ja om du litar på IIS Express SSL-certifikatet.
Följande dialogruta visas:
dialogrutan
Välj Ja om du samtycker till att lita på utvecklingscertifikatet.
För information om hur du kan lita på webbläsaren Firefox, se Firefox SEC_ERROR_INADEQUATE_KEY_USAGE certifikatfel.
Visual Studio startar Kestrel-webbservern och öppnar ett webbläsarfönster.
Hello World!
visas i webbläsaren. Filen Program.cs
innehåller en minimal men fullständig app.
Stäng webbläsarfönstret.
Lägga till NuGet-paket
NuGet-paketen måste läggas till för att kunna stödja databasen och diagnostiken som används i den här handledningen.
- På menyn Verktyg väljer du NuGet Package Manager > Manage NuGet Packages for Solution.
- Välj fliken Bläddra.
- Ange Microsoft.EntityFrameworkCore.InMemory i sökrutan och välj sedan
Microsoft.EntityFrameworkCore.InMemory
. - Markera kryssrutan Project i den högra rutan och välj sedan Install.
- Följ anvisningarna ovan för att lägga till
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
-paketet.
Modell- och databaskontextklasserna
- I projektmappen skapar du en fil med namnet
Todo.cs
med följande kod:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Föregående kod skapar modellen för den här appen. En modell är en klass som representerar data som appen hanterar.
- Skapa en fil med namnet
TodoDb.cs
med följande kod:
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Föregående kod definierar databaskontexten, som är huvudklassen som samordnar Entity Framework- funktioner för en datamodell. Den här klassen härleds från klassen Microsoft.EntityFrameworkCore.DbContext.
Lägg till API-koden
- Ersätt innehållet i
Program.cs
-filen med följande kod:
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();
Följande markerade kod lägger till databaskontexten i beroendeinmatning (DI) container och aktiverar visning av databasrelaterade undantag:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
DI-containern ger åtkomst till databaskontexten och andra tjänster.
I den här guiden används Endpoints Explorer och .http-filer för att testa API:et.
Testpubliceringsdata
Följande kod i Program.cs
skapar en HTTP POST-slutpunkt /todoitems
som lägger till data i den minnesinterna databasen:
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Kör appen. Webbläsaren visar ett 404-fel eftersom det inte längre finns någon /
slutpunkt.
POST-slutpunkten används för att lägga till data i appen.
Välj Visa>Andra fönster>Slutpunktsexplorer.
Högerklicka på slutpunkten POST och välj Generera begäran.
En ny fil skapas i projektmappen med namnet
TodoApi.http
, med innehåll som liknar följande exempel:@TodoApi_HostAddress = https://localhost:7031 Post {{TodoApi_HostAddress}}/todoitems ###
- Den första raden skapar en variabel som används för alla slutpunkter.
- Nästa rad definierar en POST-begäran.
- Trippelhashtaggen (
###
) är en avgränsare för begäran: vad som kommer efter det är för en annan begäran.
POST-begäran behöver rubriker och en brödtext. Om du vill definiera dessa delar av begäran lägger du till följande rader omedelbart efter POST-begäranderaden:
Content-Type: application/json { "name":"walk dog", "isComplete":true }
Föregående kod lägger till ett content-type-huvud och en JSON-begärandetext. Filen TodoApi.http bör nu se ut som i följande exempel, men med portnumret:
@TodoApi_HostAddress = https://localhost:7057 Post {{TodoApi_HostAddress}}/todoitems Content-Type: application/json { "name":"walk dog", "isComplete":true } ###
Kör appen.
Välj länken Skicka begäran som ligger ovanför
POST
begäranderaden.POST-begäran skickas till appen och svaret visas i fönstret Svar.
Granska GET-slutpunkterna
Exempelappen implementerar flera GET-slutpunkter genom att anropa MapGet
:
API | Beskrivning | Begäranens innehåll | Svarsdel |
---|---|---|---|
GET /todoitems |
Hämta alla to-do objekt | Ingen | Matris med to-do objekt |
GET /todoitems/complete |
Hämta alla slutförda to-do objekt | Ingen | Matris med to-do objekt |
GET /todoitems/{id} |
Hämta ett objekt efter ID | Ingen | Att göra-objekt |
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());
Testa GET-slutpunkterna
Testa appen genom att anropa GET
slutpunkter från en webbläsare eller med hjälp av Endpoints Explorer. Följande steg gäller för Endpoints Explorer.
I Endpoints Explorerhögerklickar du på den första GET-slutpunkten och väljer Generera begäran.
Följande innehåll läggs till i filen
TodoApi.http
:Get {{TodoApi_HostAddress}}/todoitems ###
Välj länken Skicka begäran som ligger ovanför den nya
GET
begäranderaden.GET-begäran skickas till appen och svaret visas i fönstret Svar.
Svarstexten liknar följande JSON:
[ { "id": 1, "name": "walk dog", "isComplete": true } ]
I Endpoints Explorerhögerklickar du på slutpunkten
/todoitems/{id}
GET och väljer Generera begäran. Följande innehåll läggs till i filenTodoApi.http
:GET {{TodoApi_HostAddress}}/todoitems/{id} ###
Ersätt
{id}
med1
.Välj länken Skicka begäran som ligger ovanför den nya GET-begäranderaden.
GET-begäran skickas till appen och svaret visas i fönstret Svar.
Svarstexten liknar följande JSON:
{ "id": 1, "name": "walk dog", "isComplete": true }
Den här appen använder en minnesintern databas. Om appen startas om returnerar GET-begäran inte några data. Om inga data returneras POST data till appen och prova GET-begäran igen.
Returnera värden
ASP.NET Core serialiserar automatiskt objektet till JSON- och skriver JSON i brödtexten i svarsmeddelandet. Svarskoden för den här returtypen är 200 OK, förutsatt att det inte finns några ohanterade undantag. Ohanterade undantag översätts till 5xx-fel.
Returtyperna kan representera ett brett utbud av HTTP-statuskoder. Till exempel kan GET /todoitems/{id}
returnera två olika statusvärden:
- Om inget objekt matchar det begärda ID:t returnerar metoden en 404-statusNotFound felkod.
- Annars returnerar metoden 200 med en JSON-svarstext. Om du returnerar
item
resulterar det i ett HTTP 200-svar.
Granska PUT-slutpunkten
Exempelappen implementerar en enda PUT-slutpunkt med hjälp av 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();
});
Den här metoden liknar metoden MapPost
, förutom att den använder HTTP PUT. Ett lyckat svar returnerar 204 (inget innehåll). Enligt HTTP-specifikationen kräver en PUT-begäran att klienten skickar hela den uppdaterade entiteten, inte bara ändringarna. Om du vill ha stöd för partiella uppdateringar använder du HTTP PATCH-.
Testa PUT-slutpunkten
Det här exemplet använder en minnesintern databas som måste initieras varje gång appen startas. Det måste finnas ett objekt i databasen innan du gör ett PUT-anrop. Anropa GET för att se till att det finns ett objekt i databasen innan du gör ett PUT-anrop.
Uppdatera det to-do objekt som har Id = 1
och ange dess namn till "feed fish"
.
I Endpoints Explorerhögerklickar du på slutpunkten PUT och väljer Generera begäran.
Följande innehåll läggs till i filen
TodoApi.http
:Put {{TodoApi_HostAddress}}/todoitems/{id} ###
Ersätt
{id}
med1
på PUT-begäranderaden.Lägg till följande rader omedelbart efter PUT-begäranderaden:
Content-Type: application/json { "name": "feed fish", "isComplete": false }
Föregående kod lägger till ett content-type-huvud och en JSON-begärandetext.
Välj länken Skicka begäran som ligger ovanför den nya PUT-begäranderaden.
PUT-begäran skickas till appen och svaret visas i fönstret Svar. Svarstexten är tom och statuskoden är 204.
Granska och testa DELETE-slutpunkten
Exempelappen implementerar en enskild DELETE-slutpunkt med 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();
});
I Endpoints Explorerhögerklickar du på slutpunkten DELETE och väljer Generera begäran.
En DELETE-begäran läggs till i
TodoApi.http
.Ersätt
{id}
i raden för DELETE-begäran med1
. DELETE-begäran bör se ut som i följande exempel:DELETE {{TodoApi_HostAddress}}/todoitems/1 ###
Välj länken Skicka begäran för raderingsbegäran.
DELETE-begäran skickas till appen och svaret visas i fönstret Svar. Svarstexten är tom och statuskoden är 204.
Använda MapGroup-API:et
Exempelappkoden upprepar todoitems
URL-prefixet varje gång den konfigurerar en slutpunkt. API:er har ofta grupper av slutpunkter med ett vanligt URL-prefix, och metoden MapGroup är tillgänglig för att organisera sådana grupper. Det minskar repetitiv kod och gör det möjligt att anpassa hela grupper av slutpunkter med ett enda anrop till metoder som RequireAuthorization och WithMetadata.
Ersätt innehållet i Program.cs
med följande kod:
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();
Föregående kod har följande ändringar:
- Lägger till
var todoItems = app.MapGroup("/todoitems");
för att konfigurera gruppen med hjälp av URL-prefixet/todoitems
. - Ändrar alla
app.Map<HttpVerb>
metoder tilltodoItems.Map<HttpVerb>
. - Tar bort URL-prefixet
/todoitems
frånMap<HttpVerb>
-metodanrop.
Testa slutpunkterna för att kontrollera att de fungerar på samma sätt.
Använda Api:et TypedResults
Att returnera TypedResults i stället för Results har flera fördelar, inklusive testbarhet och att automatiskt returnera svarstypmetadata för OpenAPI för att beskriva slutpunkten. Mer information finns i TypedResults vs Results.
De Map<HttpVerb>
metoderna kan anropa routningshanterarmetoder i stället för att använda lambdas. Om du vill se ett exempel uppdaterar du Program.cs med följande kod:
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();
}
Den Map<HttpVerb>
koden anropar nu metoder i stället för lambdas:
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);
Dessa metoder returnerar objekt som implementerar IResult och definieras av 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();
}
Enhetstester kan anropa dessa metoder och testa att de returnerar rätt typ. Om metoden till exempel är GetAllTodos
:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
Enhetstestkoden kan verifiera att ett objekt av typen Ok<Todo[]> returneras från hanteringsmetoden. Till exempel:
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);
}
Förhindra överpublicering
För närvarande exponerar exempelappen hela Todo
objektet. Produktionsappar I produktionsprogram används ofta en delmängd av modellen för att begränsa de data som kan matas in och returneras. Det finns flera orsaker till detta och säkerheten är viktig. Delmängden av en modell kallas vanligtvis för ett dataöverföringsobjekt (DTO), indatamodell eller vymodell.
DTO- används i den här artikeln.
En DTO kan användas för att:
- Förhindra överpublicering.
- Dölj egenskaper som klienter inte ska visa.
- Utelämna vissa egenskaper för att minska nyttolaststorleken.
- Platta ut objektdiagram som innehåller kapslade objekt. Utplattade objektdiagram kan vara enklare för klienter.
Om du vill demonstrera DTO-metoden uppdaterar du klassen Todo
så att den innehåller ett hemligt fält:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Det hemliga fältet måste vara dolt från den här appen, men en administrativ app kan välja att exponera det.
Kontrollera att du kan publicera och hämta det hemliga fältet.
Skapa en fil med namnet TodoItemDTO.cs
med följande kod:
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);
}
Ersätt innehållet i Program.cs
-filen med följande kod för att använda den här DTO-modellen:
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();
}
Kontrollera att du kan publicera och hämta alla fält utom det hemliga fältet.
Felsökning med det slutförda exemplet
Om du stöter på ett problem som du inte kan lösa kan du jämföra koden med det slutförda projektet. Visa eller ladda ned slutfört projekt (hur du laddar ned).
Nästa steg
- Konfigurera JSON-serialiseringsalternativ.
- Hantera fel och undantag: Sidan undantag för utvecklare är aktiverad som standard i utvecklingsmiljön för minimala API-appar. Information om hur du hanterar fel och undantag finns i Hantera fel i ASP.NET Core-API:er.
- Ett exempel på hur du testar en minimal API-app finns i det här GitHub-exemplet.
- OpenAPI-stöd i minimala API:er.
- Snabbstart: Publicera till Azure.
- Organisera ASP.NET Grundläggande minimala API:er.
Lära sig mer
Se snabbreferensen minimala API:er
ASP.NET Core